//===--- MisExpect.cpp - Check the use of llvm.expect with PGO data -------===// // // 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 // //===----------------------------------------------------------------------===// // // This contains code to emit warnings for potentially incorrect usage of the // llvm.expect intrinsic. This utility extracts the threshold values from // metadata associated with the instrumented Branch or Switch instruction. The // threshold values are then used to determine if a warning should be emmited. // // MisExpect metadata is generated when llvm.expect intrinsics are lowered see // LowerExpectIntrinsic.cpp // //===----------------------------------------------------------------------===// #include "llvm/Transforms/Utils/MisExpect.h" #include "llvm/ADT/Twine.h" #include "llvm/Analysis/OptimizationRemarkEmitter.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/LLVMContext.h" #include "llvm/Support/BranchProbability.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FormatVariadic.h" #include #include #include #define DEBUG_TYPE "misexpect" using namespace llvm; using namespace misexpect; namespace llvm { // Command line option to enable/disable the warning when profile data suggests // a mismatch with the use of the llvm.expect intrinsic static cl::opt PGOWarnMisExpect( "pgo-warn-misexpect", cl::init(false), cl::Hidden, cl::desc("Use this option to turn on/off " "warnings about incorrect usage of llvm.expect intrinsics.")); } // namespace llvm namespace { Instruction *getOprndOrInst(Instruction *I) { assert(I != nullptr && "MisExpect target Instruction cannot be nullptr"); Instruction *Ret = nullptr; if (auto *B = dyn_cast(I)) { Ret = dyn_cast(B->getCondition()); } // TODO: Find a way to resolve condition location for switches // Using the condition of the switch seems to often resolve to an earlier // point in the program, i.e. the calculation of the switch condition, rather // than the switches location in the source code. Thus, we should use the // instruction to get source code locations rather than the condition to // improve diagnostic output, such as the caret. If the same problem exists // for branch instructions, then we should remove this function and directly // use the instruction // // else if (auto S = dyn_cast(I)) { // Ret = I; //} return Ret ? Ret : I; } void emitMisexpectDiagnostic(Instruction *I, LLVMContext &Ctx, uint64_t ProfCount, uint64_t TotalCount) { double PercentageCorrect = (double)ProfCount / TotalCount; auto PerString = formatv("{0:P} ({1} / {2})", PercentageCorrect, ProfCount, TotalCount); auto RemStr = formatv( "Potential performance regression from use of the llvm.expect intrinsic: " "Annotation was correct on {0} of profiled executions.", PerString); Twine Msg(PerString); Instruction *Cond = getOprndOrInst(I); if (PGOWarnMisExpect) Ctx.diagnose(DiagnosticInfoMisExpect(Cond, Msg)); OptimizationRemarkEmitter ORE(I->getParent()->getParent()); ORE.emit(OptimizationRemark(DEBUG_TYPE, "misexpect", Cond) << RemStr.str()); } } // namespace namespace llvm { namespace misexpect { void verifyMisExpect(Instruction *I, const SmallVector &Weights, LLVMContext &Ctx) { if (auto *MisExpectData = I->getMetadata(LLVMContext::MD_misexpect)) { auto *MisExpectDataName = dyn_cast(MisExpectData->getOperand(0)); if (MisExpectDataName && MisExpectDataName->getString().equals("misexpect")) { LLVM_DEBUG(llvm::dbgs() << "------------------\n"); LLVM_DEBUG(llvm::dbgs() << "Function: " << I->getFunction()->getName() << "\n"); LLVM_DEBUG(llvm::dbgs() << "Instruction: " << *I << ":\n"); LLVM_DEBUG(for (int Idx = 0, Size = Weights.size(); Idx < Size; ++Idx) { llvm::dbgs() << "Weights[" << Idx << "] = " << Weights[Idx] << "\n"; }); // extract values from misexpect metadata const auto *IndexCint = mdconst::dyn_extract(MisExpectData->getOperand(1)); const auto *LikelyCInt = mdconst::dyn_extract(MisExpectData->getOperand(2)); const auto *UnlikelyCInt = mdconst::dyn_extract(MisExpectData->getOperand(3)); if (!IndexCint || !LikelyCInt || !UnlikelyCInt) return; const uint64_t Index = IndexCint->getZExtValue(); const uint64_t LikelyBranchWeight = LikelyCInt->getZExtValue(); const uint64_t UnlikelyBranchWeight = UnlikelyCInt->getZExtValue(); const uint64_t ProfileCount = Weights[Index]; const uint64_t CaseTotal = std::accumulate( Weights.begin(), Weights.end(), (uint64_t)0, std::plus()); const uint64_t NumUnlikelyTargets = Weights.size() - 1; const uint64_t TotalBranchWeight = LikelyBranchWeight + (UnlikelyBranchWeight * NumUnlikelyTargets); const llvm::BranchProbability LikelyThreshold(LikelyBranchWeight, TotalBranchWeight); uint64_t ScaledThreshold = LikelyThreshold.scale(CaseTotal); LLVM_DEBUG(llvm::dbgs() << "Unlikely Targets: " << NumUnlikelyTargets << ":\n"); LLVM_DEBUG(llvm::dbgs() << "Profile Count: " << ProfileCount << ":\n"); LLVM_DEBUG(llvm::dbgs() << "Scaled Threshold: " << ScaledThreshold << ":\n"); LLVM_DEBUG(llvm::dbgs() << "------------------\n"); if (ProfileCount < ScaledThreshold) emitMisexpectDiagnostic(I, Ctx, ProfileCount, CaseTotal); } } } void checkFrontendInstrumentation(Instruction &I) { if (auto *MD = I.getMetadata(LLVMContext::MD_prof)) { unsigned NOps = MD->getNumOperands(); // Only emit misexpect diagnostics if at least 2 branch weights are present. // Less than 2 branch weights means that the profiling metadata is: // 1) incorrect/corrupted // 2) not branch weight metadata // 3) completely deterministic // In these cases we should not emit any diagnostic related to misexpect. if (NOps < 3) return; // Operand 0 is a string tag "branch_weights" if (MDString *Tag = cast(MD->getOperand(0))) { if (Tag->getString().equals("branch_weights")) { SmallVector RealWeights(NOps - 1); for (unsigned i = 1; i < NOps; i++) { ConstantInt *Value = mdconst::dyn_extract(MD->getOperand(i)); RealWeights[i - 1] = Value->getZExtValue(); } verifyMisExpect(&I, RealWeights, I.getContext()); } } } } } // namespace misexpect } // namespace llvm #undef DEBUG_TYPE