Skip to content

Commit 9161e6a

Browse files
authored
[SandboxIR] Add debug checker to compare IR before/after a revert (#115968)
This will help us catch mistakes in change tracking. It's only enabled when EXPENSIVE_CHECKS are enabled.
1 parent de2e270 commit 9161e6a

File tree

5 files changed

+204
-10
lines changed

5 files changed

+204
-10
lines changed

llvm/include/llvm/SandboxIR/Context.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@ class Context {
4444

4545
protected:
4646
LLVMContext &LLVMCtx;
47-
friend class Type; // For LLVMCtx.
48-
friend class PointerType; // For LLVMCtx.
49-
friend class IntegerType; // For LLVMCtx.
50-
friend class StructType; // For LLVMCtx.
51-
friend class Region; // For LLVMCtx.
47+
friend class Type; // For LLVMCtx.
48+
friend class PointerType; // For LLVMCtx.
49+
friend class IntegerType; // For LLVMCtx.
50+
friend class StructType; // For LLVMCtx.
51+
friend class Region; // For LLVMCtx.
52+
friend class IRSnapshotChecker; // To snapshot LLVMModuleToModuleMap.
5253

5354
Tracker IRTracker;
5455

llvm/include/llvm/SandboxIR/Instruction.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "llvm/IR/IRBuilder.h"
1313
#include "llvm/IR/Instructions.h"
14+
#include "llvm/IR/Module.h"
1415
#include "llvm/IR/PatternMatch.h"
1516
#include "llvm/SandboxIR/BasicBlock.h"
1617
#include "llvm/SandboxIR/Constant.h"

llvm/include/llvm/SandboxIR/Tracker.h

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,12 @@
4242

4343
#include "llvm/ADT/PointerUnion.h"
4444
#include "llvm/ADT/SmallVector.h"
45+
#include "llvm/ADT/StableHashing.h"
4546
#include "llvm/IR/IRBuilder.h"
4647
#include "llvm/IR/Instruction.h"
47-
#include "llvm/IR/Module.h"
4848
#include "llvm/SandboxIR/Use.h"
4949
#include "llvm/Support/Debug.h"
5050
#include <memory>
51-
#include <regex>
5251

5352
namespace llvm::sandboxir {
5453

@@ -64,9 +63,56 @@ class SwitchInst;
6463
class ConstantInt;
6564
class ShuffleVectorInst;
6665
class CmpInst;
67-
class Module;
6866
class GlobalVariable;
6967

68+
#ifndef NDEBUG
69+
70+
/// A class that saves hashes and textual IR snapshots of functions in a
71+
/// SandboxIR Context, and does hash comparison when `expectNoDiff` is called.
72+
/// If hashes differ, it prints textual IR for both old and new versions to
73+
/// aid debugging.
74+
///
75+
/// This is used as an additional debug check when reverting changes to
76+
/// SandboxIR, to verify the reverted state matches the initial state.
77+
class IRSnapshotChecker {
78+
Context &Ctx;
79+
80+
// A snapshot of textual IR for a function, with a hash for quick comparison.
81+
struct FunctionSnapshot {
82+
llvm::stable_hash Hash;
83+
std::string TextualIR;
84+
};
85+
86+
// A snapshot for each llvm::Function found in every module in the SandboxIR
87+
// Context. In practice there will always be one module, but sandbox IR
88+
// save/restore ops work at the Context level, so we must take the full state
89+
// into account.
90+
using ContextSnapshot = DenseMap<const llvm::Function *, FunctionSnapshot>;
91+
92+
ContextSnapshot OrigContextSnapshot;
93+
94+
// Dumps to a string the textual IR for a single Function.
95+
std::string dumpIR(const llvm::Function &F) const;
96+
97+
// Returns a snapshot of all the modules in the sandbox IR context.
98+
ContextSnapshot takeSnapshot() const;
99+
100+
// Compares two snapshots and returns true if they differ.
101+
bool diff(const ContextSnapshot &Orig, const ContextSnapshot &Curr) const;
102+
103+
public:
104+
IRSnapshotChecker(Context &Ctx) : Ctx(Ctx) {}
105+
106+
/// Saves a snapshot of the current state. If there was any previous snapshot,
107+
/// it will be replaced with the new one.
108+
void save();
109+
110+
/// Checks current state against saved state, crashes if different.
111+
void expectNoDiff();
112+
};
113+
114+
#endif // NDEBUG
115+
70116
/// The base class for IR Change classes.
71117
class IRChangeBase {
72118
protected:
@@ -405,14 +451,26 @@ class Tracker {
405451
TrackerState State = TrackerState::Disabled;
406452
Context &Ctx;
407453

454+
#ifndef NDEBUG
455+
IRSnapshotChecker SnapshotChecker;
456+
#endif
457+
408458
public:
409459
#ifndef NDEBUG
410460
/// Helps catch bugs where we are creating new change objects while in the
411461
/// middle of creating other change objects.
412462
bool InMiddleOfCreatingChange = false;
413463
#endif // NDEBUG
414464

415-
explicit Tracker(Context &Ctx) : Ctx(Ctx) {}
465+
explicit Tracker(Context &Ctx)
466+
: Ctx(Ctx)
467+
#ifndef NDEBUG
468+
,
469+
SnapshotChecker(Ctx)
470+
#endif
471+
{
472+
}
473+
416474
~Tracker();
417475
Context &getContext() const { return Ctx; }
418476
/// Record \p Change and take ownership. This is the main function used to

llvm/lib/SandboxIR/Tracker.cpp

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,75 @@
1010
#include "llvm/ADT/STLExtras.h"
1111
#include "llvm/IR/BasicBlock.h"
1212
#include "llvm/IR/Instruction.h"
13+
#include "llvm/IR/Module.h"
14+
#include "llvm/IR/StructuralHash.h"
1315
#include "llvm/SandboxIR/Instruction.h"
1416
#include <sstream>
1517

1618
using namespace llvm::sandboxir;
1719

1820
#ifndef NDEBUG
21+
22+
std::string IRSnapshotChecker::dumpIR(const llvm::Function &F) const {
23+
std::string Result;
24+
raw_string_ostream SS(Result);
25+
F.print(SS, /*AssemblyAnnotationWriter=*/nullptr);
26+
return Result;
27+
}
28+
29+
IRSnapshotChecker::ContextSnapshot IRSnapshotChecker::takeSnapshot() const {
30+
ContextSnapshot Result;
31+
for (const auto &Entry : Ctx.LLVMModuleToModuleMap)
32+
for (const auto &F : *Entry.first) {
33+
FunctionSnapshot Snapshot;
34+
Snapshot.Hash = StructuralHash(F, /*DetailedHash=*/true);
35+
Snapshot.TextualIR = dumpIR(F);
36+
Result[&F] = Snapshot;
37+
}
38+
return Result;
39+
}
40+
41+
bool IRSnapshotChecker::diff(const ContextSnapshot &Orig,
42+
const ContextSnapshot &Curr) const {
43+
bool DifferenceFound = false;
44+
for (const auto &[F, OrigFS] : Orig) {
45+
auto CurrFSIt = Curr.find(F);
46+
if (CurrFSIt == Curr.end()) {
47+
DifferenceFound = true;
48+
dbgs() << "Function " << F->getName() << " not found in current IR.\n";
49+
dbgs() << OrigFS.TextualIR << "\n";
50+
continue;
51+
}
52+
const FunctionSnapshot &CurrFS = CurrFSIt->second;
53+
if (OrigFS.Hash != CurrFS.Hash) {
54+
DifferenceFound = true;
55+
dbgs() << "Found IR difference in Function " << F->getName() << "\n";
56+
dbgs() << "Original:\n" << OrigFS.TextualIR << "\n";
57+
dbgs() << "Current:\n" << CurrFS.TextualIR << "\n";
58+
}
59+
}
60+
// Check that Curr doesn't contain any new functions.
61+
for (const auto &[F, CurrFS] : Curr) {
62+
if (!Orig.contains(F)) {
63+
DifferenceFound = true;
64+
dbgs() << "Function " << F->getName()
65+
<< " found in current IR but not in original snapshot.\n";
66+
dbgs() << CurrFS.TextualIR << "\n";
67+
}
68+
}
69+
return DifferenceFound;
70+
}
71+
72+
void IRSnapshotChecker::save() { OrigContextSnapshot = takeSnapshot(); }
73+
74+
void IRSnapshotChecker::expectNoDiff() {
75+
ContextSnapshot CurrContextSnapshot = takeSnapshot();
76+
if (diff(OrigContextSnapshot, CurrContextSnapshot)) {
77+
llvm_unreachable(
78+
"Original and current IR differ! Probably a checkpointing bug.");
79+
}
80+
}
81+
1982
void UseSet::dump() const {
2083
dump(dbgs());
2184
dbgs() << "\n";
@@ -275,14 +338,22 @@ void CmpSwapOperands::dump() const {
275338
}
276339
#endif
277340

278-
void Tracker::save() { State = TrackerState::Record; }
341+
void Tracker::save() {
342+
State = TrackerState::Record;
343+
#if !defined(NDEBUG) && defined(EXPENSIVE_CHECKS)
344+
SnapshotChecker.save();
345+
#endif
346+
}
279347

280348
void Tracker::revert() {
281349
assert(State == TrackerState::Record && "Forgot to save()!");
282350
State = TrackerState::Disabled;
283351
for (auto &Change : reverse(Changes))
284352
Change->revert(*this);
285353
Changes.clear();
354+
#if !defined(NDEBUG) && defined(EXPENSIVE_CHECKS)
355+
SnapshotChecker.expectNoDiff();
356+
#endif
286357
}
287358

288359
void Tracker::accept() {

llvm/unittests/SandboxIR/TrackerTest.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,3 +1844,66 @@ define void @foo(i32 %arg, float %farg) {
18441844
Ctx.revert();
18451845
EXPECT_FALSE(FAdd->getFastMathFlags() != OrigFMF);
18461846
}
1847+
1848+
TEST_F(TrackerTest, IRSnapshotCheckerNoChanges) {
1849+
parseIR(C, R"IR(
1850+
define i32 @foo(i32 %arg) {
1851+
%add0 = add i32 %arg, %arg
1852+
ret i32 %add0
1853+
}
1854+
)IR");
1855+
Function &LLVMF = *M->getFunction("foo");
1856+
sandboxir::Context Ctx(C);
1857+
1858+
[[maybe_unused]] auto *F = Ctx.createFunction(&LLVMF);
1859+
sandboxir::IRSnapshotChecker Checker(Ctx);
1860+
Checker.save();
1861+
Checker.expectNoDiff();
1862+
}
1863+
1864+
TEST_F(TrackerTest, IRSnapshotCheckerDiesWithUnexpectedChanges) {
1865+
parseIR(C, R"IR(
1866+
define i32 @foo(i32 %arg) {
1867+
%add0 = add i32 %arg, %arg
1868+
%add1 = add i32 %add0, %arg
1869+
ret i32 %add1
1870+
}
1871+
)IR");
1872+
Function &LLVMF = *M->getFunction("foo");
1873+
sandboxir::Context Ctx(C);
1874+
1875+
auto *F = Ctx.createFunction(&LLVMF);
1876+
auto *BB = &*F->begin();
1877+
auto It = BB->begin();
1878+
sandboxir::Instruction *Add0 = &*It++;
1879+
sandboxir::Instruction *Add1 = &*It++;
1880+
sandboxir::IRSnapshotChecker Checker(Ctx);
1881+
Checker.save();
1882+
Add1->setOperand(1, Add0);
1883+
EXPECT_DEATH(Checker.expectNoDiff(), "Found IR difference");
1884+
}
1885+
1886+
TEST_F(TrackerTest, IRSnapshotCheckerSaveMultipleTimes) {
1887+
parseIR(C, R"IR(
1888+
define i32 @foo(i32 %arg) {
1889+
%add0 = add i32 %arg, %arg
1890+
%add1 = add i32 %add0, %arg
1891+
ret i32 %add1
1892+
}
1893+
)IR");
1894+
Function &LLVMF = *M->getFunction("foo");
1895+
sandboxir::Context Ctx(C);
1896+
1897+
auto *F = Ctx.createFunction(&LLVMF);
1898+
auto *BB = &*F->begin();
1899+
auto It = BB->begin();
1900+
sandboxir::Instruction *Add0 = &*It++;
1901+
sandboxir::Instruction *Add1 = &*It++;
1902+
sandboxir::IRSnapshotChecker Checker(Ctx);
1903+
Checker.save();
1904+
Add1->setOperand(1, Add0);
1905+
// Now IR differs from the last snapshot. Let's take a new snapshot.
1906+
Checker.save();
1907+
// The new snapshot should have replaced the old one, so this should succeed.
1908+
Checker.expectNoDiff();
1909+
}

0 commit comments

Comments
 (0)