[llvm] [Passes] Add IR-only SQLite IR tracker (PR #191852)

Yaxun Liu via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 15 17:54:34 PDT 2026


https://github.com/yxsamliu updated https://github.com/llvm/llvm-project/pull/191852

>From d8673912302f5807002e4684b7a2ccc2ea03521a Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Tue, 14 Apr 2026 14:27:49 -0400
Subject: [PATCH 1/2] [Passes] Add IR-only SQLite IR tracker

LLVM developers often need a quick way to see how IR tied to a specific source
line changes as the middle-end pass pipeline runs. The common workflow today is
-print-after-all, but dumping the whole module after every pass is slow on large
inputs, does not provide a practical way to filter by source location, and makes
it awkward to relate the same logical program point across passes.

This commit adds an IR-only tracker that records those snapshots into a SQLite
database so developers can focus on one file:line (and column) instead of
whole-module text.

Typical use: run opt or clang with the hidden LLVM option
-ir-tracker-database=/absolute/path.db alongside the usual -passes= or -O
pipeline so the run produces a database; follow-on tooling (for example the
small Python driver in a separate patch) can then list passes and show how the
IR at a chosen source line evolved.

The database is built from the existing New pass manager instrumentation in
StandardInstrumentations: an initial snapshot at pipeline entry, then one row
per pass, with each tracked instruction storing its printed IR text and its
DILocation-derived file, line, and column. In representative runs, enabling the
tracker adds on the order of 10% wall time versus the same compilation without
it, which is much less than the cost of -print-after-all on large modules.

Also:
- Gate llvm/test/Other/ir-tracker-db.ll with REQUIRES: sqlite (LLVM_HAVE_SQLITE
  from llvm/lib/Passes/CMakeLists.txt, lit.site.cfg.py.in, lit/llvm/config.py)
  so premerge bots without SQLite skip the test.
- Apply clang-format to IR tracker code in StandardInstrumentations.cpp.
---
 llvm/include/llvm/IR/PrintPasses.h            |   3 +
 .../llvm/Passes/StandardInstrumentations.h    |   8 +
 llvm/lib/IR/PrintPasses.cpp                   |   8 +
 llvm/lib/Passes/CMakeLists.txt                |  24 ++
 llvm/lib/Passes/StandardInstrumentations.cpp  | 328 ++++++++++++++++++
 llvm/test/CMakeLists.txt                      |   1 +
 llvm/test/Other/Inputs/check-ir-tracker.py    |  40 +++
 llvm/test/Other/ir-tracker-db.ll              |  34 ++
 llvm/test/lit.site.cfg.py.in                  |   1 +
 llvm/utils/lit/lit/llvm/config.py             |   3 +
 10 files changed, 450 insertions(+)
 create mode 100644 llvm/test/Other/Inputs/check-ir-tracker.py
 create mode 100644 llvm/test/Other/ir-tracker-db.ll

diff --git a/llvm/include/llvm/IR/PrintPasses.h b/llvm/include/llvm/IR/PrintPasses.h
index 0aa1b379c35cf..95e0879da9bfb 100644
--- a/llvm/include/llvm/IR/PrintPasses.h
+++ b/llvm/include/llvm/IR/PrintPasses.h
@@ -81,6 +81,9 @@ std::string doSystemDiff(StringRef Before, StringRef After,
                          StringRef OldLineFormat, StringRef NewLineFormat,
                          StringRef UnchangedLineFormat);
 
+/// Non-empty when \c -ir-tracker-database is set.
+StringRef getIRTrackerDatabasePath();
+
 } // namespace llvm
 
 #endif // LLVM_IR_PRINTPASSES_H
diff --git a/llvm/include/llvm/Passes/StandardInstrumentations.h b/llvm/include/llvm/Passes/StandardInstrumentations.h
index 4ee5ab2554868..48ad6c1aba84f 100644
--- a/llvm/include/llvm/Passes/StandardInstrumentations.h
+++ b/llvm/include/llvm/Passes/StandardInstrumentations.h
@@ -145,6 +145,13 @@ class PrintPassInstrumentation {
   int Indent = 0;
 };
 
