[llvm] Add -verify-with-context option to enable better reporting of verifier errors (PR #84867)
Roman L via llvm-commits
llvm-commits at lists.llvm.org
Mon Mar 11 20:37:17 PDT 2024
https://github.com/opti-mix created https://github.com/llvm/llvm-project/pull/84867
The LLVM IR verifier does not provide a lot of context about places where verification errors occur. This makes it difficult to find such places when you have big LLVM IR modules.
This PR adds a new option called -verify-with-context, which enables better verifier error reporting by providing the information about function, basic block and instruction where the error happens. This is achieved by keeping track of the current function, basic block and instruction being visited by the verifier.
Here is an example output with a detailed error context:
```
Verification Error: masked_load: alignment must be a power of 2
Verification Error: Context [Function 'masked_load' -> BasicBlock '' -> Instruction 'res' (number 1 inside BB)]
Context [Function 'masked_load' -> BasicBlock '']
%res = call <2 x double> @llvm.masked.load.v2f64.p0(ptr %addr, i32 3, <2 x i1> %mask, <2 x double> %dst)
```
>From bd577b79826acd68ec744ad3510fec0d909648ad Mon Sep 17 00:00:00 2001
From: rlevenstein <rlevenstein at meta.com>
Date: Mon, 11 Mar 2024 20:26:31 -0700
Subject: [PATCH] Add -verify-with-context option to enable better reporting of
verifier errors
The LLVM IR verifier does not provide a lot of context about places
where verification errors occur. This makes it difficult to find such
places when you have big LLVM IR modules.
This PR adds a new option called -verify-with-context, which enables
better verifier error reporting by providing the information about
function, basic block and instruction where the error happens. This is
achieved by keeping track of the current function, basic block and
instruction being visited by the verifier.
Here is an example output:
```
Verification Error: masked_load: alignment must be a power of 2
Verification Error: Context [Function 'masked_load' -> BasicBlock '' -> Instruction 'res' (number 1 inside BB)]
Context [Function 'masked_load' -> BasicBlock '']
%res = call <2 x double> @llvm.masked.load.v2f64.p0(ptr %addr, i32 3, <2 x i1> %mask, <2 x double> %dst)
```
---
llvm/include/llvm/IR/InstVisitor.h | 32 +++++++-
llvm/lib/IR/Verifier.cpp | 96 +++++++++++++++++++++--
llvm/test/Verifier/verify-with-context.ll | 31 ++++++++
3 files changed, 151 insertions(+), 8 deletions(-)
create mode 100644 llvm/test/Verifier/verify-with-context.ll
diff --git a/llvm/include/llvm/IR/InstVisitor.h b/llvm/include/llvm/IR/InstVisitor.h
index 311e0ac47ddfad..89e7daf5068be9 100644
--- a/llvm/include/llvm/IR/InstVisitor.h
+++ b/llvm/include/llvm/IR/InstVisitor.h
@@ -74,14 +74,41 @@ namespace llvm {
/// virtual function call overhead. Defining and using an InstVisitor is just
/// as efficient as having your own switch statement over the instruction
/// opcode.
-template<typename SubClass, typename RetTy=void>
+
+/// Helper type to manage the current context of an InstVisitor.
+struct DefaultCtxManager {
+ void pushCtx(const Value *V) {}
+ void popCtx() {}
+};
+
+template<typename SubClass, typename RetTy=void, typename CtxManager = DefaultCtxManager>
class InstVisitor {
+ CtxManager *ctxMgr;
+
+ struct ContextUpdater {
+ CtxManager *ctxMgr;
+ llvm::SmallVector<const Value *, 4> path;
+ ContextUpdater(CtxManager *ctxMgr, const Value *V) : ctxMgr(ctxMgr) {
+ path.push_back(V);
+ if (ctxMgr)
+ ctxMgr->pushCtx(V);
+ }
+ ~ContextUpdater() {
+ if (ctxMgr)
+ ctxMgr->popCtx();
+ }
+ };
+
//===--------------------------------------------------------------------===//
// Interface code - This is the public interface of the InstVisitor that you
// use to visit instructions...
//
public:
+ // Ctors
+ InstVisitor() : ctxMgr(nullptr) {}
+ InstVisitor(CtxManager *ctxMgr) : ctxMgr(ctxMgr) {}
+
// Generic visit method - Allow visitation to all instructions in a range
template<class Iterator>
void visit(Iterator Start, Iterator End) {
@@ -96,10 +123,12 @@ class InstVisitor {
visit(M.begin(), M.end());
}
void visit(Function &F) {
+ ContextUpdater ctxManager(ctxMgr, &F);
static_cast<SubClass*>(this)->visitFunction(F);
visit(F.begin(), F.end());
}
void visit(BasicBlock &BB) {
+ ContextUpdater ctxManager(ctxMgr, &BB);
static_cast<SubClass*>(this)->visitBasicBlock(BB);
visit(BB.begin(), BB.end());
}
@@ -113,6 +142,7 @@ class InstVisitor {
// visit - Finally, code to visit an instruction...
//
RetTy visit(Instruction &I) {
+ ContextUpdater ctxManager(ctxMgr, &I);
static_assert(std::is_base_of<InstVisitor, SubClass>::value,
"Must pass the derived type to this template!");
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 0e6c01802cfb8c..0c57acb755da36 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -132,9 +132,66 @@ static cl::opt<bool> VerifyNoAliasScopeDomination(
cl::desc("Ensure that llvm.experimental.noalias.scope.decl for identical "
"scopes are not dominating"));
+static cl::opt<bool> VerifyWithContext(
+ "verify-with-context", cl::Hidden, cl::init(false),
+ cl::desc("Enable a detailed context reporting for verification errors"));
+
namespace llvm {
-struct VerifierSupport {
+/// Helper type to manage the current context of a Verifier.
+struct VerifierCtxManager {
+ /// Current context.
+ SmallVector<const Value *> ContextPath;
+ /// The number of the current instruction in the current basic block.
+ size_t CurInstNumInThisBlock;
+
+ VerifierCtxManager() : CurInstNumInThisBlock(0) {}
+
+ void pushCtx(const Value *V) {
+ if (isa<Instruction>(V))
+ CurInstNumInThisBlock++;
+ else if (isa<BasicBlock>(V) || isa<Function>(V))
+ CurInstNumInThisBlock = 0;
+ ContextPath.emplace_back(V);
+ }
+ void popCtx() {
+ ContextPath.pop_back();
+ }
+ void printCtx(raw_ostream &OS, bool NL = false) {
+ if (ContextPath.empty())
+ return;
+ OS << "Context [";
+ for (size_t i = 0; i < ContextPath.size(); ++i) {
+ if (i > 0) {
+ OS << " -> ";
+ }
+ auto C = ContextPath[i];
+ if (isa<Function>(C)) {
+ OS << "Function '";
+ } else if (isa<BasicBlock>(C)) {
+ OS << "BasicBlock '";
+ } else if (isa<Instruction>(C)) {
+ OS << "Instruction '";
+ } else {
+ OS << "Value '";
+ }
+ OS << C->getName() << "'";
+ if (CurInstNumInThisBlock && isa<Instruction>(C)) {
+ OS << " (number " << CurInstNumInThisBlock << " inside BB)";
+ }
+ }
+ OS << "]";
+ if (NL)
+ OS << "\n";
+ }
+
+ void printCtx(raw_ostream &OS, const char *msg, bool NL = false) {
+ OS << msg;
+ printCtx(OS, NL);
+ }
+};
+
+struct VerifierSupport : VerifierCtxManager {
raw_ostream *OS;
const Module &M;
ModuleSlotTracker MST;
@@ -158,6 +215,24 @@ struct VerifierSupport {
*OS << "; ModuleID = '" << M->getModuleIdentifier() << "'\n";
}
+ void Write(const Instruction &I) {
+ if (VerifyWithContext) {
+ if (auto const *BB = I.getParent()) {
+ VerifierCtxManager ctxMgr;
+ if (auto *F = BB->getParent())
+ ctxMgr.pushCtx(F);
+ ctxMgr.pushCtx(BB);
+ ctxMgr.printCtx(*OS, true);
+ }
+ }
+ Write((const Value &)I);
+ }
+
+ void Write(const Instruction *I) {
+ if (I)
+ Write(*I);
+ }
+
void Write(const Value *V) {
if (V)
Write(*V);
@@ -280,8 +355,12 @@ struct VerifierSupport {
/// This provides a nice place to put a breakpoint if you want to see why
/// something is not correct.
void CheckFailed(const Twine &Message) {
- if (OS)
+ if (VerifyWithContext && OS) {
+ *OS << "Verification Error: " << Message << '\n';
+ printCtx(*OS, "Verification Error: ", /* NL */ true);
+ } else if (OS) {
*OS << Message << '\n';
+ }
Broken = true;
}
@@ -318,8 +397,10 @@ struct VerifierSupport {
namespace {
-class Verifier : public InstVisitor<Verifier>, VerifierSupport {
- friend class InstVisitor<Verifier>;
+class Verifier : public InstVisitor<Verifier, void, VerifierSupport>,
+ VerifierSupport {
+ using VerifierInstVisitor = InstVisitor<Verifier, void, VerifierSupport>;
+ friend VerifierInstVisitor;
// ISD::ArgFlagsTy::MemAlign only have 4 bits for alignment, so
// the alignment size should not exceed 2^15. Since encode(Align)
@@ -398,7 +479,7 @@ class Verifier : public InstVisitor<Verifier>, VerifierSupport {
public:
explicit Verifier(raw_ostream *OS, bool ShouldTreatBrokenDebugInfoAsError,
const Module &M)
- : VerifierSupport(OS, M), LandingPadResultTy(nullptr),
+ : InstVisitor(this), VerifierSupport(OS, M), LandingPadResultTy(nullptr),
SawFrameEscape(false), TBAAVerifyHelper(this) {
TreatBrokenDebugInfoAsError = ShouldTreatBrokenDebugInfoAsError;
}
@@ -547,7 +628,7 @@ class Verifier : public InstVisitor<Verifier>, VerifierSupport {
void visit(DPLabel &DPL);
void visit(DPValue &DPV);
// InstVisitor overrides...
- using InstVisitor<Verifier>::visit;
+ using VerifierInstVisitor::visit;
void visitDbgRecords(Instruction &I);
void visit(Instruction &I);
@@ -706,7 +787,7 @@ void Verifier::visit(Instruction &I) {
visitDbgRecords(I);
for (unsigned i = 0, e = I.getNumOperands(); i != e; ++i)
Check(I.getOperand(i) != nullptr, "Operand is null", &I);
- InstVisitor<Verifier>::visit(I);
+ VerifierInstVisitor::visit(I);
}
// Helper to iterate over indirect users. By returning false, the callback can ask to stop traversing further.
@@ -2989,6 +3070,7 @@ void Verifier::visitFunction(const Function &F) {
void Verifier::visitBasicBlock(BasicBlock &BB) {
InstsInThisBlock.clear();
ConvergenceVerifyHelper.visit(BB);
+ CurInstNumInThisBlock = 0;
// Ensure that basic blocks have terminators!
Check(BB.getTerminator(), "Basic Block does not have terminator!", &BB);
diff --git a/llvm/test/Verifier/verify-with-context.ll b/llvm/test/Verifier/verify-with-context.ll
new file mode 100644
index 00000000000000..cc0ca61bc0884e
--- /dev/null
+++ b/llvm/test/Verifier/verify-with-context.ll
@@ -0,0 +1,31 @@
+; RUN: not opt -S %s -passes=verify -verify-with-context 2>&1 | FileCheck %s
+
+declare i32 @foo(i32)
+define i32 @test_callbr_landingpad_not_first_inst() {
+entry:
+ %0 = callbr i32 asm "", "=r,!i"()
+ to label %asm.fallthrough [label %landingpad]
+
+asm.fallthrough:
+ ret i32 42
+
+landingpad:
+ %foo = call i32 @foo(i32 42)
+; CHECK: Verification Error: No other instructions may proceed intrinsic
+; CHECK-NEXT: Verification Error: Context [Function 'test_callbr_landingpad_not_first_inst' -> BasicBlock 'landingpad' -> Instruction 'out' (number 2 inside BB)]
+; CHECK-NEXT: Context [Function 'test_callbr_landingpad_not_first_inst' -> BasicBlock 'landingpad']
+; CHECK-NEXT: %out = call i32 @llvm.callbr.landingpad.i32(i32 %0)
+ %out = call i32 @llvm.callbr.landingpad.i32(i32 %0)
+ ret i32 %out
+}
+
+declare <2 x double> @llvm.masked.load.v2f64.p0(ptr, i32, <2 x i1>, <2 x double>)
+
+define <2 x double> @masked_load(<2 x i1> %mask, ptr %addr, <2 x double> %dst) {
+; CHECK: Verification Error: masked_load: alignment must be a power of 2
+; CHECK-NEXT: Verification Error: Context [Function 'masked_load' -> BasicBlock '' -> Instruction 'res' (number 1 inside BB)]
+; CHECK-NEXT: Context [Function 'masked_load' -> BasicBlock '']
+; CHECK-NEXT: %res = call <2 x double> @llvm.masked.load.v2f64.p0(ptr %addr, i32 3, <2 x i1> %mask, <2 x double> %dst)
+ %res = call <2 x double> @llvm.masked.load.v2f64.p0(ptr %addr, i32 3, <2 x i1>%mask, <2 x double> %dst)
+ ret <2 x double> %res
+}
More information about the llvm-commits
mailing list