1 | //===--- MisExpect.cpp - Check the use of llvm.expect with PGO data -------===// |
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 contains code to emit warnings for potentially incorrect usage of the |
10 | // llvm.expect intrinsic. This utility extracts the threshold values from |
11 | // metadata associated with the instrumented Branch or Switch instruction. The |
12 | // threshold values are then used to determine if a warning should be emmited. |
13 | // |
14 | // MisExpect's implementation relies on two assumptions about how branch weights |
15 | // are managed in LLVM. |
16 | // |
17 | // 1) Frontend profiling weights are always in place before llvm.expect is |
18 | // lowered in LowerExpectIntrinsic.cpp. Frontend based instrumentation therefore |
19 | // needs to extract the branch weights and then compare them to the weights |
20 | // being added by the llvm.expect intrinsic lowering. |
21 | // |
22 | // 2) Sampling and IR based profiles will *only* have branch weight metadata |
23 | // before profiling data is consulted if they are from a lowered llvm.expect |
24 | // intrinsic. These profiles thus always extract the expected weights and then |
25 | // compare them to the weights collected during profiling to determine if a |
26 | // diagnostic message is warranted. |
27 | // |
28 | //===----------------------------------------------------------------------===// |
29 | |
30 | #include "llvm/Transforms/Utils/MisExpect.h" |
31 | #include "llvm/ADT/Twine.h" |
32 | #include "llvm/Analysis/OptimizationRemarkEmitter.h" |
33 | #include "llvm/IR/DiagnosticInfo.h" |
34 | #include "llvm/IR/Instruction.h" |
35 | #include "llvm/IR/Instructions.h" |
36 | #include "llvm/IR/LLVMContext.h" |
37 | #include "llvm/IR/ProfDataUtils.h" |
38 | #include "llvm/Support/BranchProbability.h" |
39 | #include "llvm/Support/CommandLine.h" |
40 | #include "llvm/Support/FormatVariadic.h" |
41 | #include <algorithm> |
42 | #include <cstdint> |
43 | #include <functional> |
44 | #include <numeric> |
45 | |
46 | #define DEBUG_TYPE "misexpect" |
47 | |
48 | using namespace llvm; |
49 | using namespace misexpect; |
50 | |
51 | namespace llvm { |
52 | |
53 | // Command line option to enable/disable the warning when profile data suggests |
54 | // a mismatch with the use of the llvm.expect intrinsic |
55 | static cl::opt<bool> PGOWarnMisExpect( |
56 | "pgo-warn-misexpect" , cl::init(Val: false), cl::Hidden, |
57 | cl::desc("Use this option to turn on/off " |
58 | "warnings about incorrect usage of llvm.expect intrinsics." )); |
59 | |
60 | // Command line option for setting the diagnostic tolerance threshold |
61 | static cl::opt<uint32_t> MisExpectTolerance( |
62 | "misexpect-tolerance" , cl::init(Val: 0), |
63 | cl::desc("Prevents emitting diagnostics when profile counts are " |
64 | "within N% of the threshold.." )); |
65 | |
66 | } // namespace llvm |
67 | |
68 | namespace { |
69 | |
70 | bool isMisExpectDiagEnabled(LLVMContext &Ctx) { |
71 | return PGOWarnMisExpect || Ctx.getMisExpectWarningRequested(); |
72 | } |
73 | |
74 | uint32_t getMisExpectTolerance(LLVMContext &Ctx) { |
75 | return std::max(a: static_cast<uint32_t>(MisExpectTolerance), |
76 | b: Ctx.getDiagnosticsMisExpectTolerance()); |
77 | } |
78 | |
79 | Instruction *getInstCondition(Instruction *I) { |
80 | assert(I != nullptr && "MisExpect target Instruction cannot be nullptr" ); |
81 | Instruction *Ret = nullptr; |
82 | if (auto *B = dyn_cast<BranchInst>(Val: I)) { |
83 | Ret = dyn_cast<Instruction>(Val: B->getCondition()); |
84 | } |
85 | // TODO: Find a way to resolve condition location for switches |
86 | // Using the condition of the switch seems to often resolve to an earlier |
87 | // point in the program, i.e. the calculation of the switch condition, rather |
88 | // than the switch's location in the source code. Thus, we should use the |
89 | // instruction to get source code locations rather than the condition to |
90 | // improve diagnostic output, such as the caret. If the same problem exists |
91 | // for branch instructions, then we should remove this function and directly |
92 | // use the instruction |
93 | // |
94 | else if (auto *S = dyn_cast<SwitchInst>(Val: I)) { |
95 | Ret = dyn_cast<Instruction>(Val: S->getCondition()); |
96 | } |
97 | return Ret ? Ret : I; |
98 | } |
99 | |
100 | void emitMisexpectDiagnostic(Instruction *I, LLVMContext &Ctx, |
101 | uint64_t ProfCount, uint64_t TotalCount) { |
102 | double PercentageCorrect = (double)ProfCount / TotalCount; |
103 | auto PerString = |
104 | formatv(Fmt: "{0:P} ({1} / {2})" , Vals&: PercentageCorrect, Vals&: ProfCount, Vals&: TotalCount); |
105 | auto RemStr = formatv( |
106 | Fmt: "Potential performance regression from use of the llvm.expect intrinsic: " |
107 | "Annotation was correct on {0} of profiled executions." , |
108 | Vals&: PerString); |
109 | Instruction *Cond = getInstCondition(I); |
110 | if (isMisExpectDiagEnabled(Ctx)) |
111 | Ctx.diagnose(DI: DiagnosticInfoMisExpect(Cond, Twine(PerString))); |
112 | OptimizationRemarkEmitter ORE(I->getParent()->getParent()); |
113 | ORE.emit(OptDiag: OptimizationRemark(DEBUG_TYPE, "misexpect" , Cond) << RemStr.str()); |
114 | } |
115 | |
116 | } // namespace |
117 | |
118 | namespace llvm { |
119 | namespace misexpect { |
120 | |
121 | void verifyMisExpect(Instruction &I, ArrayRef<uint32_t> RealWeights, |
122 | ArrayRef<uint32_t> ExpectedWeights) { |
123 | // To determine if we emit a diagnostic, we need to compare the branch weights |
124 | // from the profile to those added by the llvm.expect intrinsic. |
125 | // So first, we extract the "likely" and "unlikely" weights from |
126 | // ExpectedWeights And determine the correct weight in the profile to compare |
127 | // against. |
128 | uint64_t LikelyBranchWeight = 0, |
129 | UnlikelyBranchWeight = std::numeric_limits<uint32_t>::max(); |
130 | size_t MaxIndex = 0; |
131 | for (size_t Idx = 0, End = ExpectedWeights.size(); Idx < End; Idx++) { |
132 | uint32_t V = ExpectedWeights[Idx]; |
133 | if (LikelyBranchWeight < V) { |
134 | LikelyBranchWeight = V; |
135 | MaxIndex = Idx; |
136 | } |
137 | if (UnlikelyBranchWeight > V) { |
138 | UnlikelyBranchWeight = V; |
139 | } |
140 | } |
141 | |
142 | const uint64_t ProfiledWeight = RealWeights[MaxIndex]; |
143 | const uint64_t RealWeightsTotal = |
144 | std::accumulate(first: RealWeights.begin(), last: RealWeights.end(), init: (uint64_t)0, |
145 | binary_op: std::plus<uint64_t>()); |
146 | const uint64_t NumUnlikelyTargets = RealWeights.size() - 1; |
147 | |
148 | uint64_t TotalBranchWeight = |
149 | LikelyBranchWeight + (UnlikelyBranchWeight * NumUnlikelyTargets); |
150 | |
151 | // Failing this assert means that we have corrupted metadata. |
152 | assert((TotalBranchWeight >= LikelyBranchWeight) && (TotalBranchWeight > 0) && |
153 | "TotalBranchWeight is less than the Likely branch weight" ); |
154 | |
155 | // To determine our threshold value we need to obtain the branch probability |
156 | // for the weights added by llvm.expect and use that proportion to calculate |
157 | // our threshold based on the collected profile data. |
158 | auto LikelyProbablilty = BranchProbability::getBranchProbability( |
159 | Numerator: LikelyBranchWeight, Denominator: TotalBranchWeight); |
160 | |
161 | uint64_t ScaledThreshold = LikelyProbablilty.scale(Num: RealWeightsTotal); |
162 | |
163 | // clamp tolerance range to [0, 100) |
164 | auto Tolerance = getMisExpectTolerance(Ctx&: I.getContext()); |
165 | Tolerance = std::clamp(val: Tolerance, lo: 0u, hi: 99u); |
166 | |
167 | // Allow users to relax checking by N% i.e., if they use a 5% tolerance, |
168 | // then we check against 0.95*ScaledThreshold |
169 | if (Tolerance > 0) |
170 | ScaledThreshold *= (1.0 - Tolerance / 100.0); |
171 | |
172 | // When the profile weight is below the threshold, we emit the diagnostic |
173 | if (ProfiledWeight < ScaledThreshold) |
174 | emitMisexpectDiagnostic(I: &I, Ctx&: I.getContext(), ProfCount: ProfiledWeight, |
175 | TotalCount: RealWeightsTotal); |
176 | } |
177 | |
178 | void checkBackendInstrumentation(Instruction &I, |
179 | const ArrayRef<uint32_t> RealWeights) { |
180 | // Backend checking assumes any existing weight comes from an `llvm.expect` |
181 | // intrinsic. However, SampleProfiling + ThinLTO add branch weights multiple |
182 | // times, leading to an invalid assumption in our checking. Backend checks |
183 | // should only operate on branch weights that carry the "!expected" field, |
184 | // since they are guaranteed to be added by the LowerExpectIntrinsic pass. |
185 | if (!hasBranchWeightOrigin(I)) |
186 | return; |
187 | SmallVector<uint32_t> ExpectedWeights; |
188 | if (!extractBranchWeights(I, Weights&: ExpectedWeights)) |
189 | return; |
190 | verifyMisExpect(I, RealWeights, ExpectedWeights); |
191 | } |
192 | |
193 | void checkFrontendInstrumentation(Instruction &I, |
194 | const ArrayRef<uint32_t> ExpectedWeights) { |
195 | SmallVector<uint32_t> RealWeights; |
196 | if (!extractBranchWeights(I, Weights&: RealWeights)) |
197 | return; |
198 | verifyMisExpect(I, RealWeights, ExpectedWeights); |
199 | } |
200 | |
201 | void checkExpectAnnotations(Instruction &I, |
202 | const ArrayRef<uint32_t> ExistingWeights, |
203 | bool IsFrontend) { |
204 | if (IsFrontend) { |
205 | checkFrontendInstrumentation(I, ExpectedWeights: ExistingWeights); |
206 | } else { |
207 | checkBackendInstrumentation(I, RealWeights: ExistingWeights); |
208 | } |
209 | } |
210 | |
211 | } // namespace misexpect |
212 | } // namespace llvm |
213 | #undef DEBUG_TYPE |
214 | |