+/// IR tracker instrumentation that records instructions with debug locations
+/// after each pass into a SQLite database when \c -ir-tracker-database is set.
+class IRTrackerInstrumentation {
+public:
+  LLVM_ABI void registerCallbacks(PassInstrumentationCallbacks &PIC);
+};
+
 class PreservedCFGCheckerInstrumentation {
 public:
   // Keeps sticky poisoned flag for the given basic block once it has been
@@ -599,6 +606,7 @@ class PrintCrashIRInstrumentation {
 class StandardInstrumentations {
   PrintIRInstrumentation PrintIR;
   PrintPassInstrumentation PrintPass;
+  IRTrackerInstrumentation IRTracker;
   TimePassesHandler TimePasses;
   TimeProfilingPassesHandler TimeProfilingPasses;
   OptNoneInstrumentation OptNone;
diff --git a/llvm/lib/IR/PrintPasses.cpp b/llvm/lib/IR/PrintPasses.cpp
index 610411a3cf978..9815872406f4c 100644
--- a/llvm/lib/IR/PrintPasses.cpp
+++ b/llvm/lib/IR/PrintPasses.cpp
@@ -109,6 +109,12 @@ static cl::list<std::string>
                             "options"),
                    cl::CommaSeparated, cl::Hidden);
 
+static cl::opt<std::string> IRTrackerDatabase(
+    "ir-tracker-database",
+    cl::desc("IR tracker: SQLite database path; record instructions with "
+             "debug locations after each pass"),
+    cl::value_desc("file"), cl::init(""), cl::Hidden);
+
 /// This is a helper to determine whether to print IR before or
 /// after a pass.
 
@@ -164,6 +170,8 @@ bool llvm::isFunctionInPrintList(StringRef FunctionName) {
          PrintFuncNames.count(std::string(FunctionName));
 }
 
+StringRef llvm::getIRTrackerDatabasePath() { return IRTrackerDatabase; }
+
 std::error_code cleanUpTempFilesImpl(ArrayRef<std::string> FileName,
                                      unsigned N) {
   std::error_code RC;
diff --git a/llvm/lib/Passes/CMakeLists.txt b/llvm/lib/Passes/CMakeLists.txt
index 5d7cd3689f3ff..710b0b1112b53 100644
--- a/llvm/lib/Passes/CMakeLists.txt
+++ b/llvm/lib/Passes/CMakeLists.txt
@@ -33,3 +33,27 @@ add_llvm_component_library(LLVMPasses
   Vectorize
   Instrumentation
   )
+
+# Optional: IR tracker SQLite backend (-ir-tracker-database).
+find_package(SQLite3 QUIET)
+if(SQLite3_FOUND)
+  set(IR_TRACKER_SQLITE_LIB SQLite::SQLite3)
+  set(IR_TRACKER_HAVE_SQLITE 1)
+else()
+  find_path(LLVM_SQLITE3_INCLUDE_DIR sqlite3.h)
+  find_library(LLVM_SQLITE3_LIBRARY sqlite3)
+  if(LLVM_SQLITE3_INCLUDE_DIR AND LLVM_SQLITE3_LIBRARY)
+    set(IR_TRACKER_SQLITE_LIB "${LLVM_SQLITE3_LIBRARY}")
+    set(IR_TRACKER_HAVE_SQLITE 1)
+  endif()
+endif()
+if(IR_TRACKER_HAVE_SQLITE)
+  target_compile_definitions(LLVMPasses PRIVATE LLVM_ENABLE_IR_TRACKER_SQLITE=1)
+  if(LLVM_SQLITE3_INCLUDE_DIR)
+    target_include_directories(LLVMPasses PRIVATE "${LLVM_SQLITE3_INCLUDE_DIR}")
+  endif()
+  target_link_libraries(LLVMPasses PRIVATE ${IR_TRACKER_SQLITE_LIB})
+  set(LLVM_HAVE_SQLITE 1 CACHE INTERNAL "Whether this LLVM build links SQLite (used by IR tracker and other optional features)")
+else()
+  set(LLVM_HAVE_SQLITE 0 CACHE INTERNAL "Whether this LLVM build links SQLite (used by IR tracker and other optional features)")
+endif()
diff --git a/llvm/lib/Passes/StandardInstrumentations.cpp b/llvm/lib/Passes/StandardInstrumentations.cpp
index 19e72a8612c4a..4e29811ab2136 100644
--- a/llvm/lib/Passes/StandardInstrumentations.cpp
+++ b/llvm/lib/Passes/StandardInstrumentations.cpp
@@ -14,6 +14,7 @@
 
 #include "llvm/Passes/StandardInstrumentations.h"
 #include "llvm/ADT/Any.h"
+#include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Analysis/LazyCallGraph.h"
 #include "llvm/Analysis/LoopInfo.h"
@@ -22,8 +23,10 @@
 #include "llvm/CodeGen/MachineModuleInfo.h"
 #include "llvm/CodeGen/MachineVerifier.h"
 #include "llvm/IR/Constants.h"
+#include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/ModuleSlotTracker.h"
 #include "llvm/IR/PassInstrumentation.h"
 #include "llvm/IR/PassManager.h"
 #include "llvm/IR/PrintPasses.h"
@@ -40,6 +43,10 @@
 #include "llvm/Support/Signals.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Support/xxhash.h"
+#ifdef LLVM_ENABLE_IR_TRACKER_SQLITE
+#include <sqlite3.h>
+#endif
+#include <memory>
 #include <unordered_map>
 #include <unordered_set>
 #include <utility>
@@ -368,6 +375,305 @@ bool isInterestingFunction(const Function &F) {
   return isFunctionInPrintList(F.getName());
 }
 
+#ifdef LLVM_ENABLE_IR_TRACKER_SQLITE
+static std::string getIRTrackerFilePath(const DILocation *Loc) {
+  if (!Loc)
+    return {};
+
+  StringRef Dir = Loc->getDirectory();
+  StringRef File = Loc->getFilename();
+  if (File.empty())
+    return {};
+  if (Dir.empty())
+    return File.str();
+
+  SmallString<256> Path(Dir);
+  sys::path::append(Path, File);
+  return std::string(Path);
+}
+
+static std::string getIRTrackerInstructionText(const Instruction &I,
+                                               ModuleSlotTracker &MST) {
+  std::string Text;
+  raw_string_ostream OS(Text);
+  I.print(OS, MST);
+  OS.flush();
+  return Text;
+}
+
+class IRTrackerState {
+  sqlite3 *DB = nullptr;
+  sqlite3_stmt *StmtInsertPass = nullptr;
+  sqlite3_stmt *StmtInsertFileIgnore = nullptr;
+  sqlite3_stmt *StmtSelectFileId = nullptr;
+  sqlite3_stmt *StmtInsertInst = nullptr;
+  unsigned NextSeq = 1;
+  bool InitialCaptured = false;
+  std::unordered_map<std::string, int> FileIdCache;
+
+  void check(int RC, const char *Ctx) const {
+    if (RC == SQLITE_OK || RC == SQLITE_DONE || RC == SQLITE_ROW)
+      return;
+    StringRef Msg = DB ? sqlite3_errmsg(DB) : sqlite3_errstr(RC);
+    report_fatal_error(Twine(Ctx) + ": " + Msg);
+  }
+
+  int getOrCreateFileId(StringRef Path) {
+    std::string Key = Path.str();
+    auto It = FileIdCache.find(Key);
+    if (It != FileIdCache.end())
+      return It->second;
+
+    check(sqlite3_bind_text(StmtInsertFileIgnore, 1, Key.data(),
+                            (int)Key.size(), SQLITE_TRANSIENT),
+          "sqlite3_bind_text(insert file)");
+    check(sqlite3_step(StmtInsertFileIgnore), "sqlite3_step(insert file)");
+    check(sqlite3_reset(StmtInsertFileIgnore), "sqlite3_reset(insert file)");
+
+    check(sqlite3_bind_text(StmtSelectFileId, 1, Key.data(), (int)Key.size(),
+                            SQLITE_TRANSIENT),
+          "sqlite3_bind_text(select file)");
+    if (sqlite3_step(StmtSelectFileId) != SQLITE_ROW)
+      report_fatal_error("ir-tracker: missing file row after insert");
+    int FileId = sqlite3_column_int(StmtSelectFileId, 0);
+    check(sqlite3_reset(StmtSelectFileId), "sqlite3_reset(select file)");
+    FileIdCache[Key] = FileId;
+    return FileId;
+  }
+
+  sqlite3_int64 insertPassRow(unsigned Seq, StringRef Phase, StringRef PassName,
+                              StringRef IRUnit) {
+    check(sqlite3_bind_int(StmtInsertPass, 1, (int)Seq), "bind seq");
+    check(sqlite3_bind_text(StmtInsertPass, 2, Phase.data(), (int)Phase.size(),
+                            SQLITE_TRANSIENT),
+          "bind phase");
+    check(sqlite3_bind_text(StmtInsertPass, 3, PassName.data(),
+                            (int)PassName.size(), SQLITE_TRANSIENT),
+          "bind pass class");
+    check(sqlite3_bind_text(StmtInsertPass, 4, IRUnit.data(),
+                            (int)IRUnit.size(), SQLITE_TRANSIENT),
+          "bind ir unit");
+    check(sqlite3_step(StmtInsertPass), "sqlite3_step(insert pass)");
+    check(sqlite3_reset(StmtInsertPass), "sqlite3_reset(insert pass)");
+    return sqlite3_last_insert_rowid(DB);
+  }
+
+  void insertInstructionRow(sqlite3_int64 PassRowId, StringRef FunctionName,
+                            StringRef BBLabel, unsigned InstSeq,
+                            StringRef Opcode, StringRef InstText, int FileId,
+                            unsigned Line, unsigned Col) {
+    check(sqlite3_bind_int64(StmtInsertInst, 1, PassRowId), "bind pass id");
+    check(sqlite3_bind_text(StmtInsertInst, 2, FunctionName.data(),
+                            (int)FunctionName.size(), SQLITE_TRANSIENT),
+          "bind function");
+    check(sqlite3_bind_text(StmtInsertInst, 3, BBLabel.data(),
+                            (int)BBLabel.size(), SQLITE_TRANSIENT),
+          "bind basic block");
+    check(sqlite3_bind_int(StmtInsertInst, 4, (int)InstSeq), "bind inst seq");
+    check(sqlite3_bind_text(StmtInsertInst, 5, Opcode.data(),
+                            (int)Opcode.size(), SQLITE_TRANSIENT),
+          "bind opcode");
+    check(sqlite3_bind_text(StmtInsertInst, 6, InstText.data(),
+                            (int)InstText.size(), SQLITE_TRANSIENT),
+          "bind inst text");
+    check(sqlite3_bind_int(StmtInsertInst, 7, FileId), "bind file id");
+    check(sqlite3_bind_int(StmtInsertInst, 8, (int)Line), "bind line");
+    check(sqlite3_bind_int(StmtInsertInst, 9, (int)Col), "bind col");
+    check(sqlite3_step(StmtInsertInst), "sqlite3_step(insert instruction)");
+    check(sqlite3_reset(StmtInsertInst), "sqlite3_reset(insert instruction)");
+  }
+
+  void indexInstructionsInFunction(const Function &F, sqlite3_int64 PassRowId) {
+    if (F.isDeclaration() || !isFunctionInPrintList(F.getName()))
+      return;
+
+    std::string FunctionName = F.getName().str();
+    ModuleSlotTracker MST(F.getParent());
+    MST.incorporateFunction(F);
+    for (const BasicBlock &BB : F) {
+      std::string BBLabel =
+          BB.hasName() ? BB.getName().str() : std::string("<unnamed>");
+      unsigned InstSeq = 0;
+      for (const Instruction &I : BB) {
+        const DebugLoc &DL = I.getDebugLoc();
+        if (!DL)
+          continue;
+        const DILocation *Loc = DL.get();
+        if (!Loc || Loc->getLine() == 0)
+          continue;
+
+        std::string FilePath = getIRTrackerFilePath(Loc);
+        if (FilePath.empty())
+          continue;
+
+        insertInstructionRow(
+            PassRowId, FunctionName, BBLabel, InstSeq++, I.getOpcodeName(),
+            getIRTrackerInstructionText(I, MST), getOrCreateFileId(FilePath),
+            Loc->getLine(), Loc->getColumn());
+      }
+    }
+  }
+
+  void indexIR(Any IR, sqlite3_int64 PassRowId) {
+    if (const auto *M = unwrapIR<Module>(IR)) {
+      for (const Function &F : *M)
+        indexInstructionsInFunction(F, PassRowId);
+      return;
+    }
+    if (const auto *F = unwrapIR<Function>(IR)) {
+      indexInstructionsInFunction(*F, PassRowId);
+      return;
+    }
+    if (const auto *C = unwrapIR<LazyCallGraph::SCC>(IR)) {
+      for (const LazyCallGraph::Node &N : *C)
+        indexInstructionsInFunction(N.getFunction(), PassRowId);
+      return;
+    }
+    if (const auto *L = unwrapIR<Loop>(IR))
+      indexInstructionsInFunction(*L->getHeader()->getParent(), PassRowId);
+  }
+
+  void captureInitialIR(Any IR) {
+    if (InitialCaptured)
+      return;
+    InitialCaptured = true;
+    sqlite3_int64 PassRowId =
+        insertPassRow(0, "initial", "<initial>", getIRName(IR));
+    indexIR(IR, PassRowId);
+  }
+
+public:
+  explicit IRTrackerState(StringRef Path) {
+    check(sqlite3_open_v2(Path.str().c_str(), &DB,
+                          SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr),
+          "sqlite3_open_v2");
+
+    const char *ResetSchema = R"SQL(
+DROP TABLE IF EXISTS ir_tracker_instructions;
+DROP TABLE IF EXISTS ir_tracker_passes;
+DROP TABLE IF EXISTS ir_tracker_files;
+DROP TABLE IF EXISTS ir_tracker_meta;
+)SQL";
+    char *Err = nullptr;
+    if (sqlite3_exec(DB, ResetSchema, nullptr, nullptr, &Err) != SQLITE_OK) {
+      std::string Msg = Err ? Err : "unknown error";
+      sqlite3_free(Err);
+      report_fatal_error(Twine("ir-tracker reset: ") + Msg);
+    }
+
+    const char *Schema = R"SQL(
+PRAGMA foreign_keys = ON;
+CREATE TABLE ir_tracker_meta (
+  key TEXT PRIMARY KEY,
+  value TEXT NOT NULL
+);
+CREATE TABLE ir_tracker_files (
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
+  path TEXT NOT NULL UNIQUE
+);
+CREATE TABLE ir_tracker_passes (
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
+  seq INTEGER NOT NULL,
+  phase TEXT NOT NULL,
+  pass_class TEXT NOT NULL,
+  ir_unit TEXT NOT NULL
+);
+CREATE UNIQUE INDEX ir_tracker_idx_passes_seq
+  ON ir_tracker_passes(seq);
+CREATE TABLE ir_tracker_instructions (
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
+  pass_id INTEGER NOT NULL REFERENCES ir_tracker_passes(id),
+  function TEXT NOT NULL,
+  basicblock TEXT NOT NULL,
+  inst_seq INTEGER NOT NULL,
+  opcode TEXT NOT NULL,
+  inst_text TEXT NOT NULL,
+  file_id INTEGER NOT NULL REFERENCES ir_tracker_files(id),
+  line INTEGER NOT NULL,
+  col INTEGER NOT NULL
+);
+CREATE INDEX ir_tracker_idx_instr_file_loc
+  ON ir_tracker_instructions(file_id, line, col);
+CREATE INDEX ir_tracker_idx_instr_pass
+  ON ir_tracker_instructions(pass_id);
+)SQL";
+    if (sqlite3_exec(DB, Schema, nullptr, nullptr, &Err) != SQLITE_OK) {
+      std::string Msg = Err ? Err : "unknown error";
+      sqlite3_free(Err);
+      report_fatal_error(Twine("ir-tracker schema: ") + Msg);
+    }
+    if (sqlite3_exec(DB,
+                     "INSERT INTO ir_tracker_meta(key, value) "
+                     "VALUES('schema_version', '1')",
+                     nullptr, nullptr, &Err) != SQLITE_OK) {
+      std::string Msg = Err ? Err : "unknown error";
+      sqlite3_free(Err);
+      report_fatal_error(Twine("ir-tracker metadata: ") + Msg);
+    }
+
+    check(sqlite3_prepare_v2(
+              DB,
+              "INSERT INTO ir_tracker_passes(seq, phase, pass_class, ir_unit) "
+              "VALUES(?, ?, ?, ?)",
+              -1, &StmtInsertPass, nullptr),
+          "sqlite3_prepare(insert pass)");
+    check(sqlite3_prepare_v2(
+              DB, "INSERT OR IGNORE INTO ir_tracker_files(path) VALUES(?)", -1,
+              &StmtInsertFileIgnore, nullptr),
+          "sqlite3_prepare(insert file)");
+    check(sqlite3_prepare_v2(DB,
+                             "SELECT id FROM ir_tracker_files WHERE path = ?",
+                             -1, &StmtSelectFileId, nullptr),
+          "sqlite3_prepare(select file)");
+    check(sqlite3_prepare_v2(
+              DB,
+              "INSERT INTO ir_tracker_instructions("
+              "pass_id, function, basicblock, inst_seq, opcode, inst_text, "
+              "file_id, line, col) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
+              -1, &StmtInsertInst, nullptr),
+          "sqlite3_prepare(insert instruction)");
+
+    check(sqlite3_exec(DB, "BEGIN IMMEDIATE", nullptr, nullptr, nullptr),
+          "sqlite3_exec(begin transaction)");
+  }
+
+  ~IRTrackerState() {
+    if (StmtInsertInst)
+      sqlite3_finalize(StmtInsertInst);
+    if (StmtSelectFileId)
+      sqlite3_finalize(StmtSelectFileId);
+    if (StmtInsertFileIgnore)
+      sqlite3_finalize(StmtInsertFileIgnore);
+    if (StmtInsertPass)
+      sqlite3_finalize(StmtInsertPass);
+    if (!DB)
+      return;
+    if (sqlite3_get_autocommit(DB) == 0)
+      check(sqlite3_exec(DB, "COMMIT", nullptr, nullptr, nullptr),
+            "sqlite3_exec(commit transaction)");
+    sqlite3_close(DB);
+  }
+
+  void beforePass(StringRef PassID, Any IR) {
+    if (InitialCaptured || isIgnored(PassID) || !shouldPrintIR(IR))
+      return;
+    captureInitialIR(IR);
+  }
+
+  void afterPass(StringRef PassID, Any IR, PassInstrumentationCallbacks &PIC) {
+    if (isIgnored(PassID) || !shouldPrintIR(IR))
+      return;
+
+    StringRef PassName = PIC.getPassNameForClassName(PassID);
+    if (PassName.empty())
+      PassName = PassID;
+    sqlite3_int64 PassRowId =
+        insertPassRow(NextSeq++, "after", PassName, getIRName(IR));
+    indexIR(IR, PassRowId);
+  }
+};
+#endif
+
 // Return true when this is a pass on IR for which printing
 // of changes is desired.
 bool isInteresting(Any IR, StringRef PassID, StringRef PassName) {
@@ -2506,10 +2812,32 @@ void PrintCrashIRInstrumentation::registerCallbacks(
       });
 }
 
