[llvm] Add IR tracker query tool and documentation (PR #191856)

Yaxun Liu via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 15 18:10:50 PDT 2026


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

>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/3] [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/3] [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")

>From 7a3ccccced74ee18c6c2e4c5d9232fe3b4cd59f8 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Wed, 15 Apr 2026 21:03:48 -0400
Subject: [PATCH 3/3] Add local Phase B query tool and JSONL-to-SQLite workflow

Add the local Phase B query tool and tests on top of the JSONL-based Phase A
prototype. The Python tool builds a SQLite query database offline from the
compiler-emitted JSONL tracker output, then provides the same passes/trace/show
/sql query flow on top of that DB.
---
 llvm/docs/IRTracker.rst                       | 204 +++++++
 llvm/docs/UserGuides.rst                      |   1 +
 llvm/test/lit.cfg.py                          |   6 +
 .../tools/llvm-ir-tracker/build-json.test     |  42 ++
 llvm/test/tools/llvm-ir-tracker/help.test     |  16 +
 llvm/test/tools/llvm-ir-tracker/show.ll       |  49 ++
 llvm/tools/ir-tracker/CMakeLists.txt          |  17 +
 llvm/tools/ir-tracker/ir-tracker.py           | 117 ++++
 llvm/tools/ir-tracker/irtrackdb.py            | 564 ++++++++++++++++++
 9 files changed, 1016 insertions(+)
 create mode 100644 llvm/docs/IRTracker.rst
 create mode 100644 llvm/test/tools/llvm-ir-tracker/build-json.test
 create mode 100644 llvm/test/tools/llvm-ir-tracker/help.test
 create mode 100644 llvm/test/tools/llvm-ir-tracker/show.ll
 create mode 100644 llvm/tools/ir-tracker/CMakeLists.txt
 create mode 100644 llvm/tools/ir-tracker/ir-tracker.py
 create mode 100644 llvm/tools/ir-tracker/irtrackdb.py

diff --git a/llvm/docs/IRTracker.rst b/llvm/docs/IRTracker.rst
new file mode 100644
index 0000000000000..9325611e0657f
--- /dev/null
+++ b/llvm/docs/IRTracker.rst
@@ -0,0 +1,204 @@
+IR Tracker (IR JSONL + SQLite query DB)
+=======================================
+
+.. contents::
+   :local:
+
+Overview
+========
+
+The **IR tracker** records how LLVM IR evolves through the new pass manager into a
+compact JSON Lines file. A small Python tool can then post-process that JSON
+output into a
+SQLite database for indexed queries. Each instruction row is tied to a debug
+location (``file``, ``line``, ``column`` from ``DILocation``), so you can ask
+which passes touched code that originated at a given source line.
+
+Recording is enabled with the hidden LLVM option
+``-ir-tracker-json-output=/absolute/path.jsonl``. The hooks live in
+``StandardInstrumentations`` and therefore apply to any tool that runs the new
+pass manager with that instrumentation (``opt``, ``clang``, etc.).
+
+Only instructions that carry a **non-zero** debug location are indexed.
+
+Recording with ``opt``
+======================
+
+.. code-block:: bash
+
+  opt -disable-output -passes='default<O2>' \
+    -ir-tracker-json-output=/tmp/pipeline.jsonl input.ll
+
+Use an absolute output path. The input IR must already contain suitable
+``!dbg`` attachments (for example, compile the source with ``-g`` and use
+``clang -emit-llvm -S`` to produce ``input.ll``).
+
+Recording with ``clang``
+========================
+
+Forward the option through Clang with ``-mllvm`` so the middle-end sees the same
+flag as ``opt``:
+
+.. code-block:: bash
+
+  clang -O1 -emit-llvm -S -g sum.c -o sum.ll \
+    -mllvm -ir-tracker-json-output=/tmp/pipeline.jsonl
+
+Here ``-g`` ensures debug locations exist on instructions; ``-O1`` (or another
+``-O`` level) selects the usual optimization pipeline that ``opt`` would run for
+that tier.
+
+SQLite build step
+=================
+
+The Python driver can convert the JSONL output into a SQLite database:
+
+.. code-block:: bash
+
+  python3 llvm/tools/ir-tracker/ir-tracker.py build \
+    --input /tmp/pipeline.jsonl --db /tmp/pipeline.db
+
+The resulting database uses ``schema_version = 1`` in ``ir_tracker_meta``. The
+main tables are:
+
+* ``ir_tracker_meta`` — key/value metadata (including ``schema_version``)
+* ``ir_tracker_files`` — deduplicated paths from ``DIFile`` (often a basename
+  such as ``sum.c``)
+* ``ir_tracker_passes`` — one row per snapshot: ``seq``, ``phase`` (``initial``
+  or ``after``), ``pass_class``, ``ir_unit``
+* ``ir_tracker_instructions`` — instruction text and opcode per pass, keyed by
+  ``file_id``, ``line``, ``col``
+
+Query tool
+==========
+
+The Python driver lives at ``llvm/tools/ir-tracker/ir-tracker.py`` (installed
+under ``<prefix>/share/ir-tracker/`` when the ``ir-tracker`` install component
+is enabled). It can build the SQLite DB from tracker JSONL output and then query
+that DB. Subcommands:
+
+* ``build`` — convert tracker JSONL output into a SQLite database
+* ``passes`` — list recorded passes in ``seq`` order
+* ``trace`` — summarize the first and last pass that still have instructions
+  matching a source location
+* ``show`` — print the instructions matching ``--file`` / ``--line`` (and
+  optional ``--col`` / ``--opcode``) across passes; by default only passes where
+  the printed IR **changed** are shown; use ``--all-passes`` for every pass, or
+  ``--seq N`` for one pass
+* ``sql`` — run a single read-only SQL statement
+
+The ``--file`` argument is matched against the path stored in
+``ir_tracker_files`` (substring match, case-insensitive). Clang usually records
+the ``DIFile`` basename, so prefer ``--file sum.c`` rather than a full host path.
+
+Example: following one source line through ``clang -O1``
+========================================================
+
+Source file ``sum.c``:
+
+.. code-block:: c
+
+  /* Example: trivial fold (x + 0) -> x */
+  int bump(int x) {
+    return x + 0;
+  }
+
+Recording (same command as in *Recording with ``clang``*):
+
+.. code-block:: bash
+
+  clang -O1 -emit-llvm -S -g sum.c -o sum.ll \
+    -mllvm -ir-tracker-json-output=/tmp/pipeline.jsonl
+
+Then build the query database:
+
+.. code-block:: bash
+
+  python3 llvm/tools/ir-tracker/ir-tracker.py build \
+    --input /tmp/pipeline.jsonl --db /tmp/pipeline.db
+
+The following excerpts come from a real ``ir-tracker`` run against the database
+produced that way. **Pass names and sequence numbers depend on your Clang/LLVM
+version, target, and optimization level**; treat pass sequence numbers as
+illustrative, not a stable ABI.
+
+List passes (truncated):
+
+.. code-block:: text
+
+      0  id=1       initial  '<initial>'  on '[module]'
+      1  id=2       after  'memprof-remove-attributes'  on '[module]'
+      2  id=3       after  'annotation2metadata'  on '[module]'
+      …
+     10  id=11      after  'sroa'  on 'bump'
+     11  id=12      after  'early-cse'  on 'bump'
+     …
+
+Trace line ``3`` (the ``return x + 0;`` line in ``sum.c``):
+
+.. code-block:: bash
+
+  python3 llvm/tools/ir-tracker/ir-tracker.py trace \
+    --db /tmp/pipeline.db --file sum.c --line 3
+
+.. code-block:: text
+
+  Matches at final pass (seq=94): 1 instruction(s)
+  First pass with any matching instruction: seq=0 <initial> on [module] (3 row(s))
+
+``show`` without ``--all-passes`` prints only passes where the matched IR text
+changed: here the load/add/return cluster simplifies until ``early-cse`` folds
+``x + 0`` to ``x``:
+
+.. code-block:: bash
+
+  python3 llvm/tools/ir-tracker/ir-tracker.py show \
+    --db /tmp/pipeline.db --file sum.c --line 3
+
+.. code-block:: text
+
+  seq=0 '<initial>' on '[module]'
+    function bump, block entry:
+        %0 = load i32, ptr %x.addr, align 4
+        %add = add nsw i32 %0, 0
+        ret i32 %add
+  seq=10 'sroa' on 'bump'
+    function bump, block entry:
+        %add = add nsw i32 %x, 0
+        ret i32 %add
+  seq=11 'early-cse' on 'bump'
+    function bump, block entry:
+        ret i32 %x
+
+The initial snapshot for the same line (``--seq 0``) recovers the unoptimized
+cluster before any pass runs:
+
+.. code-block:: bash
+
+  python3 llvm/tools/ir-tracker/ir-tracker.py show \
+    --db /tmp/pipeline.db --file sum.c --line 3 --seq 0
+
+.. code-block:: text
+
+  seq=0 '<initial>' on '[module]'
+    function bump, block entry:
+        %0 = load i32, ptr %x.addr, align 4
+        %add = add nsw i32 %0, 0
+        ret i32 %add
+
+Tests
+=====
+
+* Recorder: ``llvm/test/Other/ir-tracker-db.ll``
+* Query tool: ``llvm/test/tools/llvm-ir-tracker/``
+
+Limitations
+===========
+
+* **IR only** — there is no MIR, object, or assembly capture in this schema.
+* **Debug info required** — instructions without a non-zero ``!dbg`` location are
+  not recorded. There is no built-in mode yet to fabricate locations for plain
+  ``.ll`` or ``.bc`` without debug metadata.
+* **Locations are keys, not proofs** — optimizations can merge, clone, or drop
+  instructions; the database lists what survived each pass with a given
+  location, not a formal def-use proof.
diff --git a/llvm/docs/UserGuides.rst b/llvm/docs/UserGuides.rst
index f33ddcabc8d89..5294e0d4ba0b1 100644
--- a/llvm/docs/UserGuides.rst
+++ b/llvm/docs/UserGuides.rst
@@ -49,6 +49,7 @@ intermediate LLVM representation.
    InstCombineContributorGuide
    InstrProfileFormat
    InstrRefDebugInfo
+   IRTracker
    KeyInstructionsDebugInfo
    LFI
    LinkTimeOptimization
diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py
index af704d67b91f2..3c8aa494aa381 100644
--- a/llvm/test/lit.cfg.py
+++ b/llvm/test/lit.cfg.py
@@ -214,6 +214,11 @@ def get_asan_rtlib():
     config.llvm_src_root,
 )
 
