Skip to content

Commit 7b8cde7

Browse files
committed
[TargetVerifier][AMDGPU] Add TargetVerifier.
This pass verifies the IR for an individual backend. This is different than Lint because it consolidates all checks for a given backend in a single pass. A check for Lint may be undefined behavior across all targets, whereas a check in TargetVerifier would only pertain to the specified target but can check more than just undefined behavior such are IR validity. A use case of this would be to reject programs with invalid IR while fuzzing.
1 parent 1c5b122 commit 7b8cde7

File tree

10 files changed

+624
-7
lines changed

10 files changed

+624
-7
lines changed

llvm/include/llvm/IR/Module.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ class LLVM_ABI Module {
211211
/// @name Constructors
212212
/// @{
213213
public:
214+
/// Is this Module valid as determined by one of the verification passes
215+
/// i.e. Lint, Verifier, TargetVerifier.
216+
bool IsValid = true;
217+
214218
/// Is this Module using intrinsics to record the position of debugging
215219
/// information, or non-intrinsic records? See IsNewDbgInfoFormat in
216220
/// \ref BasicBlock.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//===-- llvm/Target/TargetVerifier.h - LLVM IR Target Verifier ---*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file defines target verifier interfaces that can be used for some
10+
// validation of input to the system, and for checking that transformations
11+
// haven't done something bad. In contrast to the Verifier or Lint, the
12+
// TargetVerifier looks for constructions invalid to a particular target
13+
// machine.
14+
//
15+
// To see what specifically is checked, look at TargetVerifier.cpp or an
16+
// individual backend's TargetVerifier.
17+
//
18+
//===----------------------------------------------------------------------===//
19+
20+
#ifndef LLVM_TARGET_VERIFIER_H
21+
#define LLVM_TARGET_VERIFIER_H
22+
23+
#include "llvm/IR/PassManager.h"
24+
#include "llvm/IR/Module.h"
25+
#include "llvm/TargetParser/Triple.h"
26+
27+
namespace llvm {
28+
29+
class Function;
30+
31+
class TargetVerifierPass : public PassInfoMixin<TargetVerifierPass> {
32+
public:
33+
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) {}
34+
};
35+
36+
class TargetVerify {
37+
protected:
38+
void WriteValues(ArrayRef<const Value *> Vs) {
39+
for (const Value *V : Vs) {
40+
if (!V)
41+
continue;
42+
if (isa<Instruction>(V)) {
43+
MessagesStr << *V << '\n';
44+
} else {
45+
V->printAsOperand(MessagesStr, true, Mod);
46+
MessagesStr << '\n';
47+
}
48+
}
49+
}
50+
51+
/// A check failed, so printout out the condition and the message.
52+
///
53+
/// This provides a nice place to put a breakpoint if you want to see why
54+
/// something is not correct.
55+
void CheckFailed(const Twine &Message) { MessagesStr << Message << '\n'; }
56+
57+
/// A check failed (with values to print).
58+
///
59+
/// This calls the Message-only version so that the above is easier to set
60+
/// a breakpoint on.
61+
template <typename T1, typename... Ts>
62+
void CheckFailed(const Twine &Message, const T1 &V1, const Ts &... Vs) {
63+
CheckFailed(Message);
64+
WriteValues({V1, Vs...});
65+
}
66+
public:
67+
Module *Mod;
68+
Triple TT;
69+
70+
std::string Messages;
71+
raw_string_ostream MessagesStr;
72+
73+
TargetVerify(Module *Mod)
74+
: Mod(Mod), TT(Triple::normalize(Mod->getTargetTriple())),
75+
MessagesStr(Messages) {}
76+
77+
void run(Function &F) {};
78+
};
79+
80+
} // namespace llvm
81+
82+
#endif // LLVM_TARGET_VERIFIER_H
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//===-- llvm/Target/TargetVerify/AMDGPUTargetVerifier.h - AMDGPU ---*- C++ -*-===//
2+
////
3+
//// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
//// See https://llvm.org/LICENSE.txt for license information.
5+
//// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
////
7+
////===----------------------------------------------------------------------===//
8+
////
9+
//// This file defines target verifier interfaces that can be used for some
10+
//// validation of input to the system, and for checking that transformations
11+
//// haven't done something bad. In contrast to the Verifier or Lint, the
12+
//// TargetVerifier looks for constructions invalid to a particular target
13+
//// machine.
14+
////
15+
//// To see what specifically is checked, look at an individual backend's
16+
//// TargetVerifier.
17+
////
18+
////===----------------------------------------------------------------------===//
19+
20+
#ifndef LLVM_AMDGPU_TARGET_VERIFIER_H
21+
#define LLVM_AMDGPU_TARGET_VERIFIER_H
22+
23+
#include "llvm/Target/TargetVerifier.h"
24+
25+
namespace llvm {
26+
27+
class Function;
28+
29+
class AMDGPUTargetVerifierPass : public TargetVerifierPass {
30+
public:
31+
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
32+
};
33+
34+
} // namespace llvm
35+
36+
#endif // LLVM_AMDGPU_TARGET_VERIFIER_H

