| //===- IndirectCallPromotion.cpp - Optimizations based on value profiling -===// |
| // |
| // 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 file implements the transformation that promotes indirect calls to |
| // conditional direct calls when the indirect-call value profile metadata is |
| // available. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/Statistic.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Analysis/IndirectCallPromotionAnalysis.h" |
| #include "llvm/Analysis/IndirectCallVisitor.h" |
| #include "llvm/Analysis/OptimizationRemarkEmitter.h" |
| #include "llvm/Analysis/ProfileSummaryInfo.h" |
| #include "llvm/IR/Attributes.h" |
| #include "llvm/IR/BasicBlock.h" |
| #include "llvm/IR/CallSite.h" |
| #include "llvm/IR/DerivedTypes.h" |
| #include "llvm/IR/DiagnosticInfo.h" |
| #include "llvm/IR/Function.h" |
| #include "llvm/IR/IRBuilder.h" |
| #include "llvm/IR/InstrTypes.h" |
| #include "llvm/IR/Instruction.h" |
| #include "llvm/IR/Instructions.h" |
| #include "llvm/IR/LLVMContext.h" |
| #include "llvm/IR/MDBuilder.h" |
| #include "llvm/IR/PassManager.h" |
| #include "llvm/IR/Type.h" |
| #include "llvm/IR/Value.h" |
| #include "llvm/InitializePasses.h" |
| #include "llvm/Pass.h" |
| #include "llvm/ProfileData/InstrProf.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include "llvm/Transforms/Instrumentation.h" |
| #include "llvm/Transforms/Instrumentation/PGOInstrumentation.h" |
| #include "llvm/Transforms/Utils/BasicBlockUtils.h" |
| #include "llvm/Transforms/Utils/CallPromotionUtils.h" |
| #include <cassert> |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| using namespace llvm; |
| |
| #define DEBUG_TYPE "pgo-icall-prom" |
| |
| STATISTIC(NumOfPGOICallPromotion, "Number of indirect call promotions."); |
| STATISTIC(NumOfPGOICallsites, "Number of indirect call candidate sites."); |
| |
| // Command line option to disable indirect-call promotion with the default as |
| // false. This is for debug purpose. |
| static cl::opt<bool> DisableICP("disable-icp", cl::init(false), cl::Hidden, |
| cl::desc("Disable indirect call promotion")); |
| |
| // Set the cutoff value for the promotion. If the value is other than 0, we |
| // stop the transformation once the total number of promotions equals the cutoff |
| // value. |
| // For debug use only. |
| static cl::opt<unsigned> |
| ICPCutOff("icp-cutoff", cl::init(0), cl::Hidden, cl::ZeroOrMore, |
| cl::desc("Max number of promotions for this compilation")); |
| |
| // If ICPCSSkip is non zero, the first ICPCSSkip callsites will be skipped. |
| // For debug use only. |
| static cl::opt<unsigned> |
| ICPCSSkip("icp-csskip", cl::init(0), cl::Hidden, cl::ZeroOrMore, |
| cl::desc("Skip Callsite up to this number for this compilation")); |
| |
| // Set if the pass is called in LTO optimization. The difference for LTO mode |
| // is the pass won't prefix the source module name to the internal linkage |
| // symbols. |
| static cl::opt<bool> ICPLTOMode("icp-lto", cl::init(false), cl::Hidden, |
| cl::desc("Run indirect-call promotion in LTO " |
| "mode")); |
| |
| // Set if the pass is called in SamplePGO mode. The difference for SamplePGO |
| // mode is it will add prof metadatato the created direct call. |
| static cl::opt<bool> |
| ICPSamplePGOMode("icp-samplepgo", cl::init(false), cl::Hidden, |
| cl::desc("Run indirect-call promotion in SamplePGO mode")); |
| |
| // If the option is set to true, only call instructions will be considered for |
| // transformation -- invoke instructions will be ignored. |
| static cl::opt<bool> |
| ICPCallOnly("icp-call-only", cl::init(false), cl::Hidden, |
| cl::desc("Run indirect-call promotion for call instructions " |
| "only")); |
| |
| // If the option is set to true, only invoke instructions will be considered for |
| // transformation -- call instructions will be ignored. |
| static cl::opt<bool> ICPInvokeOnly("icp-invoke-only", cl::init(false), |
| cl::Hidden, |
| cl::desc("Run indirect-call promotion for " |
| "invoke instruction only")); |
| |
| // Dump the function level IR if the transformation happened in this |
| // function. For debug use only. |
| static cl::opt<bool> |
| ICPDUMPAFTER("icp-dumpafter", cl::init(false), cl::Hidden, |
| cl::desc("Dump IR after transformation happens")); |
| |
| namespace { |
| |
| class PGOIndirectCallPromotionLegacyPass : public ModulePass { |
| public: |
| static char ID; |
| |
| PGOIndirectCallPromotionLegacyPass(bool InLTO = false, bool SamplePGO = false) |
| : ModulePass(ID), InLTO(InLTO), SamplePGO(SamplePGO) { |
| initializePGOIndirectCallPromotionLegacyPassPass( |
| *PassRegistry::getPassRegistry()); |
| } |
| |
| void getAnalysisUsage(AnalysisUsage &AU) const override { |
| AU.addRequired<ProfileSummaryInfoWrapperPass>(); |
| } |
| |
| StringRef getPassName() const override { return "PGOIndirectCallPromotion"; } |
| |
| private: |
| bool runOnModule(Module &M) override; |
| |
| // If this pass is called in LTO. We need to special handling the PGOFuncName |
| // for the static variables due to LTO's internalization. |
| bool InLTO; |
| |
| // If this pass is called in SamplePGO. We need to add the prof metadata to |
| // the promoted direct call. |
| bool SamplePGO; |
| }; |
| |
| } // end anonymous namespace |
| |
| char PGOIndirectCallPromotionLegacyPass::ID = 0; |
| |
| INITIALIZE_PASS_BEGIN(PGOIndirectCallPromotionLegacyPass, "pgo-icall-prom", |
| "Use PGO instrumentation profile to promote indirect " |
| "calls to direct calls.", |
| false, false) |
| INITIALIZE_PASS_DEPENDENCY(ProfileSummaryInfoWrapperPass) |
| INITIALIZE_PASS_END(PGOIndirectCallPromotionLegacyPass, "pgo-icall-prom", |
| "Use PGO instrumentation profile to promote indirect " |
| "calls to direct calls.", |
| false, false) |
| |
| ModulePass *llvm::createPGOIndirectCallPromotionLegacyPass(bool InLTO, |
| bool SamplePGO) { |
| return new PGOIndirectCallPromotionLegacyPass(InLTO, SamplePGO); |
| } |
| |
| namespace { |
| |
| // The class for main data structure to promote indirect calls to conditional |
| // direct calls. |
| class ICallPromotionFunc { |
| private: |
| Function &F; |
| Module *M; |
| |
| // Symtab that maps indirect call profile values to function names and |
| // defines. |
| InstrProfSymtab *Symtab; |
| |
| bool SamplePGO; |
| |
| OptimizationRemarkEmitter &ORE; |
| |
| // A struct that records the direct target and it's call count. |
| struct PromotionCandidate { |
| Function *TargetFunction; |
| uint64_t Count; |
| |
| PromotionCandidate(Function *F, uint64_t C) : TargetFunction(F), Count(C) {} |
| }; |
| |
| // Check if the indirect-call call site should be promoted. Return the number |
| // of promotions. Inst is the candidate indirect call, ValueDataRef |
| // contains the array of value profile data for profiled targets, |
| // TotalCount is the total profiled count of call executions, and |
| // NumCandidates is the number of candidate entries in ValueDataRef. |
| std::vector<PromotionCandidate> getPromotionCandidatesForCallSite( |
| Instruction *Inst, const ArrayRef<InstrProfValueData> &ValueDataRef, |
| uint64_t TotalCount, uint32_t NumCandidates); |
| |
| // Promote a list of targets for one indirect-call callsite. Return |
| // the number of promotions. |
| uint32_t tryToPromote(Instruction *Inst, |
| const std::vector<PromotionCandidate> &Candidates, |
| uint64_t &TotalCount); |
| |
| public: |
| ICallPromotionFunc(Function &Func, Module *Modu, InstrProfSymtab *Symtab, |
| bool SamplePGO, OptimizationRemarkEmitter &ORE) |
| : F(Func), M(Modu), Symtab(Symtab), SamplePGO(SamplePGO), ORE(ORE) {} |
| ICallPromotionFunc(const ICallPromotionFunc &) = delete; |
| ICallPromotionFunc &operator=(const ICallPromotionFunc &) = delete; |
| |
| bool processFunction(ProfileSummaryInfo *PSI); |
| }; |
| |
| } // end anonymous namespace |
| |
| // Indirect-call promotion heuristic. The direct targets are sorted based on |
| // the count. Stop at the first target that is not promoted. |
| std::vector<ICallPromotionFunc::PromotionCandidate> |
| ICallPromotionFunc::getPromotionCandidatesForCallSite( |
| Instruction *Inst, const ArrayRef<InstrProfValueData> &ValueDataRef, |
| uint64_t TotalCount, uint32_t NumCandidates) { |
| std::vector<PromotionCandidate> Ret; |
| |
| LLVM_DEBUG(dbgs() << " \nWork on callsite #" << NumOfPGOICallsites << *Inst |
| << " Num_targets: " << ValueDataRef.size() |
| << " Num_candidates: " << NumCandidates << "\n"); |
| NumOfPGOICallsites++; |
| if (ICPCSSkip != 0 && NumOfPGOICallsites <= ICPCSSkip) { |
| LLVM_DEBUG(dbgs() << " Skip: User options.\n"); |
| return Ret; |
| } |
| |
| for (uint32_t I = 0; I < NumCandidates; I++) { |
| uint64_t Count = ValueDataRef[I].Count; |
| assert(Count <= TotalCount); |
| uint64_t Target = ValueDataRef[I].Value; |
| LLVM_DEBUG(dbgs() << " Candidate " << I << " Count=" << Count |
| << " Target_func: " << Target << "\n"); |
| |
| if (ICPInvokeOnly && isa<CallInst>(Inst)) { |
| LLVM_DEBUG(dbgs() << " Not promote: User options.\n"); |
| ORE.emit([&]() { |
| return OptimizationRemarkMissed(DEBUG_TYPE, "UserOptions", Inst) |
| << " Not promote: User options"; |
| }); |
| break; |
| } |
| if (ICPCallOnly && isa<InvokeInst>(Inst)) { |
| LLVM_DEBUG(dbgs() << " Not promote: User option.\n"); |
| ORE.emit([&]() { |
| return OptimizationRemarkMissed(DEBUG_TYPE, "UserOptions", Inst) |
| << " Not promote: User options"; |
| }); |
| break; |
| } |
| if (ICPCutOff != 0 && NumOfPGOICallPromotion >= ICPCutOff) { |
| LLVM_DEBUG(dbgs() << " Not promote: Cutoff reached.\n"); |
| ORE.emit([&]() { |
| return OptimizationRemarkMissed(DEBUG_TYPE, "CutOffReached", Inst) |
| << " Not promote: Cutoff reached"; |
| }); |
| break; |
| } |
| |
| Function *TargetFunction = Symtab->getFunction(Target); |
| if (TargetFunction == nullptr) { |
| LLVM_DEBUG(dbgs() << " Not promote: Cannot find the target\n"); |
| ORE.emit([&]() { |
| return OptimizationRemarkMissed(DEBUG_TYPE, "UnableToFindTarget", Inst) |
| << "Cannot promote indirect call: target with md5sum " |
| << ore::NV("target md5sum", Target) << " not found"; |
| }); |
| break; |
| } |
| |
| const char *Reason = nullptr; |
| if (!isLegalToPromote(CallSite(Inst), TargetFunction, &Reason)) { |
| using namespace ore; |
| |
| ORE.emit([&]() { |
| return OptimizationRemarkMissed(DEBUG_TYPE, "UnableToPromote", Inst) |
| << "Cannot promote indirect call to " |
| << NV("TargetFunction", TargetFunction) << " with count of " |
| << NV("Count", Count) << ": " << Reason; |
| }); |
| break; |
| } |
| |
| Ret.push_back(PromotionCandidate(TargetFunction, Count)); |
| TotalCount -= Count; |
| } |
| return Ret; |
| } |
| |
| Instruction *llvm::pgo::promoteIndirectCall(Instruction *Inst, |
| Function *DirectCallee, |
| uint64_t Count, uint64_t TotalCount, |
| bool AttachProfToDirectCall, |
| OptimizationRemarkEmitter *ORE) { |
| |
| uint64_t ElseCount = TotalCount - Count; |
| uint64_t MaxCount = (Count >= ElseCount ? Count : ElseCount); |
| uint64_t Scale = calculateCountScale(MaxCount); |
| MDBuilder MDB(Inst->getContext()); |
| MDNode *BranchWeights = MDB.createBranchWeights( |
| scaleBranchCount(Count, Scale), scaleBranchCount(ElseCount, Scale)); |
| |
| Instruction *NewInst = |
| promoteCallWithIfThenElse(CallSite(Inst), DirectCallee, BranchWeights); |
| |
| if (AttachProfToDirectCall) { |
| MDBuilder MDB(NewInst->getContext()); |
| NewInst->setMetadata( |
| LLVMContext::MD_prof, |
| MDB.createBranchWeights({static_cast<uint32_t>(Count)})); |
| } |
| |
| using namespace ore; |
| |
| if (ORE) |
| ORE->emit([&]() { |
| return OptimizationRemark(DEBUG_TYPE, "Promoted", Inst) |
| << "Promote indirect call to " << NV("DirectCallee", DirectCallee) |
| << " with count " << NV("Count", Count) << " out of " |
| << NV("TotalCount", TotalCount); |
| }); |
| return NewInst; |
| } |
| |
| // Promote indirect-call to conditional direct-call for one callsite. |
| uint32_t ICallPromotionFunc::tryToPromote( |
| Instruction *Inst, const std::vector<PromotionCandidate> &Candidates, |
| uint64_t &TotalCount) { |
| uint32_t NumPromoted = 0; |
| |
| for (auto &C : Candidates) { |
| uint64_t Count = C.Count; |
| pgo::promoteIndirectCall(Inst, C.TargetFunction, Count, TotalCount, |
| SamplePGO, &ORE); |
| assert(TotalCount >= Count); |
| TotalCount -= Count; |
| NumOfPGOICallPromotion++; |
| NumPromoted++; |
| } |
| return NumPromoted; |
| } |
| |
| // Traverse all the indirect-call callsite and get the value profile |
| // annotation to perform indirect-call promotion. |
| bool ICallPromotionFunc::processFunction(ProfileSummaryInfo *PSI) { |
| bool Changed = false; |
| ICallPromotionAnalysis ICallAnalysis; |
| for (auto &I : findIndirectCalls(F)) { |
| uint32_t NumVals, NumCandidates; |
| uint64_t TotalCount; |
| auto ICallProfDataRef = ICallAnalysis.getPromotionCandidatesForInstruction( |
| I, NumVals, TotalCount, NumCandidates); |
| if (!NumCandidates || |
| (PSI && PSI->hasProfileSummary() && !PSI->isHotCount(TotalCount))) |
| continue; |
| auto PromotionCandidates = getPromotionCandidatesForCallSite( |
| I, ICallProfDataRef, TotalCount, NumCandidates); |
| uint32_t NumPromoted = tryToPromote(I, PromotionCandidates, TotalCount); |
| if (NumPromoted == 0) |
| continue; |
| |
| Changed = true; |
| // Adjust the MD.prof metadata. First delete the old one. |
| I->setMetadata(LLVMContext::MD_prof, nullptr); |
| // If all promoted, we don't need the MD.prof metadata. |
| if (TotalCount == 0 || NumPromoted == NumVals) |
| continue; |
| // Otherwise we need update with the un-promoted records back. |
| annotateValueSite(*M, *I, ICallProfDataRef.slice(NumPromoted), TotalCount, |
| IPVK_IndirectCallTarget, NumCandidates); |
| } |
| return Changed; |
| } |
| |
| // A wrapper function that does the actual work. |
| static bool promoteIndirectCalls(Module &M, ProfileSummaryInfo *PSI, |
| bool InLTO, bool SamplePGO, |
| ModuleAnalysisManager *AM = nullptr) { |
| if (DisableICP) |
| return false; |
| InstrProfSymtab Symtab; |
| if (Error E = Symtab.create(M, InLTO)) { |
| std::string SymtabFailure = toString(std::move(E)); |
| LLVM_DEBUG(dbgs() << "Failed to create symtab: " << SymtabFailure << "\n"); |
| (void)SymtabFailure; |
| return false; |
| } |
| bool Changed = false; |
| for (auto &F : M) { |
| if (F.isDeclaration() || F.hasOptNone()) |
| continue; |
| |
| std::unique_ptr<OptimizationRemarkEmitter> OwnedORE; |
| OptimizationRemarkEmitter *ORE; |
| if (AM) { |
| auto &FAM = |
| AM->getResult<FunctionAnalysisManagerModuleProxy>(M).getManager(); |
| ORE = &FAM.getResult<OptimizationRemarkEmitterAnalysis>(F); |
| } else { |
| OwnedORE = std::make_unique<OptimizationRemarkEmitter>(&F); |
| ORE = OwnedORE.get(); |
| } |
| |
| ICallPromotionFunc ICallPromotion(F, &M, &Symtab, SamplePGO, *ORE); |
| bool FuncChanged = ICallPromotion.processFunction(PSI); |
| if (ICPDUMPAFTER && FuncChanged) { |
| LLVM_DEBUG(dbgs() << "\n== IR Dump After =="; F.print(dbgs())); |
| LLVM_DEBUG(dbgs() << "\n"); |
| } |
| Changed |= FuncChanged; |
| if (ICPCutOff != 0 && NumOfPGOICallPromotion >= ICPCutOff) { |
| LLVM_DEBUG(dbgs() << " Stop: Cutoff reached.\n"); |
| break; |
| } |
| } |
| return Changed; |
| } |
| |
| bool PGOIndirectCallPromotionLegacyPass::runOnModule(Module &M) { |
| ProfileSummaryInfo *PSI = |
| &getAnalysis<ProfileSummaryInfoWrapperPass>().getPSI(); |
| |
| // Command-line option has the priority for InLTO. |
| return promoteIndirectCalls(M, PSI, InLTO | ICPLTOMode, |
| SamplePGO | ICPSamplePGOMode); |
| } |
| |
| PreservedAnalyses PGOIndirectCallPromotion::run(Module &M, |
| ModuleAnalysisManager &AM) { |
| ProfileSummaryInfo *PSI = &AM.getResult<ProfileSummaryAnalysis>(M); |
| |
| if (!promoteIndirectCalls(M, PSI, InLTO | ICPLTOMode, |
| SamplePGO | ICPSamplePGOMode, &AM)) |
| return PreservedAnalyses::all(); |
| |
| return PreservedAnalyses::none(); |
| } |