Path: blob/main/contrib/llvm-project/llvm/lib/Transforms/IPO/SampleContextTracker.cpp
35266 views
//===- SampleContextTracker.cpp - Context-sensitive Profile Tracker -------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file implements the SampleContextTracker used by CSSPGO.9//10//===----------------------------------------------------------------------===//1112#include "llvm/Transforms/IPO/SampleContextTracker.h"13#include "llvm/ADT/StringRef.h"14#include "llvm/IR/DebugInfoMetadata.h"15#include "llvm/IR/InstrTypes.h"16#include "llvm/IR/Instruction.h"17#include "llvm/ProfileData/SampleProf.h"18#include <map>19#include <queue>20#include <vector>2122using namespace llvm;23using namespace sampleprof;2425#define DEBUG_TYPE "sample-context-tracker"2627namespace llvm {2829ContextTrieNode *ContextTrieNode::getChildContext(const LineLocation &CallSite,30FunctionId CalleeName) {31if (CalleeName.empty())32return getHottestChildContext(CallSite);3334uint64_t Hash = FunctionSamples::getCallSiteHash(CalleeName, CallSite);35auto It = AllChildContext.find(Hash);36if (It != AllChildContext.end())37return &It->second;38return nullptr;39}4041ContextTrieNode *42ContextTrieNode::getHottestChildContext(const LineLocation &CallSite) {43// CSFDO-TODO: This could be slow, change AllChildContext so we can44// do point look up for child node by call site alone.45// Retrieve the child node with max count for indirect call46ContextTrieNode *ChildNodeRet = nullptr;47uint64_t MaxCalleeSamples = 0;48for (auto &It : AllChildContext) {49ContextTrieNode &ChildNode = It.second;50if (ChildNode.CallSiteLoc != CallSite)51continue;52FunctionSamples *Samples = ChildNode.getFunctionSamples();53if (!Samples)54continue;55if (Samples->getTotalSamples() > MaxCalleeSamples) {56ChildNodeRet = &ChildNode;57MaxCalleeSamples = Samples->getTotalSamples();58}59}6061return ChildNodeRet;62}6364ContextTrieNode &65SampleContextTracker::moveContextSamples(ContextTrieNode &ToNodeParent,66const LineLocation &CallSite,67ContextTrieNode &&NodeToMove) {68uint64_t Hash =69FunctionSamples::getCallSiteHash(NodeToMove.getFuncName(), CallSite);70std::map<uint64_t, ContextTrieNode> &AllChildContext =71ToNodeParent.getAllChildContext();72assert(!AllChildContext.count(Hash) && "Node to remove must exist");73AllChildContext[Hash] = NodeToMove;74ContextTrieNode &NewNode = AllChildContext[Hash];75NewNode.setCallSiteLoc(CallSite);7677// Walk through nodes in the moved the subtree, and update78// FunctionSamples' context as for the context promotion.79// We also need to set new parant link for all children.80std::queue<ContextTrieNode *> NodeToUpdate;81NewNode.setParentContext(&ToNodeParent);82NodeToUpdate.push(&NewNode);8384while (!NodeToUpdate.empty()) {85ContextTrieNode *Node = NodeToUpdate.front();86NodeToUpdate.pop();87FunctionSamples *FSamples = Node->getFunctionSamples();8889if (FSamples) {90setContextNode(FSamples, Node);91FSamples->getContext().setState(SyntheticContext);92}9394for (auto &It : Node->getAllChildContext()) {95ContextTrieNode *ChildNode = &It.second;96ChildNode->setParentContext(Node);97NodeToUpdate.push(ChildNode);98}99}100101return NewNode;102}103104void ContextTrieNode::removeChildContext(const LineLocation &CallSite,105FunctionId CalleeName) {106uint64_t Hash = FunctionSamples::getCallSiteHash(CalleeName, CallSite);107// Note this essentially calls dtor and destroys that child context108AllChildContext.erase(Hash);109}110111std::map<uint64_t, ContextTrieNode> &ContextTrieNode::getAllChildContext() {112return AllChildContext;113}114115FunctionId ContextTrieNode::getFuncName() const { return FuncName; }116117FunctionSamples *ContextTrieNode::getFunctionSamples() const {118return FuncSamples;119}120121void ContextTrieNode::setFunctionSamples(FunctionSamples *FSamples) {122FuncSamples = FSamples;123}124125std::optional<uint32_t> ContextTrieNode::getFunctionSize() const {126return FuncSize;127}128129void ContextTrieNode::addFunctionSize(uint32_t FSize) {130if (!FuncSize)131FuncSize = 0;132133FuncSize = *FuncSize + FSize;134}135136LineLocation ContextTrieNode::getCallSiteLoc() const { return CallSiteLoc; }137138ContextTrieNode *ContextTrieNode::getParentContext() const {139return ParentContext;140}141142void ContextTrieNode::setParentContext(ContextTrieNode *Parent) {143ParentContext = Parent;144}145146void ContextTrieNode::setCallSiteLoc(const LineLocation &Loc) {147CallSiteLoc = Loc;148}149150void ContextTrieNode::dumpNode() {151dbgs() << "Node: " << FuncName << "\n"152<< " Callsite: " << CallSiteLoc << "\n"153<< " Size: " << FuncSize << "\n"154<< " Children:\n";155156for (auto &It : AllChildContext) {157dbgs() << " Node: " << It.second.getFuncName() << "\n";158}159}160161void ContextTrieNode::dumpTree() {162dbgs() << "Context Profile Tree:\n";163std::queue<ContextTrieNode *> NodeQueue;164NodeQueue.push(this);165166while (!NodeQueue.empty()) {167ContextTrieNode *Node = NodeQueue.front();168NodeQueue.pop();169Node->dumpNode();170171for (auto &It : Node->getAllChildContext()) {172ContextTrieNode *ChildNode = &It.second;173NodeQueue.push(ChildNode);174}175}176}177178ContextTrieNode *ContextTrieNode::getOrCreateChildContext(179const LineLocation &CallSite, FunctionId CalleeName, bool AllowCreate) {180uint64_t Hash = FunctionSamples::getCallSiteHash(CalleeName, CallSite);181auto It = AllChildContext.find(Hash);182if (It != AllChildContext.end()) {183assert(It->second.getFuncName() == CalleeName &&184"Hash collision for child context node");185return &It->second;186}187188if (!AllowCreate)189return nullptr;190191AllChildContext[Hash] = ContextTrieNode(this, CalleeName, nullptr, CallSite);192return &AllChildContext[Hash];193}194195// Profiler tracker than manages profiles and its associated context196SampleContextTracker::SampleContextTracker(197SampleProfileMap &Profiles,198const DenseMap<uint64_t, StringRef> *GUIDToFuncNameMap)199: GUIDToFuncNameMap(GUIDToFuncNameMap) {200for (auto &FuncSample : Profiles) {201FunctionSamples *FSamples = &FuncSample.second;202SampleContext Context = FuncSample.second.getContext();203LLVM_DEBUG(dbgs() << "Tracking Context for function: " << Context.toString()204<< "\n");205ContextTrieNode *NewNode = getOrCreateContextPath(Context, true);206assert(!NewNode->getFunctionSamples() &&207"New node can't have sample profile");208NewNode->setFunctionSamples(FSamples);209}210populateFuncToCtxtMap();211}212213void SampleContextTracker::populateFuncToCtxtMap() {214for (auto *Node : *this) {215FunctionSamples *FSamples = Node->getFunctionSamples();216if (FSamples) {217FSamples->getContext().setState(RawContext);218setContextNode(FSamples, Node);219FuncToCtxtProfiles[Node->getFuncName()].push_back(FSamples);220}221}222}223224FunctionSamples *225SampleContextTracker::getCalleeContextSamplesFor(const CallBase &Inst,226StringRef CalleeName) {227LLVM_DEBUG(dbgs() << "Getting callee context for instr: " << Inst << "\n");228DILocation *DIL = Inst.getDebugLoc();229if (!DIL)230return nullptr;231232CalleeName = FunctionSamples::getCanonicalFnName(CalleeName);233234FunctionId FName = getRepInFormat(CalleeName);235236// For indirect call, CalleeName will be empty, in which case the context237// profile for callee with largest total samples will be returned.238ContextTrieNode *CalleeContext = getCalleeContextFor(DIL, FName);239if (CalleeContext) {240FunctionSamples *FSamples = CalleeContext->getFunctionSamples();241LLVM_DEBUG(if (FSamples) {242dbgs() << " Callee context found: " << getContextString(CalleeContext)243<< "\n";244});245return FSamples;246}247248return nullptr;249}250251std::vector<const FunctionSamples *>252SampleContextTracker::getIndirectCalleeContextSamplesFor(253const DILocation *DIL) {254std::vector<const FunctionSamples *> R;255if (!DIL)256return R;257258ContextTrieNode *CallerNode = getContextFor(DIL);259LineLocation CallSite = FunctionSamples::getCallSiteIdentifier(DIL);260for (auto &It : CallerNode->getAllChildContext()) {261ContextTrieNode &ChildNode = It.second;262if (ChildNode.getCallSiteLoc() != CallSite)263continue;264if (FunctionSamples *CalleeSamples = ChildNode.getFunctionSamples())265R.push_back(CalleeSamples);266}267268return R;269}270271FunctionSamples *272SampleContextTracker::getContextSamplesFor(const DILocation *DIL) {273assert(DIL && "Expect non-null location");274275ContextTrieNode *ContextNode = getContextFor(DIL);276if (!ContextNode)277return nullptr;278279// We may have inlined callees during pre-LTO compilation, in which case280// we need to rely on the inline stack from !dbg to mark context profile281// as inlined, instead of `MarkContextSamplesInlined` during inlining.282// Sample profile loader walks through all instructions to get profile,283// which calls this function. So once that is done, all previously inlined284// context profile should be marked properly.285FunctionSamples *Samples = ContextNode->getFunctionSamples();286if (Samples && ContextNode->getParentContext() != &RootContext)287Samples->getContext().setState(InlinedContext);288289return Samples;290}291292FunctionSamples *293SampleContextTracker::getContextSamplesFor(const SampleContext &Context) {294ContextTrieNode *Node = getContextFor(Context);295if (!Node)296return nullptr;297298return Node->getFunctionSamples();299}300301SampleContextTracker::ContextSamplesTy &302SampleContextTracker::getAllContextSamplesFor(const Function &Func) {303StringRef CanonName = FunctionSamples::getCanonicalFnName(Func);304return FuncToCtxtProfiles[getRepInFormat(CanonName)];305}306307SampleContextTracker::ContextSamplesTy &308SampleContextTracker::getAllContextSamplesFor(StringRef Name) {309return FuncToCtxtProfiles[getRepInFormat(Name)];310}311312FunctionSamples *SampleContextTracker::getBaseSamplesFor(const Function &Func,313bool MergeContext) {314StringRef CanonName = FunctionSamples::getCanonicalFnName(Func);315return getBaseSamplesFor(getRepInFormat(CanonName), MergeContext);316}317318FunctionSamples *SampleContextTracker::getBaseSamplesFor(FunctionId Name,319bool MergeContext) {320LLVM_DEBUG(dbgs() << "Getting base profile for function: " << Name << "\n");321322// Base profile is top-level node (child of root node), so try to retrieve323// existing top-level node for given function first. If it exists, it could be324// that we've merged base profile before, or there's actually context-less325// profile from the input (e.g. due to unreliable stack walking).326ContextTrieNode *Node = getTopLevelContextNode(Name);327if (MergeContext) {328LLVM_DEBUG(dbgs() << " Merging context profile into base profile: " << Name329<< "\n");330331// We have profile for function under different contexts,332// create synthetic base profile and merge context profiles333// into base profile.334for (auto *CSamples : FuncToCtxtProfiles[Name]) {335SampleContext &Context = CSamples->getContext();336// Skip inlined context profile and also don't re-merge any context337if (Context.hasState(InlinedContext) || Context.hasState(MergedContext))338continue;339340ContextTrieNode *FromNode = getContextNodeForProfile(CSamples);341if (FromNode == Node)342continue;343344ContextTrieNode &ToNode = promoteMergeContextSamplesTree(*FromNode);345assert((!Node || Node == &ToNode) && "Expect only one base profile");346Node = &ToNode;347}348}349350// Still no profile even after merge/promotion (if allowed)351if (!Node)352return nullptr;353354return Node->getFunctionSamples();355}356357void SampleContextTracker::markContextSamplesInlined(358const FunctionSamples *InlinedSamples) {359assert(InlinedSamples && "Expect non-null inlined samples");360LLVM_DEBUG(dbgs() << "Marking context profile as inlined: "361<< getContextString(*InlinedSamples) << "\n");362InlinedSamples->getContext().setState(InlinedContext);363}364365ContextTrieNode &SampleContextTracker::getRootContext() { return RootContext; }366367void SampleContextTracker::promoteMergeContextSamplesTree(368const Instruction &Inst, FunctionId CalleeName) {369LLVM_DEBUG(dbgs() << "Promoting and merging context tree for instr: \n"370<< Inst << "\n");371// Get the caller context for the call instruction, we don't use callee372// name from call because there can be context from indirect calls too.373DILocation *DIL = Inst.getDebugLoc();374ContextTrieNode *CallerNode = getContextFor(DIL);375if (!CallerNode)376return;377378// Get the context that needs to be promoted379LineLocation CallSite = FunctionSamples::getCallSiteIdentifier(DIL);380// For indirect call, CalleeName will be empty, in which case we need to381// promote all non-inlined child context profiles.382if (CalleeName.empty()) {383for (auto &It : CallerNode->getAllChildContext()) {384ContextTrieNode *NodeToPromo = &It.second;385if (CallSite != NodeToPromo->getCallSiteLoc())386continue;387FunctionSamples *FromSamples = NodeToPromo->getFunctionSamples();388if (FromSamples && FromSamples->getContext().hasState(InlinedContext))389continue;390promoteMergeContextSamplesTree(*NodeToPromo);391}392return;393}394395// Get the context for the given callee that needs to be promoted396ContextTrieNode *NodeToPromo =397CallerNode->getChildContext(CallSite, CalleeName);398if (!NodeToPromo)399return;400401promoteMergeContextSamplesTree(*NodeToPromo);402}403404ContextTrieNode &SampleContextTracker::promoteMergeContextSamplesTree(405ContextTrieNode &NodeToPromo) {406// Promote the input node to be directly under root. This can happen407// when we decided to not inline a function under context represented408// by the input node. The promote and merge is then needed to reflect409// the context profile in the base (context-less) profile.410FunctionSamples *FromSamples = NodeToPromo.getFunctionSamples();411assert(FromSamples && "Shouldn't promote a context without profile");412(void)FromSamples; // Unused in release build.413414LLVM_DEBUG(dbgs() << " Found context tree root to promote: "415<< getContextString(&NodeToPromo) << "\n");416417assert(!FromSamples->getContext().hasState(InlinedContext) &&418"Shouldn't promote inlined context profile");419return promoteMergeContextSamplesTree(NodeToPromo, RootContext);420}421422#ifndef NDEBUG423std::string424SampleContextTracker::getContextString(const FunctionSamples &FSamples) const {425return getContextString(getContextNodeForProfile(&FSamples));426}427428std::string429SampleContextTracker::getContextString(ContextTrieNode *Node) const {430SampleContextFrameVector Res;431if (Node == &RootContext)432return std::string();433Res.emplace_back(Node->getFuncName(), LineLocation(0, 0));434435ContextTrieNode *PreNode = Node;436Node = Node->getParentContext();437while (Node && Node != &RootContext) {438Res.emplace_back(Node->getFuncName(), PreNode->getCallSiteLoc());439PreNode = Node;440Node = Node->getParentContext();441}442443std::reverse(Res.begin(), Res.end());444445return SampleContext::getContextString(Res);446}447#endif448449void SampleContextTracker::dump() { RootContext.dumpTree(); }450451StringRef SampleContextTracker::getFuncNameFor(ContextTrieNode *Node) const {452if (!FunctionSamples::UseMD5)453return Node->getFuncName().stringRef();454assert(GUIDToFuncNameMap && "GUIDToFuncNameMap needs to be populated first");455return GUIDToFuncNameMap->lookup(Node->getFuncName().getHashCode());456}457458ContextTrieNode *459SampleContextTracker::getContextFor(const SampleContext &Context) {460return getOrCreateContextPath(Context, false);461}462463ContextTrieNode *464SampleContextTracker::getCalleeContextFor(const DILocation *DIL,465FunctionId CalleeName) {466assert(DIL && "Expect non-null location");467468ContextTrieNode *CallContext = getContextFor(DIL);469if (!CallContext)470return nullptr;471472// When CalleeName is empty, the child context profile with max473// total samples will be returned.474return CallContext->getChildContext(475FunctionSamples::getCallSiteIdentifier(DIL), CalleeName);476}477478ContextTrieNode *SampleContextTracker::getContextFor(const DILocation *DIL) {479assert(DIL && "Expect non-null location");480SmallVector<std::pair<LineLocation, FunctionId>, 10> S;481482// Use C++ linkage name if possible.483const DILocation *PrevDIL = DIL;484for (DIL = DIL->getInlinedAt(); DIL; DIL = DIL->getInlinedAt()) {485StringRef Name = PrevDIL->getScope()->getSubprogram()->getLinkageName();486if (Name.empty())487Name = PrevDIL->getScope()->getSubprogram()->getName();488S.push_back(489std::make_pair(FunctionSamples::getCallSiteIdentifier(DIL),490getRepInFormat(Name)));491PrevDIL = DIL;492}493494// Push root node, note that root node like main may only495// a name, but not linkage name.496StringRef RootName = PrevDIL->getScope()->getSubprogram()->getLinkageName();497if (RootName.empty())498RootName = PrevDIL->getScope()->getSubprogram()->getName();499S.push_back(std::make_pair(LineLocation(0, 0),500getRepInFormat(RootName)));501502ContextTrieNode *ContextNode = &RootContext;503int I = S.size();504while (--I >= 0 && ContextNode) {505LineLocation &CallSite = S[I].first;506FunctionId CalleeName = S[I].second;507ContextNode = ContextNode->getChildContext(CallSite, CalleeName);508}509510if (I < 0)511return ContextNode;512513return nullptr;514}515516ContextTrieNode *517SampleContextTracker::getOrCreateContextPath(const SampleContext &Context,518bool AllowCreate) {519ContextTrieNode *ContextNode = &RootContext;520LineLocation CallSiteLoc(0, 0);521522for (const auto &Callsite : Context.getContextFrames()) {523// Create child node at parent line/disc location524if (AllowCreate) {525ContextNode =526ContextNode->getOrCreateChildContext(CallSiteLoc, Callsite.Func);527} else {528ContextNode =529ContextNode->getChildContext(CallSiteLoc, Callsite.Func);530}531CallSiteLoc = Callsite.Location;532}533534assert((!AllowCreate || ContextNode) &&535"Node must exist if creation is allowed");536return ContextNode;537}538539ContextTrieNode *540SampleContextTracker::getTopLevelContextNode(FunctionId FName) {541assert(!FName.empty() && "Top level node query must provide valid name");542return RootContext.getChildContext(LineLocation(0, 0), FName);543}544545ContextTrieNode &546SampleContextTracker::addTopLevelContextNode(FunctionId FName) {547assert(!getTopLevelContextNode(FName) && "Node to add must not exist");548return *RootContext.getOrCreateChildContext(LineLocation(0, 0), FName);549}550551void SampleContextTracker::mergeContextNode(ContextTrieNode &FromNode,552ContextTrieNode &ToNode) {553FunctionSamples *FromSamples = FromNode.getFunctionSamples();554FunctionSamples *ToSamples = ToNode.getFunctionSamples();555if (FromSamples && ToSamples) {556// Merge/duplicate FromSamples into ToSamples557ToSamples->merge(*FromSamples);558ToSamples->getContext().setState(SyntheticContext);559FromSamples->getContext().setState(MergedContext);560if (FromSamples->getContext().hasAttribute(ContextShouldBeInlined))561ToSamples->getContext().setAttribute(ContextShouldBeInlined);562} else if (FromSamples) {563// Transfer FromSamples from FromNode to ToNode564ToNode.setFunctionSamples(FromSamples);565setContextNode(FromSamples, &ToNode);566FromSamples->getContext().setState(SyntheticContext);567}568}569570ContextTrieNode &SampleContextTracker::promoteMergeContextSamplesTree(571ContextTrieNode &FromNode, ContextTrieNode &ToNodeParent) {572573// Ignore call site location if destination is top level under root574LineLocation NewCallSiteLoc = LineLocation(0, 0);575LineLocation OldCallSiteLoc = FromNode.getCallSiteLoc();576ContextTrieNode &FromNodeParent = *FromNode.getParentContext();577ContextTrieNode *ToNode = nullptr;578bool MoveToRoot = (&ToNodeParent == &RootContext);579if (!MoveToRoot) {580NewCallSiteLoc = OldCallSiteLoc;581}582583// Locate destination node, create/move if not existing584ToNode = ToNodeParent.getChildContext(NewCallSiteLoc, FromNode.getFuncName());585if (!ToNode) {586// Do not delete node to move from its parent here because587// caller is iterating over children of that parent node.588ToNode =589&moveContextSamples(ToNodeParent, NewCallSiteLoc, std::move(FromNode));590LLVM_DEBUG({591dbgs() << " Context promoted and merged to: " << getContextString(ToNode)592<< "\n";593});594} else {595// Destination node exists, merge samples for the context tree596mergeContextNode(FromNode, *ToNode);597LLVM_DEBUG({598if (ToNode->getFunctionSamples())599dbgs() << " Context promoted and merged to: "600<< getContextString(ToNode) << "\n";601});602603// Recursively promote and merge children604for (auto &It : FromNode.getAllChildContext()) {605ContextTrieNode &FromChildNode = It.second;606promoteMergeContextSamplesTree(FromChildNode, *ToNode);607}608609// Remove children once they're all merged610FromNode.getAllChildContext().clear();611}612613// For root of subtree, remove itself from old parent too614if (MoveToRoot)615FromNodeParent.removeChildContext(OldCallSiteLoc, ToNode->getFuncName());616617return *ToNode;618}619620void SampleContextTracker::createContextLessProfileMap(621SampleProfileMap &ContextLessProfiles) {622for (auto *Node : *this) {623FunctionSamples *FProfile = Node->getFunctionSamples();624// Profile's context can be empty, use ContextNode's func name.625if (FProfile)626ContextLessProfiles.create(Node->getFuncName()).merge(*FProfile);627}628}629} // namespace llvm630631632