1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
|
//===- ProfileVerify.cpp - Verify profile info for testing ----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "llvm/Transforms/Utils/ProfileVerify.h"
#include "llvm/ADT/DynamicAPInt.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Analysis/BranchProbabilityInfo.h"
#include "llvm/IR/Analysis.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/MDBuilder.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/PassManager.h"
#include "llvm/IR/ProfDataUtils.h"
#include "llvm/Support/BranchProbability.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/CommandLine.h"
using namespace llvm;
static cl::opt<int64_t>
DefaultFunctionEntryCount("profcheck-default-function-entry-count",
cl::init(1000));
static cl::opt<bool>
AnnotateSelect("profcheck-annotate-select", cl::init(true),
cl::desc("Also inject (if missing) and verify MD_prof for "
"`select` instructions"));
static cl::opt<bool>
WeightsForTest("profcheck-weights-for-test", cl::init(false),
cl::desc("Generate weights with small values for tests."));
static cl::opt<uint32_t> SelectTrueWeight(
"profcheck-default-select-true-weight", cl::init(2U),
cl::desc("When annotating `select` instructions, this value will be used "
"for the first ('true') case."));
static cl::opt<uint32_t> SelectFalseWeight(
"profcheck-default-select-false-weight", cl::init(3U),
cl::desc("When annotating `select` instructions, this value will be used "
"for the second ('false') case."));
namespace {
class ProfileInjector {
Function &F;
FunctionAnalysisManager &FAM;
public:
static const Instruction *
getTerminatorBenefitingFromMDProf(const BasicBlock &BB) {
if (succ_size(&BB) < 2)
return nullptr;
auto *Term = BB.getTerminator();
return (isa<BranchInst>(Term) || isa<SwitchInst>(Term) ||
isa<IndirectBrInst>(Term) || isa<CallBrInst>(Term))
? Term
: nullptr;
}
static Instruction *getTerminatorBenefitingFromMDProf(BasicBlock &BB) {
return const_cast<Instruction *>(
getTerminatorBenefitingFromMDProf(const_cast<const BasicBlock &>(BB)));
}
ProfileInjector(Function &F, FunctionAnalysisManager &FAM) : F(F), FAM(FAM) {}
bool inject();
};
bool isAsmOnly(const Function &F) {
if (!F.hasFnAttribute(Attribute::AttrKind::Naked))
return false;
for (const auto &BB : F)
for (const auto &I : drop_end(BB.instructionsWithoutDebug())) {
const auto *CB = dyn_cast<CallBase>(&I);
if (!CB || !CB->isInlineAsm())
return false;
}
return true;
}
void emitProfileError(StringRef Msg, Function &F) {
F.getContext().emitError("Profile verification failed for function '" +
F.getName() + "': " + Msg);
}
} // namespace
// FIXME: currently this injects only for terminators. Select isn't yet
// supported.
bool ProfileInjector::inject() {
// skip purely asm functions
if (isAsmOnly(F))
return false;
// Get whatever branch probability info can be derived from the given IR -
// whether it has or not metadata. The main intention for this pass is to
// ensure that other passes don't drop or "forget" to update MD_prof. We do
// this as a mode in which lit tests would run. We want to avoid changing the
// behavior of those tests. A pass may use BPI (or BFI, which is computed from
// BPI). If no metadata is present, BPI is guesstimated by
// BranchProbabilityAnalysis. The injector (this pass) only persists whatever
// information the analysis provides, in other words, the pass being tested
// will get the same BPI it does if the injector wasn't running.
auto &BPI = FAM.getResult<BranchProbabilityAnalysis>(F);
// Inject a function count if there's none. It's reasonable for a pass to
// want to clear the MD_prof of a function with zero entry count. If the
// original profile (iFDO or AFDO) is empty for a function, it's simpler to
// require assigning it the 0-entry count explicitly than to mark every branch
// as cold (we do want some explicit information in the spirit of what this
// verifier wants to achieve - make dropping / corrupting MD_prof
// unit-testable)
if (!F.getEntryCount(/*AllowSynthetic=*/true))
F.setEntryCount(DefaultFunctionEntryCount);
// If there is an entry count that's 0, then don't bother injecting. We won't
// verify these either.
if (F.getEntryCount(/*AllowSynthetic=*/true)->getCount() == 0)
return false;
bool Changed = false;
// Cycle through the weights list. If we didn't, tests with more than (say)
// one conditional branch would have the same !prof metadata on all of them,
// and numerically that may make for a poor unit test.
uint32_t WeightsForTestOffset = 0;
for (auto &BB : F) {
if (AnnotateSelect) {
for (auto &I : BB) {
if (auto *SI = dyn_cast<SelectInst>(&I)) {
if (SI->getCondition()->getType()->isVectorTy())
continue;
if (I.getMetadata(LLVMContext::MD_prof))
continue;
setBranchWeights(I, {SelectTrueWeight, SelectFalseWeight},
/*IsExpected=*/false);
}
}
}
auto *Term = getTerminatorBenefitingFromMDProf(BB);
if (!Term || Term->getMetadata(LLVMContext::MD_prof))
continue;
SmallVector<BranchProbability> Probs;
SmallVector<uint32_t> Weights;
Weights.reserve(Term->getNumSuccessors());
if (WeightsForTest) {
static const std::array Primes{3, 5, 7, 11, 13, 17, 19, 23, 29, 31,
37, 41, 43, 47, 53, 59, 61, 67, 71};
for (uint32_t I = 0, E = Term->getNumSuccessors(); I < E; ++I)
Weights.emplace_back(
Primes[(WeightsForTestOffset + I) % Primes.size()]);
++WeightsForTestOffset;
} else {
Probs.reserve(Term->getNumSuccessors());
for (auto I = 0U, E = Term->getNumSuccessors(); I < E; ++I)
Probs.emplace_back(BPI.getEdgeProbability(&BB, Term->getSuccessor(I)));
assert(llvm::find_if(Probs,
[](const BranchProbability &P) {
return P.isUnknown();
}) == Probs.end() &&
"All branch probabilities should be valid");
const auto *FirstZeroDenominator =
find_if(Probs, [](const BranchProbability &P) {
return P.getDenominator() == 0;
});
(void)FirstZeroDenominator;
assert(FirstZeroDenominator == Probs.end());
const auto *FirstNonZeroNumerator = find_if(
Probs, [](const BranchProbability &P) { return !P.isZero(); });
assert(FirstNonZeroNumerator != Probs.end());
DynamicAPInt LCM(Probs[0].getDenominator());
DynamicAPInt GCD(FirstNonZeroNumerator->getNumerator());
for (const auto &Prob : drop_begin(Probs)) {
if (!Prob.getNumerator())
continue;
LCM = llvm::lcm(LCM, DynamicAPInt(Prob.getDenominator()));
GCD = llvm::gcd(GCD, DynamicAPInt(Prob.getNumerator()));
}
for (const auto &Prob : Probs) {
DynamicAPInt W =
(Prob.getNumerator() * LCM / GCD) / Prob.getDenominator();
Weights.emplace_back(static_cast<uint32_t>((int64_t)W));
}
}
setBranchWeights(*Term, Weights, /*IsExpected=*/false);
Changed = true;
}
return Changed;
}
PreservedAnalyses ProfileInjectorPass::run(Function &F,
FunctionAnalysisManager &FAM) {
ProfileInjector PI(F, FAM);
if (!PI.inject())
return PreservedAnalyses::all();
return PreservedAnalyses::none();
}
PreservedAnalyses ProfileVerifierPass::run(Module &M,
ModuleAnalysisManager &MAM) {
auto PopulateIgnoreList = [&](StringRef GVName) {
if (const auto *CT = M.getGlobalVariable(GVName))
if (const auto *CA =
dyn_cast_if_present<ConstantArray>(CT->getInitializer()))
for (const auto &Elt : CA->operands())
if (const auto *CS = dyn_cast<ConstantStruct>(Elt))
if (CS->getNumOperands() >= 2 && CS->getOperand(1))
if (const auto *F = dyn_cast<Function>(
CS->getOperand(1)->stripPointerCasts()))
IgnoreList.insert(F);
};
PopulateIgnoreList("llvm.global_ctors");
PopulateIgnoreList("llvm.global_dtors");
// expose the function-level run as public through a wrapper, so we can use
// pass manager mechanisms dealing with declarations and with composing the
// returned PreservedAnalyses values.
struct Wrapper : PassInfoMixin<Wrapper> {
ProfileVerifierPass &PVP;
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
return PVP.run(F, FAM);
}
explicit Wrapper(ProfileVerifierPass &PVP) : PVP(PVP) {}
};
return createModuleToFunctionPassAdaptor(Wrapper(*this)).run(M, MAM);
}
PreservedAnalyses ProfileVerifierPass::run(Function &F,
FunctionAnalysisManager &FAM) {
// skip purely asm functions
if (isAsmOnly(F))
return PreservedAnalyses::all();
if (IgnoreList.contains(&F))
return PreservedAnalyses::all();
const auto EntryCount = F.getEntryCount(/*AllowSynthetic=*/true);
if (!EntryCount) {
auto *MD = F.getMetadata(LLVMContext::MD_prof);
if (!MD || !isExplicitlyUnknownProfileMetadata(*MD)) {
emitProfileError("function entry count missing (set to 0 if cold)", F);
return PreservedAnalyses::all();
}
} else if (EntryCount->getCount() == 0) {
return PreservedAnalyses::all();
}
for (const auto &BB : F) {
if (AnnotateSelect) {
for (const auto &I : BB)
if (auto *SI = dyn_cast<SelectInst>(&I)) {
if (SI->getCondition()->getType()->isVectorTy())
continue;
if (I.getMetadata(LLVMContext::MD_prof))
continue;
emitProfileError("select annotation missing", F);
}
}
if (const auto *Term =
ProfileInjector::getTerminatorBenefitingFromMDProf(BB))
if (!Term->getMetadata(LLVMContext::MD_prof))
emitProfileError("branch annotation missing", F);
}
return PreservedAnalyses::all();
}
|