| 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 | |