+void IRTrackerInstrumentation::registerCallbacks(
+    PassInstrumentationCallbacks &PIC) {
+#ifdef LLVM_ENABLE_IR_TRACKER_SQLITE
+  StringRef Path = getIRTrackerDatabasePath();
+  if (Path.empty())
+    return;
+
+  auto State = std::make_shared<IRTrackerState>(Path);
+  PIC.registerBeforeNonSkippedPassCallback(
+      [State](StringRef PassID, Any IR) { State->beforePass(PassID, IR); });
+  PIC.registerAfterPassCallback(
+      [State, &PIC](StringRef PassID, Any IR, const PreservedAnalyses &) {
+        State->afterPass(PassID, IR, PIC);
+      });
+#else
+  if (!getIRTrackerDatabasePath().empty())
+    report_fatal_error(
+        "-ir-tracker-database requires an LLVM build linked against SQLite3");
+#endif
+}
+
 void StandardInstrumentations::registerCallbacks(
     PassInstrumentationCallbacks &PIC, ModuleAnalysisManager *MAM) {
   PrintIR.registerCallbacks(PIC);
   PrintPass.registerCallbacks(PIC);
+  IRTracker.registerCallbacks(PIC);
   TimePasses.registerCallbacks(PIC);
   OptNone.registerCallbacks(PIC);
   OptPassGate.registerCallbacks(PIC);
diff --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt
index a027cd754135c..b2acce1d8a25c 100644
--- a/llvm/test/CMakeLists.txt
+++ b/llvm/test/CMakeLists.txt
@@ -8,6 +8,7 @@ llvm_canonicalize_cmake_booleans(
   LLVM_ENABLE_CURL
   LLVM_ENABLE_HTTPLIB
   LLVM_ENABLE_ZLIB
+  LLVM_HAVE_SQLITE
   LLVM_ENABLE_ZSTD
   LLVM_ENABLE_LIBXML2
   LLVM_LINK_LLVM_DYLIB
diff --git a/llvm/test/Other/Inputs/check-ir-tracker.py b/llvm/test/Other/Inputs/check-ir-tracker.py
new file mode 100644
index 0000000000000..4fe2f70bf6c4d
--- /dev/null
+++ b/llvm/test/Other/Inputs/check-ir-tracker.py
@@ -0,0 +1,40 @@
+import sqlite3
+import sys
+
+
+def main() -> int:
+    con = sqlite3.connect(sys.argv[1])
+    cur = con.cursor()
+
+    schema = cur.execute(
+        "SELECT value FROM ir_tracker_meta WHERE key = 'schema_version'"
+    ).fetchone()
+    assert schema == ("1",), schema
+
+    passes = cur.execute(
+        "SELECT seq, phase FROM ir_tracker_passes ORDER BY seq"
+    ).fetchall()
+    assert passes, passes
+    assert passes[0] == (0, "initial"), passes
+    assert any(phase == "after" for _, phase in passes), passes
+
+    expected = set(sys.argv[2:])
+    functions = {
+        row[0]
+        for row in cur.execute(
+            "SELECT DISTINCT function FROM ir_tracker_instructions ORDER BY function"
+        )
+    }
+    assert functions == expected, (functions, expected)
+
+    rows = cur.execute(
+        "SELECT function, opcode FROM ir_tracker_instructions ORDER BY function, inst_seq"
+    ).fetchall()
+    for function in expected:
+        assert any(row[0] == function for row in rows), rows
+
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())
diff --git a/llvm/test/Other/ir-tracker-db.ll b/llvm/test/Other/ir-tracker-db.ll
new file mode 100644
index 0000000000000..a803f25eed987
--- /dev/null
+++ b/llvm/test/Other/ir-tracker-db.ll
@@ -0,0 +1,34 @@
+; REQUIRES: sqlite
+;
+; RUN: opt -disable-output -passes=instcombine -ir-tracker-database=%t.db %s
+; RUN: %python %S/Inputs/check-ir-tracker.py %t.db f g
+; RUN: opt -disable-output -passes=instcombine -filter-print-funcs=f -ir-tracker-database=%t-filter.db %s
+; RUN: %python %S/Inputs/check-ir-tracker.py %t-filter.db f
+
+define i32 @f(i32 %x) !dbg !6 {
+entry:
+  %add = add i32 %x, 1, !dbg !8
+  ret i32 %add, !dbg !9
+}
+
+define i32 @g(i32 %x) !dbg !7 {
+entry:
+  %mul = mul i32 %x, 2, !dbg !10
+  ret i32 %mul, !dbg !11
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "ir-tracker-test", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "ir-tracker.c", directory: "/tmp")
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!4 = !DISubroutineType(types: !5)
+!5 = !{!3, !3}
+!6 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 7, type: !4, scopeLine: 7, spFlags: DISPFlagDefinition, unit: !0)
+!7 = distinct !DISubprogram(name: "g", scope: !1, file: !1, line: 13, type: !4, scopeLine: 13, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 8, column: 3, scope: !6)
+!9 = !DILocation(line: 9, column: 3, scope: !6)
+!10 = !DILocation(line: 14, column: 3, scope: !7)
+!11 = !DILocation(line: 15, column: 3, scope: !7)
diff --git a/llvm/test/lit.site.cfg.py.in b/llvm/test/lit.site.cfg.py.in
index 6fe48f21cc685..b953968dbab19 100644
--- a/llvm/test/lit.site.cfg.py.in
+++ b/llvm/test/lit.site.cfg.py.in
@@ -35,6 +35,7 @@ config.host_ldflags = '@HOST_LDFLAGS@'
 config.llvm_use_intel_jitevents = @LLVM_USE_INTEL_JITEVENTS@
 config.llvm_use_sanitizer = "@LLVM_USE_SANITIZER@"
 config.have_zlib = @LLVM_ENABLE_ZLIB@
