[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