+ir_tracker_cmd = "%s %s/tools/ir-tracker/ir-tracker.py" % (
+    sys.executable,
+    config.llvm_src_root,
+)
+
 llvm_original_di_preservation_cmd = os.path.join(
     config.llvm_src_root, "utils", "llvm-original-di-preservation.py"
 )
@@ -239,6 +244,7 @@ def get_asan_rtlib():
     ToolSubst("%ocamlc", ocamlc_command, unresolved="ignore"),
     ToolSubst("%ocamlopt", ocamlopt_command, unresolved="ignore"),
     ToolSubst("%opt-viewer", opt_viewer_cmd),
+    ToolSubst("%ir-tracker", ir_tracker_cmd),
     ToolSubst("%llvm-objcopy", FindTool("llvm-objcopy")),
     ToolSubst("%llvm-strip", FindTool("llvm-strip")),
     ToolSubst("%llvm-install-name-tool", FindTool("llvm-install-name-tool")),
diff --git a/llvm/test/tools/llvm-ir-tracker/build-json.test b/llvm/test/tools/llvm-ir-tracker/build-json.test
new file mode 100644
index 0000000000000..f8da484ac57e6
--- /dev/null
+++ b/llvm/test/tools/llvm-ir-tracker/build-json.test
@@ -0,0 +1,42 @@
+RUN: rm -f %t.jsonl %t.db
+RUN: %python - <<'PY' > %t.jsonl
+import json
+records = [
+    {"kind": "pass", "seq": 0, "phase": "initial", "pass": "<initial>", "ir_unit": "[module]"},
+    {
+        "kind": "inst",
+        "file": "/tmp/json.c",
+        "line": 8,
+        "col": 3,
+        "function": "f",
+        "block": "entry",
+        "inst_seq": 0,
+        "opcode": "add",
+        "text": "%a = add i32 %x, 1, !dbg !7",
+    },
+    {"kind": "pass", "seq": 1, "phase": "after", "pass": "no-op-module", "ir_unit": "[module]"},
+    {
+        "kind": "inst",
+        "file": "/tmp/json.c",
+        "line": 8,
+        "col": 3,
+        "function": "f",
+        "block": "entry",
+        "inst_seq": 0,
+        "opcode": "add",
+        "text": "%a = add i32 %x, 1, !dbg !7",
+    },
+]
+for rec in records:
+    print(json.dumps(rec))
+PY
+RUN: %ir-tracker build --input %t.jsonl --db %t.db | FileCheck %s --check-prefix=BUILD
+RUN: %ir-tracker passes --db %t.db | FileCheck %s --check-prefix=PASSES
+RUN: %ir-tracker trace --db %t.db --file json.c --line 8 | FileCheck %s --check-prefix=TRACE
+
+BUILD: built {{.*}} pass snapshots, 2 instruction rows
+PASSES: 0  id={{[0-9]+}}  initial  '<initial>'  on '[module]'
+PASSES: 1  id={{[0-9]+}}  after  'no-op-module'  on '[module]'
+PASSES: total passes recorded: 2
+TRACE: Matches at final pass (seq=1): 1 instruction(s)
+TRACE: First pass with any matching instruction: seq=0 <initial> on [module] (1 row(s))
diff --git a/llvm/test/tools/llvm-ir-tracker/help.test b/llvm/test/tools/llvm-ir-tracker/help.test
new file mode 100644
index 0000000000000..4e1ae33bbe74b
--- /dev/null
+++ b/llvm/test/tools/llvm-ir-tracker/help.test
@@ -0,0 +1,16 @@
+RUN: %ir-tracker build --help | FileCheck %s --check-prefix=BUILD
+RUN: %ir-tracker --help | FileCheck %s --check-prefix=MAIN
+RUN: %ir-tracker passes --help | FileCheck %s --check-prefix=PASSES
+RUN: %ir-tracker trace --help | FileCheck %s --check-prefix=TRACE
+RUN: %ir-tracker show --help | FileCheck %s --check-prefix=SHOW
+RUN: %ir-tracker sql --help | FileCheck %s --check-prefix=SQL
+
+MAIN: Build and query IR-tracker SQLite databases
+MAIN: {build,passes,trace,show,sql}
+
+BUILD: usage: ir-tracker build
+BUILD: tracker JSON or text
+PASSES: usage: ir-tracker passes
+TRACE: usage: ir-tracker trace
+SHOW: usage: ir-tracker show
+SQL: usage: ir-tracker sql
diff --git a/llvm/test/tools/llvm-ir-tracker/show.ll b/llvm/test/tools/llvm-ir-tracker/show.ll
new file mode 100644
index 0000000000000..83d144002cbfb
--- /dev/null
+++ b/llvm/test/tools/llvm-ir-tracker/show.ll
@@ -0,0 +1,49 @@
+; RUN: opt -disable-output -passes=no-op-module -ir-tracker-json-output=%t.jsonl %s
+; RUN: %ir-tracker build --input %t.jsonl --db %t.db
+; RUN: %ir-tracker passes --db %t.db | FileCheck %s --check-prefix=PASSES
+; RUN: %ir-tracker trace --db %t.db --file show.c --line 8 | FileCheck %s --check-prefix=TRACE
+; RUN: %ir-tracker show --db %t.db --file show.c --line 8 --seq 0 | FileCheck %s --check-prefix=SEQ0
+; RUN: %ir-tracker show --db %t.db --file show.c --line 8 | FileCheck %s --check-prefix=CHANGED
+; RUN: %ir-tracker show --db %t.db --file show.c --line 8 --all-passes | FileCheck %s --check-prefix=ALL
+
+define i32 @f(i32 %x) !dbg !6 {
+entry:
+  %a = add i32 %x, 1, !dbg !8
+  ret i32 %a, !dbg !9
+}
+
+!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: "show.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)
+!8 = !DILocation(line: 8, column: 3, scope: !6)
+!9 = !DILocation(line: 9, column: 3, scope: !6)
+
+; PASSES: 0  id={{[0-9]+}}  initial  '<initial>'  on '[module]'
+; PASSES: 1  id={{[0-9]+}}  after  'no-op-module'  on '[module]'
+; PASSES: total passes recorded: 2
+
+; TRACE: Matches at final pass (seq=1): 1 instruction(s)
+; TRACE: First pass with any matching instruction: seq=0 <initial> on [module] (1 row(s))
+
+; SEQ0: seq=0 '<initial>' on '[module]'
+; SEQ0-NEXT:   function f, block entry:
+; SEQ0-NEXT:     %a = add i32 %x, 1, !dbg !{{[0-9]+}}
+
+; CHANGED: seq=0 '<initial>' on '[module]'
+; CHANGED-NEXT:   function f, block entry:
+; CHANGED-NEXT:     %a = add i32 %x, 1, !dbg !{{[0-9]+}}
+; CHANGED-NOT: seq=1
+
+; ALL: seq=0 '<initial>' on '[module]'
+; ALL-NEXT:   function f, block entry:
+; ALL-NEXT:     %a = add i32 %x, 1, !dbg !{{[0-9]+}}
+; ALL: seq=1 'no-op-module' on '[module]'
+; ALL-NEXT:   function f, block entry:
+; ALL-NEXT:     %a = add i32 %x, 1, !dbg !{{[0-9]+}}
diff --git a/llvm/tools/ir-tracker/CMakeLists.txt b/llvm/tools/ir-tracker/CMakeLists.txt
new file mode 100644
index 0000000000000..0ba220293b3e7
--- /dev/null
+++ b/llvm/tools/ir-tracker/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(files
+  "ir-tracker.py"
+  "irtrackdb.py")
+
+foreach(file ${files})
+  install(PROGRAMS ${file}
+    DESTINATION "${CMAKE_INSTALL_DATADIR}/ir-tracker"
+    COMPONENT ir-tracker)
+endforeach(file)
+
+add_custom_target(ir-tracker DEPENDS ${files})
+set_target_properties(ir-tracker PROPERTIES FOLDER "LLVM/Tools")
+if(NOT LLVM_ENABLE_IDE)
+  add_llvm_install_targets("install-ir-tracker"
+                           DEPENDS ir-tracker
+                           COMPONENT ir-tracker)
+endif()
diff --git a/llvm/tools/ir-tracker/ir-tracker.py b/llvm/tools/ir-tracker/ir-tracker.py
new file mode 100644
index 0000000000000..1d71dd6fd45a0
--- /dev/null
+++ b/llvm/tools/ir-tracker/ir-tracker.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+"""Build and query SQLite databases for llvm/tools/ir-tracker."""
+
+from __future__ import annotations
+
+import argparse
+import sys
+from typing import Optional, Sequence
+
+import irtrackdb
+
+
+def cmd_build(args: argparse.Namespace) -> int:
+    return irtrackdb.build_db(args.input, args.db)
+
+
+def cmd_passes(args: argparse.Namespace) -> int:
+    con = irtrackdb.open_db_readonly(args.db)
+    if not con:
+        return 1
+    try:
+        return irtrackdb.run_passes(con)
+    finally:
+        con.close()
+
+
+def cmd_trace(args: argparse.Namespace) -> int:
+    con = irtrackdb.open_db_readonly(args.db)
+    if not con:
+        return 1
+    try:
+        return irtrackdb.run_trace(
+            con, args.file, args.line, args.col, args.opcode or ""
+        )
+    finally:
+        con.close()
+
+
+def cmd_show(args: argparse.Namespace) -> int:
+    con = irtrackdb.open_db_readonly(args.db)
+    if not con:
+        return 1
+    try:
+        return irtrackdb.run_show(
+            con,
+            args.file,
+            args.line,
+            args.col,
+            args.opcode or "",
+            args.seq,
+            args.all_passes,
+        )
+    finally:
+        con.close()
+
+
+def cmd_sql(args: argparse.Namespace) -> int:
+    con = irtrackdb.open_db_readonly(args.db)
+    if not con:
+        return 1
+    try:
+        return irtrackdb.run_sql(con, args.query)
+    finally:
+        con.close()
+
+
+def main(argv: Optional[Sequence[str]] = None) -> int:
+    argv = list(sys.argv[1:] if argv is None else argv)
+    parser = argparse.ArgumentParser(
+        prog="ir-tracker",
+        description="Build and query IR-tracker SQLite databases",
+    )
+    sub = parser.add_subparsers(dest="cmd", required=True)
+
+    build = sub.add_parser(
+        "build", help="Build a SQLite DB from tracker JSON or text output"
+    )
+    build.add_argument("--input", required=True)
+    build.add_argument("--db", required=True)
+    build.set_defaults(func=cmd_build)
+
+    passes = sub.add_parser("passes", help="List recorded passes")
+    passes.add_argument("--db", required=True)
+    passes.set_defaults(func=cmd_passes)
+
+    trace = sub.add_parser("trace", help="Find first/final pass for a source line")
+    trace.add_argument("--db", required=True)
+    trace.add_argument("--file", required=True)
+    trace.add_argument("--line", required=True)
+    trace.add_argument("--col", type=int, default=None)
+    trace.add_argument("--opcode", default="")
+    trace.set_defaults(func=cmd_trace)
+
+    show = sub.add_parser("show", help="Show tracked instructions for a source line")
+    show.add_argument("--db", required=True)
+    show.add_argument("--file", required=True)
+    show.add_argument("--line", required=True)
+    show.add_argument("--col", type=int, default=None)
+    show.add_argument("--opcode", default="")
+    show.add_argument("--seq", type=int, default=-1)
+    show.add_argument("--all-passes", action="store_true")
+    show.set_defaults(func=cmd_show)
+
+    sql = sub.add_parser("sql", help="Run a read-only SQL query")
+    sql.add_argument("--db", required=True)
+    sql.add_argument("query")
+    sql.set_defaults(func=cmd_sql)
+
+    args = parser.parse_args(argv)
+    return int(args.func(args))
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())
diff --git a/llvm/tools/ir-tracker/irtrackdb.py b/llvm/tools/ir-tracker/irtrackdb.py
new file mode 100644
index 0000000000000..d1ab6b5717ee0
--- /dev/null
+++ b/llvm/tools/ir-tracker/irtrackdb.py
@@ -0,0 +1,564 @@
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+"""SQLite helpers for llvm/tools/ir-tracker."""
+
+from __future__ import annotations
+
+import os
+import json
+import re
+import sqlite3
+import sys
+from typing import Dict, List, NamedTuple, Optional, Sequence
+
+T_FILES = "ir_tracker_files"
+T_INSTR = "ir_tracker_instructions"
+T_META = "ir_tracker_meta"
+T_PASSES = "ir_tracker_passes"
+SCHEMA_VERSION = 1
+
+HEADER_RE = re.compile(
+    r"^seq=(?P<seq>[0-9]+)\tphase=(?P<phase>[^\t]+)\tpass=(?P<pass>[^\t]+)\tir_unit=(?P<ir_unit>[^\t]+)$"
+)
+
+
+def open_db_readonly(path: str) -> Optional[sqlite3.Connection]:
+    if not path:
+        print("ir-tracker: empty database path", file=sys.stderr)
+        return None
+    if not os.path.isfile(path):
+        print(f"ir-tracker: database not found: {path}", file=sys.stderr)
+        return None
+    con = sqlite3.connect(f"file:{path}?mode=ro", uri=True)
+    con.row_factory = sqlite3.Row
+    return con
+
+
+def open_db_write(path: str) -> Optional[sqlite3.Connection]:
+    if not path:
+        print("ir-tracker: empty database path", file=sys.stderr)
+        return None
+    parent = os.path.dirname(path)
+    if parent:
+        os.makedirs(parent, exist_ok=True)
+    if os.path.exists(path):
+        os.remove(path)
+    con = sqlite3.connect(path)
+    con.row_factory = sqlite3.Row
+    return con
+
+
+def init_schema(con: sqlite3.Connection) -> None:
+    con.executescript(
+        f"""
+        PRAGMA foreign_keys = ON;
+        CREATE TABLE {T_META} (
+          key TEXT PRIMARY KEY,
+          value TEXT NOT NULL
+        );
+        CREATE TABLE {T_FILES} (
+          id INTEGER PRIMARY KEY AUTOINCREMENT,
+          path TEXT NOT NULL UNIQUE
+        );
+        CREATE TABLE {T_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 {T_PASSES}(seq);
+        CREATE TABLE {T_INSTR} (
+          id INTEGER PRIMARY KEY AUTOINCREMENT,
+          pass_id INTEGER NOT NULL REFERENCES {T_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 {T_FILES}(id),
+          line INTEGER NOT NULL,
+          col INTEGER NOT NULL
+        );
+        CREATE INDEX ir_tracker_idx_instr_file_loc
+          ON {T_INSTR}(file_id, line, col);
+        CREATE INDEX ir_tracker_idx_instr_pass
+          ON {T_INSTR}(pass_id);
+        """
+    )
+    con.execute(
+        f"INSERT INTO {T_META}(key, value) VALUES('schema_version', ?)",
+        (str(SCHEMA_VERSION),),
+    )
+
+
+def _get_or_create_file_id(
+    con: sqlite3.Connection, cache: Dict[str, int], path: str
+) -> int:
+    cached = cache.get(path)
+    if cached is not None:
+        return cached
+    con.execute(f"INSERT OR IGNORE INTO {T_FILES}(path) VALUES(?)", (path,))
+    row = con.execute(f"SELECT id FROM {T_FILES} WHERE path = ?", (path,)).fetchone()
+    assert row is not None
+    file_id = int(row["id"])
+    cache[path] = file_id
+    return file_id
+
+
+def _insert_pass(
+    con: sqlite3.Connection, seq: int, phase: str, pass_name: str, ir_unit: str
+) -> int:
+    return int(
+        con.execute(
+            f"INSERT INTO {T_PASSES}(seq, phase, pass_class, ir_unit) "
+            f"VALUES(?, ?, ?, ?)",
+            (seq, phase, pass_name, ir_unit),
+        ).lastrowid
+    )
+
+
+def _insert_inst(
+    con: sqlite3.Connection,
+    file_cache: Dict[str, int],
+    current_pass_id: int,
+    file_path: str,
+    line_s: int,
+    col_s: int,
+    func: str,
+    bb: str,
+    inst_seq_s: int,
+    opcode: str,
+    inst_text: str,
+) -> None:
+    file_id = _get_or_create_file_id(con, file_cache, file_path)
+    con.execute(
+        f"INSERT INTO {T_INSTR}("
+        "pass_id, function, basicblock, inst_seq, opcode, inst_text, "
+        "file_id, line, col) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
+        (
+            current_pass_id,
+            func,
+            bb,
+            inst_seq_s,
+            opcode,
+            inst_text,
+            file_id,
+            line_s,
+            col_s,
+        ),
+    )
+
+
+def _detect_input_format(path: str) -> str:
+    with open(path, "r", encoding="utf-8") as f:
+        for raw in f:
+            line = raw.strip()
+            if not line:
+                continue
+            return "jsonl" if line.startswith("{") else "text"
+    return "text"
+
+
+def _build_db_from_text(
+    con: sqlite3.Connection, input_path: str
+) -> tuple[int, int]:
+    file_cache: Dict[str, int] = {}
+    current_pass_id: Optional[int] = None
+    n_passes = 0
+    n_rows = 0
+
+    with open(input_path, "r", encoding="utf-8") as f:
+        for line_no, raw in enumerate(f, 1):
+            line = raw.rstrip("\n")
+            if not line:
+                continue
+
+            m = HEADER_RE.match(line)
+            if m:
+                current_pass_id = _insert_pass(
+                    con,
+                    int(m.group("seq")),
+                    m.group("phase"),
+                    m.group("pass"),
+                    m.group("ir_unit"),
+                )
+                n_passes += 1
+                continue
+
+            if current_pass_id is None:
+                print(
+                    f"ir-tracker: data row before first pass header at line {line_no}",
+                    file=sys.stderr,
+                )
+                raise ValueError("data row before first pass header")
+
+            parts = line.split("\t", 7)
+            if len(parts) != 8:
+                print(
+                    f"ir-tracker: malformed tracker row at line {line_no}",
+                    file=sys.stderr,
+                )
+                raise ValueError("malformed tracker row")
+
+            file_path, line_s, col_s, func, bb, inst_seq_s, opcode, inst_text = parts
+            _insert_inst(
+                con,
+                file_cache,
+                current_pass_id,
+                file_path,
+                int(line_s),
+                int(col_s),
+                func,
+                bb,
+                int(inst_seq_s),
+                opcode,
+                inst_text,
+            )
+            n_rows += 1
+    return n_passes, n_rows
+
+
+def _build_db_from_jsonl(
+    con: sqlite3.Connection, input_path: str
+) -> tuple[int, int]:
+    file_cache: Dict[str, int] = {}
+    current_pass_id: Optional[int] = None
+    n_passes = 0
+    n_rows = 0
+
+    with open(input_path, "r", encoding="utf-8") as f:
+        for line_no, raw in enumerate(f, 1):
+            line = raw.strip()
+            if not line:
+                continue
+
+            try:
+                obj = json.loads(line)
+            except json.JSONDecodeError as err:
+                print(
+                    f"ir-tracker: malformed JSON at line {line_no}: {err}",
+                    file=sys.stderr,
+                )
+                raise ValueError("malformed json") from err
+
+            kind = obj.get("kind")
+            if kind == "pass":
+                try:
+                    current_pass_id = _insert_pass(
+                        con,
+                        int(obj["seq"]),
+                        str(obj["phase"]),
+                        str(obj["pass"]),
+                        str(obj["ir_unit"]),
+                    )
+                except KeyError as err:
+                    print(
+                        f"ir-tracker: missing pass field {err} at line {line_no}",
+                        file=sys.stderr,
+                    )
+                    raise ValueError("missing pass field") from err
+                n_passes += 1
+                continue
+
+            if kind == "inst":
+                if current_pass_id is None:
+                    print(
+                        f"ir-tracker: inst row before first pass record at line {line_no}",
+                        file=sys.stderr,
+                    )
+                    raise ValueError("inst row before first pass record")
+                try:
+                    _insert_inst(
+                        con,
+                        file_cache,
+                        current_pass_id,
+                        str(obj["file"]),
+                        int(obj["line"]),
+                        int(obj["col"]),
+                        str(obj["function"]),
+                        str(obj["block"]),
+                        int(obj["inst_seq"]),
+                        str(obj["opcode"]),
+                        str(obj["text"]),
+                    )
+                except KeyError as err:
+                    print(
+                        f"ir-tracker: missing inst field {err} at line {line_no}",
+                        file=sys.stderr,
+                    )
+                    raise ValueError("missing inst field") from err
+                n_rows += 1
+                continue
+
+            print(
+                f"ir-tracker: unknown record kind at line {line_no}: {kind!r}",
+                file=sys.stderr,
+            )
+            raise ValueError("unknown record kind")
+
+    return n_passes, n_rows
+
+
+def build_db(input_path: str, db_path: str) -> int:
+    if not input_path:
+        print("ir-tracker: empty input path", file=sys.stderr)
+        return 1
+    if not os.path.isfile(input_path):
+        print(f"ir-tracker: input not found: {input_path}", file=sys.stderr)
+        return 1
+
+    con = open_db_write(db_path)
+    if not con:
+        return 1
+
+    try:
+        init_schema(con)
+        con.commit()
+        con.execute("BEGIN IMMEDIATE")
+        input_format = _detect_input_format(input_path)
+        if input_format == "jsonl":
+            n_passes, n_rows = _build_db_from_jsonl(con, input_path)
+        else:
+            n_passes, n_rows = _build_db_from_text(con, input_path)
+        con.commit()
+    except (sqlite3.Error, ValueError) as err:
+        print(f"ir-tracker: sqlite error while building db: {err}", file=sys.stderr)
+        return 1
+    finally:
+        con.close()
+
+    print(
+        f"built {db_path} from {input_path}: {n_passes} pass snapshots, {n_rows} instruction rows"
+    )
+    return 0
+
+
+def get_schema_version(con: sqlite3.Connection) -> int:
+    row = con.execute(
+        f"SELECT value FROM {T_META} WHERE key = 'schema_version'"
+    ).fetchone()
+    if not row or row["value"] is None:
+        return -1
+    try:
+        return int(row["value"])
+    except ValueError:
+        return -1
+
+
+def resolve_file_ids(con: sqlite3.Connection, file_pat: str) -> List[int]:
+    needle = file_pat.lower()
+    ids: List[int] = []
+    for row in con.execute(f"SELECT id, path FROM {T_FILES}"):
+        path = (row["path"] or "").lower()
+        if needle in path or path.endswith(needle):
+            ids.append(int(row["id"]))
+    return ids
+
+
+def run_passes(con: sqlite3.Connection) -> int:
+    rows = con.execute(
+        f"SELECT id, seq, phase, pass_class, ir_unit FROM {T_PASSES} ORDER BY seq"
+    ).fetchall()
+    for row in rows:
+        print(
+            f"{int(row['seq']):5d}  id={int(row['id']):<6}  "
+            f"{row['phase']}  '{row['pass_class']}'  on '{row['ir_unit']}'"
+        )
+    print(f"total passes recorded: {len(rows)}")
+    return 0
+
+
+def _parse_line(line_s: str) -> Optional[int]:
+    try:
+        line = int(line_s, 0)
+    except ValueError:
+        return None
+    return line if line > 0 else None
+
+
+def _filter_clause(
+    file_ids: Sequence[int], line: int, trace_col: Optional[int], trace_opcode: str
+) -> tuple[str, List[object]]:
+    in_clause = ",".join("?" * len(file_ids))
+    sql = f"i.file_id IN ({in_clause}) AND i.line = ?"
+    params: List[object] = [*file_ids, line]
+    if trace_col is not None:
+        sql += " AND i.col = ?"
+        params.append(trace_col)
+    if trace_opcode:
+        sql += " AND i.opcode = ?"
+        params.append(trace_opcode)
+    return sql, params
+
+
+def run_trace(
+    con: sqlite3.Connection,
+    file_pat: str,
+    line_s: str,
+    trace_col: Optional[int],
+    trace_opcode: str,
+) -> int:
+    if get_schema_version(con) < 1:
+        print("ir-tracker: unsupported schema version", file=sys.stderr)
+        return 1
+
+    file_ids = resolve_file_ids(con, file_pat)
+    if not file_ids:
+        print("ir-tracker: no matching file rows", file=sys.stderr)
+        return 1
+
+    line = _parse_line(line_s)
+    if line is None:
+        print("ir-tracker: invalid --line", file=sys.stderr)
+        return 1
+
+    where_sql, params = _filter_clause(file_ids, line, trace_col, trace_opcode)
+
+    row = con.execute(
+        f"SELECT MAX(p.seq) AS max_seq "
+        f"FROM {T_INSTR} i JOIN {T_PASSES} p ON i.pass_id = p.id "
+        f"WHERE {where_sql}",
+        params,
+    ).fetchone()
+    if not row or row["max_seq"] is None:
+        print("ir-tracker: no matching instructions found", file=sys.stderr)
+        return 1
+
+    max_seq = int(row["max_seq"])
+    count_row = con.execute(
+        f"SELECT COUNT(*) AS c "
+        f"FROM {T_INSTR} i JOIN {T_PASSES} p ON i.pass_id = p.id "
+        f"WHERE p.seq = ? AND {where_sql}",
+        [max_seq, *params],
+    ).fetchone()
+    print(
+        f"Matches at final pass (seq={max_seq}): {int(count_row['c'])} "
+        f"instruction(s)"
+    )
+
+    first_row = con.execute(
+        f"SELECT p.seq, p.pass_class, p.ir_unit, COUNT(*) AS c "
+        f"FROM {T_INSTR} i JOIN {T_PASSES} p ON i.pass_id = p.id "
+        f"WHERE {where_sql} GROUP BY p.id ORDER BY p.seq ASC LIMIT 1",
+        params,
+    ).fetchone()
+    if first_row:
+        print(
+            f"First pass with any matching instruction: seq={int(first_row['seq'])} "
+            f"{first_row['pass_class']} on {first_row['ir_unit']} "
+            f"({int(first_row['c'])} row(s))"
+        )
+    return 0
+
+
+class ShowInstRow(NamedTuple):
+    seq: int
+    pass_class: str
+    ir_unit: str
+    function: str
+    basicblock: str
+    inst_text: str
+
+
+def _print_group(rows: Sequence[ShowInstRow]) -> None:
+    if not rows:
+        return
+    head = rows[0]
+    print(f"seq={head.seq} '{head.pass_class}' on '{head.ir_unit}'")
+    current_func = ""
+    current_bb = ""
+    for row in rows:
+        if row.function != current_func or row.basicblock != current_bb:
+            print(f"  function {row.function}, block {row.basicblock}:")
+            current_func = row.function
+            current_bb = row.basicblock
+        print(f"    {row.inst_text}")
+
+
+def run_show(
+    con: sqlite3.Connection,
+    file_pat: str,
+    line_s: str,
+    trace_col: Optional[int],
+    trace_opcode: str,
+    seq: int,
+    show_all_passes: bool,
+) -> int:
+    if get_schema_version(con) < 1:
+        print("ir-tracker: unsupported schema version", file=sys.stderr)
+        return 1
+    if show_all_passes and seq >= 0:
+        print(
+            "ir-tracker: --all-passes and --seq are mutually exclusive", file=sys.stderr
+        )
+        return 1
+
+    file_ids = resolve_file_ids(con, file_pat)
+    if not file_ids:
+        print("ir-tracker: no matching file rows", file=sys.stderr)
+        return 1
+
+    line = _parse_line(line_s)
+    if line is None:
+        print("ir-tracker: invalid --line", file=sys.stderr)
+        return 1
+
+    where_sql, params = _filter_clause(file_ids, line, trace_col, trace_opcode)
+    seq_sql = ""
+    if seq >= 0:
+        seq_sql = " AND p.seq = ?"
+        params = [*params, seq]
+
+    query = (
+        f"SELECT p.seq, p.pass_class, p.ir_unit, i.function, i.basicblock, "
+        f"i.inst_seq, i.inst_text "
+        f"FROM {T_INSTR} i JOIN {T_PASSES} p ON i.pass_id = p.id "
+        f"WHERE {where_sql}{seq_sql} "
+        f"ORDER BY p.seq ASC, i.function ASC, i.basicblock ASC, i.inst_seq ASC"
+    )
+    rows = [
+        ShowInstRow(
+            int(row["seq"]),
+            row["pass_class"] or "",
+            row["ir_unit"] or "",
+            row["function"] or "",
+            row["basicblock"] or "",
+            row["inst_text"] or "",
+        )
+        for row in con.execute(query, params)
+    ]
+    if not rows:
+        print("ir-tracker: no matching instructions found", file=sys.stderr)
+        return 1
+
+    by_seq: Dict[int, List[ShowInstRow]] = {}
+    for row in rows:
+        by_seq.setdefault(row.seq, []).append(row)
+
+    last_fp = None
+    for current_seq in sorted(by_seq):
+        group = by_seq[current_seq]
+        fp = "\n".join(row.inst_text for row in group)
+        if seq < 0 and not show_all_passes and fp == last_fp:
+            continue
+        _print_group(group)
+        last_fp = fp
+    return 0
+
+
+def run_sql(con: sqlite3.Connection, sql: str) -> int:
+    try:
+        cur = con.execute(sql)
+    except sqlite3.Error as err:
+        print(f"ir-tracker: prepare(sql): {err}", file=sys.stderr)
+        return 1
+
+    while True:
+        row = cur.fetchone()
+        if row is None:
+            break
+        print("(" + ", ".join("None" if v is None else str(v) for v in row) + ")")
+    return 0



More information about the llvm-commits mailing list