+config.have_sqlite = @LLVM_HAVE_SQLITE@
 config.have_zstd = @LLVM_ENABLE_ZSTD@
 config.have_libxml2 = @LLVM_ENABLE_LIBXML2@
 config.have_curl = @LLVM_ENABLE_CURL@
diff --git a/llvm/utils/lit/lit/llvm/config.py b/llvm/utils/lit/lit/llvm/config.py
index de70e80e60177..e10a86636c159 100644
--- a/llvm/utils/lit/lit/llvm/config.py
+++ b/llvm/utils/lit/lit/llvm/config.py
@@ -147,6 +147,9 @@ def __init__(self, lit_config, config):
         have_zlib = getattr(config, "have_zlib", None)
         if have_zlib:
             features.add("zlib")
+        have_sqlite = getattr(config, "have_sqlite", None)
+        if have_sqlite:
+            features.add("sqlite")
         have_zstd = getattr(config, "have_zstd", None)
         if have_zstd:
             features.add("zstd")

>From 50721951affb17bb78805c4cf10b3bf8e9196a3f Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Wed, 15 Apr 2026 14:24:31 -0400
Subject: [PATCH 2/2] [Passes] Switch local Phase A prototype from SQLite to
 JSONL output (NFC to branch stack)

Replace the local Phase A recorder experiment from SQLite with a compiler-emitted
JSON Lines sink. This keeps the same StandardInstrumentations hook and tracked-
instruction walk, but changes the interchange format to JSONL to avoid custom
escaping while preserving the low-overhead recording cost seen in the local
experiments. The local IR-only regression test is updated to validate the JSONL
output directly.
---
 llvm/include/llvm/IR/PrintPasses.h            |   4 +-
 .../llvm/Passes/StandardInstrumentations.h    |   2 +-
 llvm/lib/IR/PrintPasses.cpp                   |  10 +-
 llvm/lib/Passes/CMakeLists.txt                |  24 --
 llvm/lib/Passes/StandardInstrumentations.cpp  | 269 +++---------------
 llvm/test/CMakeLists.txt                      |   1 -
 llvm/test/Other/Inputs/check-ir-tracker.py    |  40 ---
 llvm/test/Other/ir-tracker-db.ll              |  31 +-
 llvm/test/lit.site.cfg.py.in                  |   1 -
 llvm/utils/lit/lit/llvm/config.py             |   3 -
 10 files changed, 78 insertions(+), 307 deletions(-)
 delete mode 100644 llvm/test/Other/Inputs/check-ir-tracker.py