llvm/lib/Analysis/Lint.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -747,10 +747,13 @@ PreservedAnalyses LintPass::run(Function &F, FunctionAnalysisManager &AM) {
747747
Lint L(Mod, DL, AA, AC, DT, TLI);
748748
L.visit(F);
749749
dbgs() << L.MessagesStr.str();
750-
if (LintAbortOnError && !L.MessagesStr.str().empty())
751-
report_fatal_error(Twine("Linter found errors, aborting. (enabled by --") +
750+
if (!L.MessagesStr.str().empty()) {
751+
F.getParent()->IsValid = false;
752+
if (LintAbortOnError)
753+
report_fatal_error(Twine("Linter found errors, aborting. (enabled by --") +
752754
LintAbortOnErrorArgName + ")",
753-
false);
755+
false);
756+
}
754757
return PreservedAnalyses::all();
755758
}
756759

llvm/lib/IR/Verifier.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ static cl::opt<bool> VerifyNoAliasScopeDomination(
135135
cl::desc("Ensure that llvm.experimental.noalias.scope.decl for identical "
136136
"scopes are not dominating"));
137137

138+
static cl::opt<bool>
139+
VerifyAbortOnError("verifier-abort-on-error", cl::init(false),
140+
cl::desc("In the Verifier pass, abort on errors."));
141+
138142
namespace llvm {
139143

140144
struct VerifierSupport {
@@ -7739,16 +7743,22 @@ VerifierAnalysis::Result VerifierAnalysis::run(Function &F,
77397743

77407744
PreservedAnalyses VerifierPass::run(Module &M, ModuleAnalysisManager &AM) {
77417745
auto Res = AM.getResult<VerifierAnalysis>(M);
7742-
if (FatalErrors && (Res.IRBroken || Res.DebugInfoBroken))
7743-
report_fatal_error("Broken module found, compilation aborted!");
7746+
if (Res.IRBroken || Res.DebugInfoBroken) {
7747+
M.IsValid = false;
7748+
if (VerifyAbortOnError && FatalErrors)
7749+
report_fatal_error("Broken module found, compilation aborted!");
7750+
}
77447751

77457752
return PreservedAnalyses::all();
77467753
}
77477754

77487755
PreservedAnalyses VerifierPass::run(Function &F, FunctionAnalysisManager &AM) {
77497756
auto res = AM.getResult<VerifierAnalysis>(F);
7750-
if (res.IRBroken && FatalErrors)
7751-
report_fatal_error("Broken function found, compilation aborted!");
7757+
if (res.IRBroken) {
7758+
F.getParent()->IsValid = false;
7759+
if (VerifyAbortOnError && FatalErrors)
7760+
report_fatal_error("Broken function found, compilation aborted!");
7761+
}
77527762

77537763
return PreservedAnalyses::all();
77547764
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#include "llvm/Target/TargetVerify/AMDGPUTargetVerifier.h"
2+
3+
#include "llvm/Analysis/UniformityAnalysis.h"
4+
#include "llvm/Analysis/PostDominators.h"
5+
#include "llvm/Support/Debug.h"
6+
#include "llvm/IR/Dominators.h"
7+
#include "llvm/IR/Function.h"
8+
#include "llvm/IR/IntrinsicInst.h"
9+
#include "llvm/IR/IntrinsicsAMDGPU.h"
10+
#include "llvm/IR/Module.h"
11+
#include "llvm/IR/Value.h"
12+
13+
#include "llvm/Support/raw_ostream.h"
14+
15+
using namespace llvm;
16+
17+
static cl::opt<bool>
18+
MarkUniform("mark-uniform", cl::desc("Mark instructions as uniform"), cl::init(false));
19+
20+
// Check - We know that cond should be true, if not print an error message.
21+
#define Check(C, ...) \
22+
do { \
23+
if (!(C)) { \
24+
TargetVerify::CheckFailed(__VA_ARGS__); \
25+
return; \
26+
} \
27+
} while (false)
28+
29+
static bool isMFMA(unsigned IID) {
30+
switch (IID) {
31+
case Intrinsic::amdgcn_mfma_f32_4x4x1f32:
32+
case Intrinsic::amdgcn_mfma_f32_4x4x4f16:
33+
case Intrinsic::amdgcn_mfma_i32_4x4x4i8:
34+
case Intrinsic::amdgcn_mfma_f32_4x4x2bf16:
35+
36+
case Intrinsic::amdgcn_mfma_f32_16x16x1f32:
37+
case Intrinsic::amdgcn_mfma_f32_16x16x4f32:
38+
case Intrinsic::amdgcn_mfma_f32_16x16x4f16:
39+
case Intrinsic::amdgcn_mfma_f32_16x16x16f16:
40+
case Intrinsic::amdgcn_mfma_i32_16x16x4i8:
41+
case Intrinsic::amdgcn_mfma_i32_16x16x16i8:
42+
case Intrinsic::amdgcn_mfma_f32_16x16x2bf16:
43+
case Intrinsic::amdgcn_mfma_f32_16x16x8bf16:
44+
45+
case Intrinsic::amdgcn_mfma_f32_32x32x1f32:
46+
case Intrinsic::amdgcn_mfma_f32_32x32x2f32:
47+
case Intrinsic::amdgcn_mfma_f32_32x32x4f16:
48+
case Intrinsic::amdgcn_mfma_f32_32x32x8f16:
49+
case Intrinsic::amdgcn_mfma_i32_32x32x4i8:
50+
case Intrinsic::amdgcn_mfma_i32_32x32x8i8:
51+
case Intrinsic::amdgcn_mfma_f32_32x32x2bf16:
52+
case Intrinsic::amdgcn_mfma_f32_32x32x4bf16:
53+
54+
case Intrinsic::amdgcn_mfma_f32_4x4x4bf16_1k:
55+
case Intrinsic::amdgcn_mfma_f32_16x16x4bf16_1k:
56+
case Intrinsic::amdgcn_mfma_f32_16x16x16bf16_1k:
57+
case Intrinsic::amdgcn_mfma_f32_32x32x4bf16_1k:
58+
case Intrinsic::amdgcn_mfma_f32_32x32x8bf16_1k:
59+
60+
case Intrinsic::amdgcn_mfma_f64_16x16x4f64:
61+
case Intrinsic::amdgcn_mfma_f64_4x4x4f64:
62+
63+
case Intrinsic::amdgcn_mfma_i32_16x16x32_i8:
64+
case Intrinsic::amdgcn_mfma_i32_32x32x16_i8:
65+
case Intrinsic::amdgcn_mfma_f32_16x16x8_xf32:
66+
case Intrinsic::amdgcn_mfma_f32_32x32x4_xf32:
67+
68+
case Intrinsic::amdgcn_mfma_f32_16x16x32_bf8_bf8:
69+
case Intrinsic::amdgcn_mfma_f32_16x16x32_bf8_fp8:
70+
case Intrinsic::amdgcn_mfma_f32_16x16x32_fp8_bf8:
71+
case Intrinsic::amdgcn_mfma_f32_16x16x32_fp8_fp8:
72+
73+
case Intrinsic::amdgcn_mfma_f32_32x32x16_bf8_bf8:
74+
case Intrinsic::amdgcn_mfma_f32_32x32x16_bf8_fp8:
75+
case Intrinsic::amdgcn_mfma_f32_32x32x16_fp8_bf8:
76+
case Intrinsic::amdgcn_mfma_f32_32x32x16_fp8_fp8:
77+
return true;
78+
default:
79+
return false;
80+
}
81+
}
82+
83+
namespace llvm {
84+
class AMDGPUTargetVerify : public TargetVerify {
85+
public:
86+
Module *Mod;
87+
88+
DominatorTree *DT;
89+
PostDominatorTree *PDT;
90+
UniformityInfo *UA;
91+
92+
AMDGPUTargetVerify(Module *Mod, DominatorTree *DT, PostDominatorTree *PDT, UniformityInfo *UA)
93+
: TargetVerify(Mod), Mod(Mod), DT(DT), PDT(PDT), UA(UA) {}
94+
95+
void run(Function &F);
96+
};
97+
98+
static bool IsValidInt(const Type *Ty) {
99+
return Ty->isIntegerTy(1) ||
100+
Ty->isIntegerTy(8) ||
101+
Ty->isIntegerTy(16) ||
102+
Ty->isIntegerTy(32) ||
103+
Ty->isIntegerTy(64) ||
104+
Ty->isIntegerTy(128);
105+
}
106+
107+
static bool isShader(CallingConv::ID CC) {
108+
switch(CC) {
109+
case CallingConv::AMDGPU_VS:
110+
case CallingConv::AMDGPU_LS:
111+
case CallingConv::AMDGPU_HS:
112+
case CallingConv::AMDGPU_ES:
113+
case CallingConv::AMDGPU_GS:
114+
case CallingConv::AMDGPU_PS:
115+
case CallingConv::AMDGPU_CS_Chain:
116+
case CallingConv::AMDGPU_CS_ChainPreserve:
117+
case CallingConv::AMDGPU_CS:
118+
return true;
119+
default:
120+
return false;
121+
}
122+
}
123+
124+
void AMDGPUTargetVerify::run(Function &F) {
125+
// Ensure shader calling convention returns void
126+
if (isShader(F.getCallingConv()))
127+
Check(F.getReturnType() == Type::getVoidTy(F.getContext()), "Shaders must return void");
128+
129+
for (auto &BB : F) {
130+
131+
for (auto &I : BB) {
132+
if (MarkUniform)
133+
outs() << UA->isUniform(&I) << ' ' << I << '\n';
134+
135+
// Ensure integral types are valid: i8, i16, i32, i64, i128
136+
if (I.getType()->isIntegerTy())
137+
Check(IsValidInt(I.getType()), "Int type is invalid.", &I);
138+
for (unsigned i = 0; i < I.getNumOperands(); ++i)
139+
if (I.getOperand(i)->getType()->isIntegerTy())
140+
Check(IsValidInt(I.getOperand(i)->getType()),
141+
"Int type is invalid.", I.getOperand(i));
142+
143+
// Ensure no store to const memory
144+
if (auto *SI = dyn_cast<StoreInst>(&I))
145+
{
146+
unsigned AS = SI->getPointerAddressSpace();
147+
Check(AS != 4, "Write to const memory", SI);
148+
}
149+
150+
// Ensure no kernel to kernel calls.
151+
if (auto *CI = dyn_cast<CallInst>(&I))
152+
{
153+
CallingConv::ID CalleeCC = CI->getCallingConv();
154+
if (CalleeCC == CallingConv::AMDGPU_KERNEL)
155+
{
156+
CallingConv::ID CallerCC = CI->getParent()->getParent()->getCallingConv();
157+
Check(CallerCC != CallingConv::AMDGPU_KERNEL,
158+
"A kernel may not call a kernel", CI->getParent()->getParent());
159+
}
160+
}
161+
162+
// Ensure MFMA is not in control flow with diverging operands
163+
if (auto *II = dyn_cast<IntrinsicInst>(&I)) {
164+
if (isMFMA(II->getIntrinsicID())) {
165+
bool InControlFlow = false;
166+
for (const auto &P : predecessors(&BB))
167+
if (!PDT->dominates(&BB, P)) {
168+
InControlFlow = true;
169+
break;
170+
}
171+
for (const auto &S : successors(&BB))
172+
if (!DT->dominates(&BB, S)) {
173+
InControlFlow = true;
174+
break;
175+
}
176+
if (InControlFlow) {
177+
// If operands to MFMA are not uniform, MFMA cannot be in control flow
178+
bool hasUniformOperands = true;
179+
for (unsigned i = 0; i < II->getNumOperands(); i++) {
180+
if (!UA->isUniform(II->getOperand(i))) {
181+
dbgs() << "Not uniform: " << *II->getOperand(i) << '\n';
182+
hasUniformOperands = false;
183+
}
184+
}
185+
if (!hasUniformOperands) Check(false, "MFMA in control flow", II);
186+
//else Check(false, "MFMA in control flow (uniform operands)", II);
187+
}
188+
//else Check(false, "MFMA not in control flow", II);
189+
}
190+
}
191+
}
192+
}
193+
}
194+
195+
PreservedAnalyses AMDGPUTargetVerifierPass::run(Function &F, FunctionAnalysisManager &AM) {
196+
197+
auto *Mod = F.getParent();
198+
199+
auto UA = &AM.getResult<UniformityInfoAnalysis>(F);
200+
auto *DT = &AM.getResult<DominatorTreeAnalysis>(F);
201+
auto *PDT = &AM.getResult<PostDominatorTreeAnalysis>(F);
202+
203+
AMDGPUTargetVerify TV(Mod, DT, PDT, UA);
204+
TV.run(F);
205+
206+
dbgs() << TV.MessagesStr.str();
207+
if (!TV.MessagesStr.str().empty()) {
208+
F.getParent()->IsValid = false;
209+
}
210+
211+
return PreservedAnalyses::all();
212+
}
213+
} // namespace llvm

0 commit comments

Comments
 (0)