diff --git a/llvm/include/llvm/IR/PrintPasses.h b/llvm/include/llvm/IR/PrintPasses.h
index 95e0879da9bfb..9511c3f8209fc 100644
--- a/llvm/include/llvm/IR/PrintPasses.h
+++ b/llvm/include/llvm/IR/PrintPasses.h
@@ -81,8 +81,8 @@ std::string doSystemDiff(StringRef Before, StringRef After,
                          StringRef OldLineFormat, StringRef NewLineFormat,
                          StringRef UnchangedLineFormat);
 
-/// Non-empty when \c -ir-tracker-database is set.
-StringRef getIRTrackerDatabasePath();
+/// Non-empty when \c -ir-tracker-json-output is set.
+StringRef getIRTrackerJSONOutputPath();
 
 } // namespace llvm
 
diff --git a/llvm/include/llvm/Passes/StandardInstrumentations.h b/llvm/include/llvm/Passes/StandardInstrumentations.h
index 48ad6c1aba84f..55460a760bb3b 100644
--- a/llvm/include/llvm/Passes/StandardInstrumentations.h
+++ b/llvm/include/llvm/Passes/StandardInstrumentations.h
@@ -146,7 +146,7 @@ class PrintPassInstrumentation {
 };
 
 /// IR tracker instrumentation that records instructions with debug locations
-/// after each pass into a SQLite database when \c -ir-tracker-database is set.
+/// after each pass into JSON Lines when \c -ir-tracker-json-output is set.
 class IRTrackerInstrumentation {
 public:
   LLVM_ABI void registerCallbacks(PassInstrumentationCallbacks &PIC);
diff --git a/llvm/lib/IR/PrintPasses.cpp b/llvm/lib/IR/PrintPasses.cpp
index 9815872406f4c..a678cbac4227f 100644
--- a/llvm/lib/IR/PrintPasses.cpp
+++ b/llvm/lib/IR/PrintPasses.cpp
@@ -109,10 +109,10 @@ static cl::list<std::string>
                             "options"),
                    cl::CommaSeparated, cl::Hidden);
 
-static cl::opt<std::string> IRTrackerDatabase(
-    "ir-tracker-database",
-    cl::desc("IR tracker: SQLite database path; record instructions with "
-             "debug locations after each pass"),
+static cl::opt<std::string> IRTrackerJSONOutput(
+    "ir-tracker-json-output",
+    cl::desc("IR tracker: JSON Lines output path; record instructions with debug "
+             "locations after each pass"),
     cl::value_desc("file"), cl::init(""), cl::Hidden);
 
 /// This is a helper to determine whether to print IR before or
@@ -170,7 +170,7 @@ bool llvm::isFunctionInPrintList(StringRef FunctionName) {
          PrintFuncNames.count(std::string(FunctionName));
 }
 
-StringRef llvm::getIRTrackerDatabasePath() { return IRTrackerDatabase; }
+StringRef llvm::getIRTrackerJSONOutputPath() { return IRTrackerJSONOutput; }
 
 std::error_code cleanUpTempFilesImpl(ArrayRef<std::string> FileName,
                                      unsigned N) {
diff --git a/llvm/lib/Passes/CMakeLists.txt b/llvm/lib/Passes/CMakeLists.txt
index 710b0b1112b53..5d7cd3689f3ff 100644
--- a/llvm/lib/Passes/CMakeLists.txt
+++ b/llvm/lib/Passes/CMakeLists.txt
@@ -33,27 +33,3 @@ add_llvm_component_library(LLVMPasses
   Vectorize
   Instrumentation
   )
-
-# Optional: IR tracker SQLite backend (-ir-tracker-database).
-find_package(SQLite3 QUIET)
-if(SQLite3_FOUND)
-  set(IR_TRACKER_SQLITE_LIB SQLite::SQLite3)
-  set(IR_TRACKER_HAVE_SQLITE 1)
-else()
-  find_path(LLVM_SQLITE3_INCLUDE_DIR sqlite3.h)
-  find_library(LLVM_SQLITE3_LIBRARY sqlite3)
-  if(LLVM_SQLITE3_INCLUDE_DIR AND LLVM_SQLITE3_LIBRARY)
-    set(IR_TRACKER_SQLITE_LIB "${LLVM_SQLITE3_LIBRARY}")
-    set(IR_TRACKER_HAVE_SQLITE 1)
-  endif()
-endif()
-if(IR_TRACKER_HAVE_SQLITE)
-  target_compile_definitions(LLVMPasses PRIVATE LLVM_ENABLE_IR_TRACKER_SQLITE=1)
-  if(LLVM_SQLITE3_INCLUDE_DIR)
-    target_include_directories(LLVMPasses PRIVATE "${LLVM_SQLITE3_INCLUDE_DIR}")
-  endif()
-  target_link_libraries(LLVMPasses PRIVATE ${IR_TRACKER_SQLITE_LIB})
-  set(LLVM_HAVE_SQLITE 1 CACHE INTERNAL "Whether this LLVM build links SQLite (used by IR tracker and other optional features)")
-else()
-  set(LLVM_HAVE_SQLITE 0 CACHE INTERNAL "Whether this LLVM build links SQLite (used by IR tracker and other optional features)")
-endif()
diff --git a/llvm/lib/Passes/StandardInstrumentations.cpp b/llvm/lib/Passes/StandardInstrumentations.cpp
index 4e29811ab2136..f8b84f03fcad1 100644
--- a/llvm/lib/Passes/StandardInstrumentations.cpp
+++ b/llvm/lib/Passes/StandardInstrumentations.cpp
@@ -35,17 +35,16 @@
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/Error.h"
+#include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/GraphWriter.h"
+#include "llvm/Support/JSON.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/Program.h"
 #include "llvm/Support/Regex.h"
 #include "llvm/Support/Signals.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Support/xxhash.h"
-#ifdef LLVM_ENABLE_IR_TRACKER_SQLITE
-#include <sqlite3.h>
-#endif
 #include <memory>
 #include <unordered_map>
 #include <unordered_set>
@@ -375,7 +374,6 @@ bool isInterestingFunction(const Function &F) {
   return isFunctionInPrintList(F.getName());
 }
 
-#ifdef LLVM_ENABLE_IR_TRACKER_SQLITE
 static std::string getIRTrackerFilePath(const DILocation *Loc) {
   if (!Loc)
     return {};
@@ -401,89 +399,23 @@ static std::string getIRTrackerInstructionText(const Instruction &I,
   return Text;
 }
 
-class IRTrackerState {
-  sqlite3 *DB = nullptr;
-  sqlite3_stmt *StmtInsertPass = nullptr;
-  sqlite3_stmt *StmtInsertFileIgnore = nullptr;
-  sqlite3_stmt *StmtSelectFileId = nullptr;
-  sqlite3_stmt *StmtInsertInst = nullptr;
+class IRTrackerJSONState {
+  std::unique_ptr<raw_fd_ostream> OS;
   unsigned NextSeq = 1;
   bool InitialCaptured = false;
-  std::unordered_map<std::string, int> FileIdCache;
 
-  void check(int RC, const char *Ctx) const {
-    if (RC == SQLITE_OK || RC == SQLITE_DONE || RC == SQLITE_ROW)
-      return;
-    StringRef Msg = DB ? sqlite3_errmsg(DB) : sqlite3_errstr(RC);
-    report_fatal_error(Twine(Ctx) + ": " + Msg);
-  }
-
-  int getOrCreateFileId(StringRef Path) {
-    std::string Key = Path.str();
-    auto It = FileIdCache.find(Key);
-    if (It != FileIdCache.end())
-      return It->second;
-
-    check(sqlite3_bind_text(StmtInsertFileIgnore, 1, Key.data(),
-                            (int)Key.size(), SQLITE_TRANSIENT),
-          "sqlite3_bind_text(insert file)");
-    check(sqlite3_step(StmtInsertFileIgnore), "sqlite3_step(insert file)");
-    check(sqlite3_reset(StmtInsertFileIgnore), "sqlite3_reset(insert file)");
-
-    check(sqlite3_bind_text(StmtSelectFileId, 1, Key.data(), (int)Key.size(),
-                            SQLITE_TRANSIENT),
-          "sqlite3_bind_text(select file)");
-    if (sqlite3_step(StmtSelectFileId) != SQLITE_ROW)
-      report_fatal_error("ir-tracker: missing file row after insert");
-    int FileId = sqlite3_column_int(StmtSelectFileId, 0);
-    check(sqlite3_reset(StmtSelectFileId), "sqlite3_reset(select file)");
-    FileIdCache[Key] = FileId;
-    return FileId;
-  }
-
-  sqlite3_int64 insertPassRow(unsigned Seq, StringRef Phase, StringRef PassName,
-                              StringRef IRUnit) {
-    check(sqlite3_bind_int(StmtInsertPass, 1, (int)Seq), "bind seq");
-    check(sqlite3_bind_text(StmtInsertPass, 2, Phase.data(), (int)Phase.size(),
-                            SQLITE_TRANSIENT),
-          "bind phase");
-    check(sqlite3_bind_text(StmtInsertPass, 3, PassName.data(),
-                            (int)PassName.size(), SQLITE_TRANSIENT),
-          "bind pass class");
-    check(sqlite3_bind_text(StmtInsertPass, 4, IRUnit.data(),
-                            (int)IRUnit.size(), SQLITE_TRANSIENT),
-          "bind ir unit");
-    check(sqlite3_step(StmtInsertPass), "sqlite3_step(insert pass)");
-    check(sqlite3_reset(StmtInsertPass), "sqlite3_reset(insert pass)");
-    return sqlite3_last_insert_rowid(DB);
-  }
-
-  void insertInstructionRow(sqlite3_int64 PassRowId, StringRef FunctionName,
-                            StringRef BBLabel, unsigned InstSeq,
-                            StringRef Opcode, StringRef InstText, int FileId,
-                            unsigned Line, unsigned Col) {
-    check(sqlite3_bind_int64(StmtInsertInst, 1, PassRowId), "bind pass id");
-    check(sqlite3_bind_text(StmtInsertInst, 2, FunctionName.data(),
-                            (int)FunctionName.size(), SQLITE_TRANSIENT),
-          "bind function");
-    check(sqlite3_bind_text(StmtInsertInst, 3, BBLabel.data(),
-                            (int)BBLabel.size(), SQLITE_TRANSIENT),
-          "bind basic block");
-    check(sqlite3_bind_int(StmtInsertInst, 4, (int)InstSeq), "bind inst seq");
-    check(sqlite3_bind_text(StmtInsertInst, 5, Opcode.data(),
-                            (int)Opcode.size(), SQLITE_TRANSIENT),
-          "bind opcode");
-    check(sqlite3_bind_text(StmtInsertInst, 6, InstText.data(),
-                            (int)InstText.size(), SQLITE_TRANSIENT),
-          "bind inst text");
-    check(sqlite3_bind_int(StmtInsertInst, 7, FileId), "bind file id");
-    check(sqlite3_bind_int(StmtInsertInst, 8, (int)Line), "bind line");
-    check(sqlite3_bind_int(StmtInsertInst, 9, (int)Col), "bind col");
-    check(sqlite3_step(StmtInsertInst), "sqlite3_step(insert instruction)");
-    check(sqlite3_reset(StmtInsertInst), "sqlite3_reset(insert instruction)");
-  }
-
-  void indexInstructionsInFunction(const Function &F, sqlite3_int64 PassRowId) {
+  void writePassRecord(unsigned Seq, StringRef Phase, StringRef PassName,
+                       StringRef IRUnit) {
+    json::Object Obj;
+    Obj["kind"] = "pass";
+    Obj["seq"] = Seq;
+    Obj["phase"] = Phase.str();
+    Obj["pass"] = PassName.str();
+    Obj["ir_unit"] = IRUnit.str();
+    *OS << formatv("{0}\n", json::Value(std::move(Obj)));
+  }
+
+  void writeInstructionsInFunction(const Function &F) {
     if (F.isDeclaration() || !isFunctionInPrintList(F.getName()))
       return;
 
@@ -506,158 +438,56 @@ class IRTrackerState {
         if (FilePath.empty())
           continue;
 
-        insertInstructionRow(
-            PassRowId, FunctionName, BBLabel, InstSeq++, I.getOpcodeName(),
-            getIRTrackerInstructionText(I, MST), getOrCreateFileId(FilePath),
-            Loc->getLine(), Loc->getColumn());
+        json::Object Obj;
+        Obj["kind"] = "inst";
+        Obj["file"] = FilePath;
+        Obj["line"] = Loc->getLine();
+        Obj["col"] = Loc->getColumn();
+        Obj["function"] = FunctionName;
+        Obj["block"] = BBLabel;
+        Obj["inst_seq"] = InstSeq++;
+        Obj["opcode"] = std::string(I.getOpcodeName());
+        Obj["text"] = getIRTrackerInstructionText(I, MST);
+        *OS << formatv("{0}\n", json::Value(std::move(Obj)));
       }
     }
   }
 
-  void indexIR(Any IR, sqlite3_int64 PassRowId) {
+  void writeIR(Any IR, unsigned Seq, StringRef Phase, StringRef PassName,
+               StringRef IRUnit) {
+    writePassRecord(Seq, Phase, PassName, IRUnit);
     if (const auto *M = unwrapIR<Module>(IR)) {
       for (const Function &F : *M)
-        indexInstructionsInFunction(F, PassRowId);
+        writeInstructionsInFunction(F);
       return;
     }
     if (const auto *F = unwrapIR<Function>(IR)) {
-      indexInstructionsInFunction(*F, PassRowId);
+      writeInstructionsInFunction(*F);
       return;
     }
     if (const auto *C = unwrapIR<LazyCallGraph::SCC>(IR)) {
       for (const LazyCallGraph::Node &N : *C)
-        indexInstructionsInFunction(N.getFunction(), PassRowId);
+        writeInstructionsInFunction(N.getFunction());
       return;
     }
     if (const auto *L = unwrapIR<Loop>(IR))
-      indexInstructionsInFunction(*L->getHeader()->getParent(), PassRowId);
-  }
-
-  void captureInitialIR(Any IR) {
-    if (InitialCaptured)
-      return;
-    InitialCaptured = true;
-    sqlite3_int64 PassRowId =
-        insertPassRow(0, "initial", "<initial>", getIRName(IR));
-    indexIR(IR, PassRowId);
+      writeInstructionsInFunction(*L->getHeader()->getParent());
   }
 
 public:
-  explicit IRTrackerState(StringRef Path) {
-    check(sqlite3_open_v2(Path.str().c_str(), &DB,
-                          SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr),
-          "sqlite3_open_v2");
-
-    const char *ResetSchema = R"SQL(
-DROP TABLE IF EXISTS ir_tracker_instructions;
-DROP TABLE IF EXISTS ir_tracker_passes;
-DROP TABLE IF EXISTS ir_tracker_files;
-DROP TABLE IF EXISTS ir_tracker_meta;
-)SQL";
-    char *Err = nullptr;
-    if (sqlite3_exec(DB, ResetSchema, nullptr, nullptr, &Err) != SQLITE_OK) {
-      std::string Msg = Err ? Err : "unknown error";
-      sqlite3_free(Err);
-      report_fatal_error(Twine("ir-tracker reset: ") + Msg);
-    }
-
-    const char *Schema = R"SQL(
-PRAGMA foreign_keys = ON;
-CREATE TABLE ir_tracker_meta (
-  key TEXT PRIMARY KEY,
-  value TEXT NOT NULL
-);
-CREATE TABLE ir_tracker_files (
-  id INTEGER PRIMARY KEY AUTOINCREMENT,
-  path TEXT NOT NULL UNIQUE
-);
-CREATE TABLE ir_tracker_passes (
-  id INTEGER PRIMARY KEY AUTOINCREMENT,
-  seq INTEGER NOT NULL,
-  phase TEXT NOT NULL,
-  pass_class TEXT NOT NULL,
-  ir_unit TEXT NOT NULL
-);
-CREATE UNIQUE INDEX ir_tracker_idx_passes_seq
-  ON ir_tracker_passes(seq);
-CREATE TABLE ir_tracker_instructions (
-  id INTEGER PRIMARY KEY AUTOINCREMENT,
-  pass_id INTEGER NOT NULL REFERENCES ir_tracker_passes(id),
-  function TEXT NOT NULL,
-  basicblock TEXT NOT NULL,
-  inst_seq INTEGER NOT NULL,
-  opcode TEXT NOT NULL,
-  inst_text TEXT NOT NULL,
-  file_id INTEGER NOT NULL REFERENCES ir_tracker_files(id),
-  line INTEGER NOT NULL,
-  col INTEGER NOT NULL
-);
-CREATE INDEX ir_tracker_idx_instr_file_loc
-  ON ir_tracker_instructions(file_id, line, col);
-CREATE INDEX ir_tracker_idx_instr_pass
-  ON ir_tracker_instructions(pass_id);
-)SQL";
-    if (sqlite3_exec(DB, Schema, nullptr, nullptr, &Err) != SQLITE_OK) {
-      std::string Msg = Err ? Err : "unknown error";
-      sqlite3_free(Err);
-      report_fatal_error(Twine("ir-tracker schema: ") + Msg);
-    }
-    if (sqlite3_exec(DB,
-                     "INSERT INTO ir_tracker_meta(key, value) "
-                     "VALUES('schema_version', '1')",
-                     nullptr, nullptr, &Err) != SQLITE_OK) {
-      std::string Msg = Err ? Err : "unknown error";
-      sqlite3_free(Err);
-      report_fatal_error(Twine("ir-tracker metadata: ") + Msg);
-    }
-
-    check(sqlite3_prepare_v2(
-              DB,
-              "INSERT INTO ir_tracker_passes(seq, phase, pass_class, ir_unit) "
-              "VALUES(?, ?, ?, ?)",
-              -1, &StmtInsertPass, nullptr),
-          "sqlite3_prepare(insert pass)");
-    check(sqlite3_prepare_v2(
-              DB, "INSERT OR IGNORE INTO ir_tracker_files(path) VALUES(?)", -1,
-              &StmtInsertFileIgnore, nullptr),
-          "sqlite3_prepare(insert file)");
-    check(sqlite3_prepare_v2(DB,
-                             "SELECT id FROM ir_tracker_files WHERE path = ?",
-                             -1, &StmtSelectFileId, nullptr),
-          "sqlite3_prepare(select file)");
-    check(sqlite3_prepare_v2(
-              DB,
-              "INSERT INTO ir_tracker_instructions("
-              "pass_id, function, basicblock, inst_seq, opcode, inst_text, "
-              "file_id, line, col) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
-              -1, &StmtInsertInst, nullptr),
-          "sqlite3_prepare(insert instruction)");
-
-    check(sqlite3_exec(DB, "BEGIN IMMEDIATE", nullptr, nullptr, nullptr),
-          "sqlite3_exec(begin transaction)");
-  }
-
-  ~IRTrackerState() {
-    if (StmtInsertInst)
-      sqlite3_finalize(StmtInsertInst);
-    if (StmtSelectFileId)
-      sqlite3_finalize(StmtSelectFileId);
-    if (StmtInsertFileIgnore)
-      sqlite3_finalize(StmtInsertFileIgnore);
-    if (StmtInsertPass)
-      sqlite3_finalize(StmtInsertPass);
-    if (!DB)
-      return;
-    if (sqlite3_get_autocommit(DB) == 0)
-      check(sqlite3_exec(DB, "COMMIT", nullptr, nullptr, nullptr),
-            "sqlite3_exec(commit transaction)");
-    sqlite3_close(DB);
+  explicit IRTrackerJSONState(StringRef Path) {
+    std::error_code EC;
+    OS = std::make_unique<raw_fd_ostream>(Path, EC, sys::fs::OF_Text);
+    if (EC)
+      report_fatal_error(Twine("ir-tracker json output open: ") +
+                         EC.message());
   }
 
   void beforePass(StringRef PassID, Any IR) {
     if (InitialCaptured || isIgnored(PassID) || !shouldPrintIR(IR))
       return;
-    captureInitialIR(IR);
+    InitialCaptured = true;
+    writeIR(IR, 0, "initial", "<initial>", getIRName(IR));
   }
 
   void afterPass(StringRef PassID, Any IR, PassInstrumentationCallbacks &PIC) {
@@ -667,12 +497,9 @@ CREATE INDEX ir_tracker_idx_instr_pass
     StringRef PassName = PIC.getPassNameForClassName(PassID);
     if (PassName.empty())
       PassName = PassID;
-    sqlite3_int64 PassRowId =
-        insertPassRow(NextSeq++, "after", PassName, getIRName(IR));
-    indexIR(IR, PassRowId);
+    writeIR(IR, NextSeq++, "after", PassName, getIRName(IR));
   }
 };
-#endif
 
 // Return true when this is a pass on IR for which printing
 // of changes is desired.
@@ -2814,23 +2641,17 @@ void PrintCrashIRInstrumentation::registerCallbacks(
 
 void IRTrackerInstrumentation::registerCallbacks(
     PassInstrumentationCallbacks &PIC) {
-#ifdef LLVM_ENABLE_IR_TRACKER_SQLITE
-  StringRef Path = getIRTrackerDatabasePath();
+  StringRef Path = getIRTrackerJSONOutputPath();
   if (Path.empty())
     return;
 
-  auto State = std::make_shared<IRTrackerState>(Path);
+  auto State = std::make_shared<IRTrackerJSONState>(Path);
   PIC.registerBeforeNonSkippedPassCallback(
       [State](StringRef PassID, Any IR) { State->beforePass(PassID, IR); });
   PIC.registerAfterPassCallback(
       [State, &PIC](StringRef PassID, Any IR, const PreservedAnalyses &) {
         State->afterPass(PassID, IR, PIC);
       });
-#else
-  if (!getIRTrackerDatabasePath().empty())
-    report_fatal_error(
-        "-ir-tracker-database requires an LLVM build linked against SQLite3");
-#endif
 }
 
 void StandardInstrumentations::registerCallbacks(
diff --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt
index b2acce1d8a25c..a027cd754135c 100644
--- a/llvm/test/CMakeLists.txt
+++ b/llvm/test/CMakeLists.txt
@@ -8,7 +8,6 @@ llvm_canonicalize_cmake_booleans(
   LLVM_ENABLE_CURL
   LLVM_ENABLE_HTTPLIB
   LLVM_ENABLE_ZLIB
-  LLVM_HAVE_SQLITE
   LLVM_ENABLE_ZSTD
   LLVM_ENABLE_LIBXML2
   LLVM_LINK_LLVM_DYLIB
diff --git a/llvm/test/Other/Inputs/check-ir-tracker.py b/llvm/test/Other/Inputs/check-ir-tracker.py
deleted file mode 100644
index 4fe2f70bf6c4d..0000000000000
--- a/llvm/test/Other/Inputs/check-ir-tracker.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import sqlite3
-import sys
-
-
-def main() -> int:
-    con = sqlite3.connect(sys.argv[1])
-    cur = con.cursor()
-
-    schema = cur.execute(
-        "SELECT value FROM ir_tracker_meta WHERE key = 'schema_version'"
-    ).fetchone()
-    assert schema == ("1",), schema
-
-    passes = cur.execute(
-        "SELECT seq, phase FROM ir_tracker_passes ORDER BY seq"
-    ).fetchall()
-    assert passes, passes
-    assert passes[0] == (0, "initial"), passes
-    assert any(phase == "after" for _, phase in passes), passes
-
-    expected = set(sys.argv[2:])
-    functions = {
-        row[0]
-        for row in cur.execute(
-            "SELECT DISTINCT function FROM ir_tracker_instructions ORDER BY function"
-        )
-    }
-    assert functions == expected, (functions, expected)
-
-    rows = cur.execute(
-        "SELECT function, opcode FROM ir_tracker_instructions ORDER BY function, inst_seq"
-    ).fetchall()
-    for function in expected:
-        assert any(row[0] == function for row in rows), rows
-
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/llvm/test/Other/ir-tracker-db.ll b/llvm/test/Other/ir-tracker-db.ll
index a803f25eed987..71a3bddea4452 100644
--- a/llvm/test/Other/ir-tracker-db.ll
+++ b/llvm/test/Other/ir-tracker-db.ll
@@ -1,9 +1,7 @@
-; REQUIRES: sqlite
-;
-; RUN: opt -disable-output -passes=instcombine -ir-tracker-database=%t.db %s
-; RUN: %python %S/Inputs/check-ir-tracker.py %t.db f g
-; RUN: opt -disable-output -passes=instcombine -filter-print-funcs=f -ir-tracker-database=%t-filter.db %s
-; RUN: %python %S/Inputs/check-ir-tracker.py %t-filter.db f
+; RUN: opt -disable-output -passes=instcombine -ir-tracker-json-output=%t.jsonl %s
+; RUN: FileCheck %s --input-file=%t.jsonl --check-prefix=ALL
+; RUN: opt -disable-output -passes=instcombine -filter-print-funcs=f -ir-tracker-json-output=%t-filter.jsonl %s
+; RUN: FileCheck %s --input-file=%t-filter.jsonl --check-prefix=FILTER
 
 define i32 @f(i32 %x) !dbg !6 {
 entry:
@@ -32,3 +30,24 @@ entry:
 !9 = !DILocation(line: 9, column: 3, scope: !6)
 !10 = !DILocation(line: 14, column: 3, scope: !7)
 !11 = !DILocation(line: 15, column: 3, scope: !7)
+
+; ALL: {"kind":"pass","phase":"initial","pass":"<initial>","ir_unit":"[module]","seq":0}
+; ALL-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"f","inst_seq":0,"kind":"inst","line":8,"opcode":"add","text":"  %add = add i32 %x, 1, !dbg !{{[0-9]+}}"}
+; ALL-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"f","inst_seq":1,"kind":"inst","line":9,"opcode":"ret","text":"  ret i32 %add, !dbg !{{[0-9]+}}"}
+; ALL-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"g","inst_seq":0,"kind":"inst","line":14,"opcode":"mul","text":"  %mul = mul i32 %x, 2, !dbg !{{[0-9]+}}"}
+; ALL-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"g","inst_seq":1,"kind":"inst","line":15,"opcode":"ret","text":"  ret i32 %mul, !dbg !{{[0-9]+}}"}
+; ALL: {"kind":"pass","phase":"after","pass":"instcombine","ir_unit":"f","seq":1}
+; ALL-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"f","inst_seq":0,"kind":"inst","line":8,"opcode":"add","text":"  %add = add i32 %x, 1, !dbg !{{[0-9]+}}"}
+; ALL-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"f","inst_seq":1,"kind":"inst","line":9,"opcode":"ret","text":"  ret i32 %add, !dbg !{{[0-9]+}}"}
+; ALL: {"kind":"pass","phase":"after","pass":"instcombine","ir_unit":"g","seq":2}
+; ALL-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"g","inst_seq":0,"kind":"inst","line":14,"opcode":"shl","text":"  %mul = shl i32 %x, 1, !dbg !{{[0-9]+}}"}
+; ALL-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"g","inst_seq":1,"kind":"inst","line":15,"opcode":"ret","text":"  ret i32 %mul, !dbg !{{[0-9]+}}"}
+
+; FILTER: {"kind":"pass","phase":"initial","pass":"<initial>","ir_unit":"[module]","seq":0}
+; FILTER-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"f","inst_seq":0,"kind":"inst","line":8,"opcode":"add","text":"  %add = add i32 %x, 1, !dbg !{{[0-9]+}}"}
+; FILTER-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"f","inst_seq":1,"kind":"inst","line":9,"opcode":"ret","text":"  ret i32 %add, !dbg !{{[0-9]+}}"}
+; FILTER-NOT: "function":"g"
+; FILTER: {"kind":"pass","phase":"after","pass":"instcombine","ir_unit":"f","seq":1}
+; FILTER-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"f","inst_seq":0,"kind":"inst","line":8,"opcode":"add","text":"  %add = add i32 %x, 1, !dbg !{{[0-9]+}}"}
+; FILTER-NEXT: {"block":"entry","col":3,"file":"/tmp/ir-tracker.c","function":"f","inst_seq":1,"kind":"inst","line":9,"opcode":"ret","text":"  ret i32 %add, !dbg !{{[0-9]+}}"}
+; FILTER-NOT: "ir_unit":"g"
diff --git a/llvm/test/lit.site.cfg.py.in b/llvm/test/lit.site.cfg.py.in
index b953968dbab19..6fe48f21cc685 100644
--- a/llvm/test/lit.site.cfg.py.in
+++ b/llvm/test/lit.site.cfg.py.in
@@ -35,7 +35,6 @@ config.host_ldflags = '@HOST_LDFLAGS@'
 config.llvm_use_intel_jitevents = @LLVM_USE_INTEL_JITEVENTS@
 config.llvm_use_sanitizer = "@LLVM_USE_SANITIZER@"
 config.have_zlib = @LLVM_ENABLE_ZLIB@
-config.have_sqlite = @LLVM_HAVE_SQLITE@
 config.have_zstd = @LLVM_ENABLE_ZSTD@
 config.have_libxml2 = @LLVM_ENABLE_LIBXML2@
 config.have_curl = @LLVM_ENABLE_CURL@
diff --git a/llvm/utils/lit/lit/llvm/config.py b/llvm/utils/lit/lit/llvm/config.py
index e10a86636c159..de70e80e60177 100644
--- a/llvm/utils/lit/lit/llvm/config.py
+++ b/llvm/utils/lit/lit/llvm/config.py
@@ -147,9 +147,6 @@ def __init__(self, lit_config, config):
         have_zlib = getattr(config, "have_zlib", None)
         if have_zlib:
             features.add("zlib")
-        have_sqlite = getattr(config, "have_sqlite", None)
-        if have_sqlite:
-            features.add("sqlite")
         have_zstd = getattr(config, "have_zstd", None)
         if have_zstd:
             features.add("zstd")



More information about the llvm-commits mailing list