| 1 | //===- SampleContextTracker.cpp - Context-sensitive Profile Tracker -------===// |
| 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 file implements the SampleContextTracker used by CSSPGO. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "llvm/Transforms/IPO/SampleContextTracker.h" |
| 14 | #include "llvm/ADT/StringRef.h" |
| 15 | #include "llvm/IR/DebugInfoMetadata.h" |
| 16 | #include "llvm/IR/InstrTypes.h" |
| 17 | #include "llvm/IR/Instruction.h" |
| 18 | #include "llvm/ProfileData/SampleProf.h" |
| 19 | #include <map> |
| 20 | #include <queue> |
| 21 | #include <vector> |
| 22 | |
| 23 | using namespace llvm; |
| 24 | using namespace sampleprof; |
| 25 | |
| 26 | #define DEBUG_TYPE "sample-context-tracker" |
| 27 | |
| 28 | namespace llvm { |
| 29 | |
| 30 | ContextTrieNode *ContextTrieNode::getChildContext(const LineLocation &CallSite, |
| 31 | FunctionId CalleeName) { |
| 32 | if (CalleeName.empty()) |
| 33 | return getHottestChildContext(CallSite); |
| 34 | |
| 35 | uint64_t Hash = FunctionSamples::getCallSiteHash(Callee: CalleeName, Callsite: CallSite); |
| 36 | auto It = AllChildContext.find(x: Hash); |
| 37 | if (It != AllChildContext.end()) |
| 38 | return &It->second; |
| 39 | return nullptr; |
| 40 | } |
| 41 | |
| 42 | ContextTrieNode * |
| 43 | ContextTrieNode::getHottestChildContext(const LineLocation &CallSite) { |
| 44 | // CSFDO-TODO: This could be slow, change AllChildContext so we can |
| 45 | // do point look up for child node by call site alone. |
| 46 | // Retrieve the child node with max count for indirect call |
| 47 | ContextTrieNode *ChildNodeRet = nullptr; |
| 48 | uint64_t MaxCalleeSamples = 0; |
| 49 | for (auto &It : AllChildContext) { |
| 50 | ContextTrieNode &ChildNode = It.second; |
| 51 | if (ChildNode.CallSiteLoc != CallSite) |
| 52 | continue; |
| 53 | FunctionSamples *Samples = ChildNode.getFunctionSamples(); |
| 54 | if (!Samples) |
| 55 | continue; |
| 56 | if (Samples->getTotalSamples() > MaxCalleeSamples) { |
| 57 | ChildNodeRet = &ChildNode; |
| 58 | MaxCalleeSamples = Samples->getTotalSamples(); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | return ChildNodeRet; |
| 63 | } |
| 64 | |
| 65 | ContextTrieNode & |
| 66 | SampleContextTracker::moveContextSamples(ContextTrieNode &ToNodeParent, |
| 67 | const LineLocation &CallSite, |
| 68 | ContextTrieNode &&NodeToMove) { |
| 69 | uint64_t Hash = |
| 70 | FunctionSamples::getCallSiteHash(Callee: NodeToMove.getFuncName(), Callsite: CallSite); |
| 71 | std::map<uint64_t, ContextTrieNode> &AllChildContext = |
| 72 | ToNodeParent.getAllChildContext(); |
| 73 | assert(!AllChildContext.count(Hash) && "Node to remove must exist" ); |
| 74 | ContextTrieNode &NewNode = AllChildContext[Hash]; |
| 75 | NewNode = NodeToMove; |
| 76 | NewNode.setCallSiteLoc(CallSite); |
| 77 | |
| 78 | // Walk through nodes in the moved the subtree, and update |
| 79 | // FunctionSamples' context as for the context promotion. |
| 80 | // We also need to set new parant link for all children. |
| 81 | std::queue<ContextTrieNode *> NodeToUpdate; |
| 82 | NewNode.setParentContext(&ToNodeParent); |
| 83 | NodeToUpdate.push(x: &NewNode); |
| 84 | |
| 85 | while (!NodeToUpdate.empty()) { |
| 86 | ContextTrieNode *Node = NodeToUpdate.front(); |
| 87 | NodeToUpdate.pop(); |
| 88 | FunctionSamples *FSamples = Node->getFunctionSamples(); |
| 89 | |
| 90 | if (FSamples) { |
| 91 | setContextNode(FSample: FSamples, Node); |
| 92 | FSamples->getContext().setState(SyntheticContext); |
| 93 | } |
| 94 | |
| 95 | for (auto &It : Node->getAllChildContext()) { |
| 96 | ContextTrieNode *ChildNode = &It.second; |
| 97 | ChildNode->setParentContext(Node); |
| 98 | NodeToUpdate.push(x: ChildNode); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | return NewNode; |
| 103 | } |
| 104 | |
| 105 | void ContextTrieNode::removeChildContext(const LineLocation &CallSite, |
| 106 | FunctionId CalleeName) { |
| 107 | uint64_t Hash = FunctionSamples::getCallSiteHash(Callee: CalleeName, Callsite: CallSite); |
| 108 | // Note this essentially calls dtor and destroys that child context |
| 109 | AllChildContext.erase(x: Hash); |
| 110 | } |
| 111 | |
| 112 | std::map<uint64_t, ContextTrieNode> &ContextTrieNode::getAllChildContext() { |
| 113 | return AllChildContext; |
| 114 | } |
| 115 | |
| 116 | FunctionId ContextTrieNode::getFuncName() const { return FuncName; } |
| 117 | |
| 118 | FunctionSamples *ContextTrieNode::getFunctionSamples() const { |
| 119 | return FuncSamples; |
| 120 | } |
| 121 | |
| 122 | void ContextTrieNode::setFunctionSamples(FunctionSamples *FSamples) { |
| 123 | FuncSamples = FSamples; |
| 124 | } |
| 125 | |
| 126 | std::optional<uint32_t> ContextTrieNode::getFunctionSize() const { |
| 127 | return FuncSize; |
| 128 | } |
| 129 | |
| 130 | void ContextTrieNode::addFunctionSize(uint32_t FSize) { |
| 131 | if (!FuncSize) |
| 132 | FuncSize = 0; |
| 133 | |
| 134 | FuncSize = *FuncSize + FSize; |
| 135 | } |
| 136 | |
| 137 | LineLocation ContextTrieNode::getCallSiteLoc() const { return CallSiteLoc; } |
| 138 | |
| 139 | ContextTrieNode *ContextTrieNode::getParentContext() const { |
| 140 | return ParentContext; |
| 141 | } |
| 142 | |
| 143 | void ContextTrieNode::setParentContext(ContextTrieNode *Parent) { |
| 144 | ParentContext = Parent; |
| 145 | } |
| 146 | |
| 147 | void ContextTrieNode::setCallSiteLoc(const LineLocation &Loc) { |
| 148 | CallSiteLoc = Loc; |
| 149 | } |
| 150 | |
| 151 | void ContextTrieNode::dumpNode() { |
| 152 | dbgs() << "Node: " << FuncName << "\n" |
| 153 | << " Callsite: " << CallSiteLoc << "\n" |
| 154 | << " Size: " << FuncSize << "\n" |
| 155 | << " Children:\n" ; |
| 156 | |
| 157 | for (auto &It : AllChildContext) { |
| 158 | dbgs() << " Node: " << It.second.getFuncName() << "\n" ; |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | void ContextTrieNode::dumpTree() { |
| 163 | dbgs() << "Context Profile Tree:\n" ; |
| 164 | std::queue<ContextTrieNode *> NodeQueue; |
| 165 | NodeQueue.push(x: this); |
| 166 | |
| 167 | while (!NodeQueue.empty()) { |
| 168 | ContextTrieNode *Node = NodeQueue.front(); |
| 169 | NodeQueue.pop(); |
| 170 | Node->dumpNode(); |
| 171 | |
| 172 | for (auto &It : Node->getAllChildContext()) { |
| 173 | ContextTrieNode *ChildNode = &It.second; |
| 174 | NodeQueue.push(x: ChildNode); |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | ContextTrieNode *ContextTrieNode::getOrCreateChildContext( |
| 180 | const LineLocation &CallSite, FunctionId CalleeName, bool AllowCreate) { |
| 181 | uint64_t Hash = FunctionSamples::getCallSiteHash(Callee: CalleeName, Callsite: CallSite); |
| 182 | auto It = AllChildContext.find(x: Hash); |
| 183 | if (It != AllChildContext.end()) { |
| 184 | assert(It->second.getFuncName() == CalleeName && |
| 185 | "Hash collision for child context node" ); |
| 186 | return &It->second; |
| 187 | } |
| 188 | |
| 189 | if (!AllowCreate) |
| 190 | return nullptr; |
| 191 | |
| 192 | ContextTrieNode &ACC = AllChildContext[Hash]; |
| 193 | ACC = ContextTrieNode(this, CalleeName, nullptr, CallSite); |
| 194 | return &ACC; |
| 195 | } |
| 196 | |
| 197 | // Profiler tracker than manages profiles and its associated context |
| 198 | SampleContextTracker::SampleContextTracker( |
| 199 | SampleProfileMap &Profiles, |
| 200 | const DenseMap<uint64_t, StringRef> *GUIDToFuncNameMap) |
| 201 | : GUIDToFuncNameMap(GUIDToFuncNameMap) { |
| 202 | for (auto &FuncSample : Profiles) { |
| 203 | FunctionSamples *FSamples = &FuncSample.second; |
| 204 | SampleContext Context = FuncSample.second.getContext(); |
| 205 | LLVM_DEBUG(dbgs() << "Tracking Context for function: " << Context.toString() |
| 206 | << "\n" ); |
| 207 | ContextTrieNode *NewNode = getOrCreateContextPath(Context, AllowCreate: true); |
| 208 | assert(!NewNode->getFunctionSamples() && |
| 209 | "New node can't have sample profile" ); |
| 210 | NewNode->setFunctionSamples(FSamples); |
| 211 | } |
| 212 | populateFuncToCtxtMap(); |
| 213 | } |
| 214 | |
| 215 | void SampleContextTracker::populateFuncToCtxtMap() { |
| 216 | for (auto *Node : *this) { |
| 217 | FunctionSamples *FSamples = Node->getFunctionSamples(); |
| 218 | if (FSamples) { |
| 219 | FSamples->getContext().setState(RawContext); |
| 220 | setContextNode(FSample: FSamples, Node); |
| 221 | FuncToCtxtProfiles[Node->getFuncName()].push_back(x: FSamples); |
| 222 | } |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | FunctionSamples * |
| 227 | SampleContextTracker::getCalleeContextSamplesFor(const CallBase &Inst, |
| 228 | StringRef CalleeName) { |
| 229 | LLVM_DEBUG(dbgs() << "Getting callee context for instr: " << Inst << "\n" ); |
| 230 | DILocation *DIL = Inst.getDebugLoc(); |
| 231 | if (!DIL) |
| 232 | return nullptr; |
| 233 | |
| 234 | CalleeName = FunctionSamples::getCanonicalFnName(FnName: CalleeName); |
| 235 | |
| 236 | FunctionId FName = getRepInFormat(Name: CalleeName); |
| 237 | |
| 238 | // For indirect call, CalleeName will be empty, in which case the context |
| 239 | // profile for callee with largest total samples will be returned. |
| 240 | ContextTrieNode *CalleeContext = getCalleeContextFor(DIL, CalleeName: FName); |
| 241 | if (CalleeContext) { |
| 242 | FunctionSamples *FSamples = CalleeContext->getFunctionSamples(); |
| 243 | LLVM_DEBUG(if (FSamples) { |
| 244 | dbgs() << " Callee context found: " << getContextString(CalleeContext) |
| 245 | << "\n" ; |
| 246 | }); |
| 247 | return FSamples; |
| 248 | } |
| 249 | |
| 250 | return nullptr; |
| 251 | } |
| 252 | |
| 253 | std::vector<const FunctionSamples *> |
| 254 | SampleContextTracker::getIndirectCalleeContextSamplesFor( |
| 255 | const DILocation *DIL) { |
| 256 | std::vector<const FunctionSamples *> R; |
| 257 | if (!DIL) |
| 258 | return R; |
| 259 | |
| 260 | ContextTrieNode *CallerNode = getContextFor(DIL); |
| 261 | LineLocation CallSite = FunctionSamples::getCallSiteIdentifier(DIL); |
| 262 | for (auto &It : CallerNode->getAllChildContext()) { |
| 263 | ContextTrieNode &ChildNode = It.second; |
| 264 | if (ChildNode.getCallSiteLoc() != CallSite) |
| 265 | continue; |
| 266 | if (FunctionSamples *CalleeSamples = ChildNode.getFunctionSamples()) |
| 267 | R.push_back(x: CalleeSamples); |
| 268 | } |
| 269 | |
| 270 | return R; |
| 271 | } |
| 272 | |
| 273 | FunctionSamples * |
| 274 | SampleContextTracker::getContextSamplesFor(const DILocation *DIL) { |
| 275 | assert(DIL && "Expect non-null location" ); |
| 276 | |
| 277 | ContextTrieNode *ContextNode = getContextFor(DIL); |
| 278 | if (!ContextNode) |
| 279 | return nullptr; |
| 280 | |
| 281 | // We may have inlined callees during pre-LTO compilation, in which case |
| 282 | // we need to rely on the inline stack from !dbg to mark context profile |
| 283 | // as inlined, instead of `MarkContextSamplesInlined` during inlining. |
| 284 | // Sample profile loader walks through all instructions to get profile, |
| 285 | // which calls this function. So once that is done, all previously inlined |
| 286 | // context profile should be marked properly. |
| 287 | FunctionSamples *Samples = ContextNode->getFunctionSamples(); |
| 288 | if (Samples && ContextNode->getParentContext() != &RootContext) |
| 289 | Samples->getContext().setState(InlinedContext); |
| 290 | |
| 291 | return Samples; |
| 292 | } |
| 293 | |
| 294 | FunctionSamples * |
| 295 | SampleContextTracker::getContextSamplesFor(const SampleContext &Context) { |
| 296 | ContextTrieNode *Node = getContextFor(Context); |
| 297 | if (!Node) |
| 298 | return nullptr; |
| 299 | |
| 300 | return Node->getFunctionSamples(); |
| 301 | } |
| 302 | |
| 303 | SampleContextTracker::ContextSamplesTy & |
| 304 | SampleContextTracker::getAllContextSamplesFor(const Function &Func) { |
| 305 | StringRef CanonName = FunctionSamples::getCanonicalFnName(F: Func); |
| 306 | return FuncToCtxtProfiles[getRepInFormat(Name: CanonName)]; |
| 307 | } |
| 308 | |
| 309 | SampleContextTracker::ContextSamplesTy & |
| 310 | SampleContextTracker::getAllContextSamplesFor(StringRef Name) { |
| 311 | return FuncToCtxtProfiles[getRepInFormat(Name)]; |
| 312 | } |
| 313 | |
| 314 | FunctionSamples *SampleContextTracker::getBaseSamplesFor(const Function &Func, |
| 315 | bool MergeContext) { |
| 316 | StringRef CanonName = FunctionSamples::getCanonicalFnName(F: Func); |
| 317 | return getBaseSamplesFor(Name: getRepInFormat(Name: CanonName), MergeContext); |
| 318 | } |
| 319 | |
| 320 | FunctionSamples *SampleContextTracker::getBaseSamplesFor(FunctionId Name, |
| 321 | bool MergeContext) { |
| 322 | LLVM_DEBUG(dbgs() << "Getting base profile for function: " << Name << "\n" ); |
| 323 | |
| 324 | // Base profile is top-level node (child of root node), so try to retrieve |
| 325 | // existing top-level node for given function first. If it exists, it could be |
| 326 | // that we've merged base profile before, or there's actually context-less |
| 327 | // profile from the input (e.g. due to unreliable stack walking). |
| 328 | ContextTrieNode *Node = getTopLevelContextNode(FName: Name); |
| 329 | if (MergeContext) { |
| 330 | LLVM_DEBUG(dbgs() << " Merging context profile into base profile: " << Name |
| 331 | << "\n" ); |
| 332 | |
| 333 | // We have profile for function under different contexts, |
| 334 | // create synthetic base profile and merge context profiles |
| 335 | // into base profile. |
| 336 | for (auto *CSamples : FuncToCtxtProfiles[Name]) { |
| 337 | SampleContext &Context = CSamples->getContext(); |
| 338 | // Skip inlined context profile and also don't re-merge any context |
| 339 | if (Context.hasState(S: InlinedContext) || Context.hasState(S: MergedContext)) |
| 340 | continue; |
| 341 | |
| 342 | ContextTrieNode *FromNode = getContextNodeForProfile(FSamples: CSamples); |
| 343 | if (FromNode == Node) |
| 344 | continue; |
| 345 | |
| 346 | ContextTrieNode &ToNode = promoteMergeContextSamplesTree(NodeToPromo&: *FromNode); |
| 347 | assert((!Node || Node == &ToNode) && "Expect only one base profile" ); |
| 348 | Node = &ToNode; |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | // Still no profile even after merge/promotion (if allowed) |
| 353 | if (!Node) |
| 354 | return nullptr; |
| 355 | |
| 356 | return Node->getFunctionSamples(); |
| 357 | } |
| 358 | |
| 359 | void SampleContextTracker::markContextSamplesInlined( |
| 360 | const FunctionSamples *InlinedSamples) { |
| 361 | assert(InlinedSamples && "Expect non-null inlined samples" ); |
| 362 | LLVM_DEBUG(dbgs() << "Marking context profile as inlined: " |
| 363 | << getContextString(*InlinedSamples) << "\n" ); |
| 364 | InlinedSamples->getContext().setState(InlinedContext); |
| 365 | } |
| 366 | |
| 367 | ContextTrieNode &SampleContextTracker::getRootContext() { return RootContext; } |
| 368 | |
| 369 | void SampleContextTracker::promoteMergeContextSamplesTree( |
| 370 | const Instruction &Inst, FunctionId CalleeName) { |
| 371 | LLVM_DEBUG(dbgs() << "Promoting and merging context tree for instr: \n" |
| 372 | << Inst << "\n" ); |
| 373 | // Get the caller context for the call instruction, we don't use callee |
| 374 | // name from call because there can be context from indirect calls too. |
| 375 | DILocation *DIL = Inst.getDebugLoc(); |
| 376 | ContextTrieNode *CallerNode = getContextFor(DIL); |
| 377 | if (!CallerNode) |
| 378 | return; |
| 379 | |
| 380 | // Get the context that needs to be promoted |
| 381 | LineLocation CallSite = FunctionSamples::getCallSiteIdentifier(DIL); |
| 382 | // For indirect call, CalleeName will be empty, in which case we need to |
| 383 | // promote all non-inlined child context profiles. |
| 384 | if (CalleeName.empty()) { |
| 385 | for (auto &It : CallerNode->getAllChildContext()) { |
| 386 | ContextTrieNode *NodeToPromo = &It.second; |
| 387 | if (CallSite != NodeToPromo->getCallSiteLoc()) |
| 388 | continue; |
| 389 | FunctionSamples *FromSamples = NodeToPromo->getFunctionSamples(); |
| 390 | if (FromSamples && FromSamples->getContext().hasState(S: InlinedContext)) |
| 391 | continue; |
| 392 | promoteMergeContextSamplesTree(NodeToPromo&: *NodeToPromo); |
| 393 | } |
| 394 | return; |
| 395 | } |
| 396 | |
| 397 | // Get the context for the given callee that needs to be promoted |
| 398 | ContextTrieNode *NodeToPromo = |
| 399 | CallerNode->getChildContext(CallSite, CalleeName); |
| 400 | if (!NodeToPromo) |
| 401 | return; |
| 402 | |
| 403 | promoteMergeContextSamplesTree(NodeToPromo&: *NodeToPromo); |
| 404 | } |
| 405 | |
| 406 | ContextTrieNode &SampleContextTracker::promoteMergeContextSamplesTree( |
| 407 | ContextTrieNode &NodeToPromo) { |
| 408 | // Promote the input node to be directly under root. This can happen |
| 409 | // when we decided to not inline a function under context represented |
| 410 | // by the input node. The promote and merge is then needed to reflect |
| 411 | // the context profile in the base (context-less) profile. |
| 412 | FunctionSamples *FromSamples = NodeToPromo.getFunctionSamples(); |
| 413 | assert(FromSamples && "Shouldn't promote a context without profile" ); |
| 414 | (void)FromSamples; // Unused in release build. |
| 415 | |
| 416 | LLVM_DEBUG(dbgs() << " Found context tree root to promote: " |
| 417 | << getContextString(&NodeToPromo) << "\n" ); |
| 418 | |
| 419 | assert(!FromSamples->getContext().hasState(InlinedContext) && |
| 420 | "Shouldn't promote inlined context profile" ); |
| 421 | return promoteMergeContextSamplesTree(FromNode&: NodeToPromo, ToNodeParent&: RootContext); |
| 422 | } |
| 423 | |
| 424 | #ifndef NDEBUG |
| 425 | std::string |
| 426 | SampleContextTracker::getContextString(const FunctionSamples &FSamples) const { |
| 427 | return getContextString(getContextNodeForProfile(&FSamples)); |
| 428 | } |
| 429 | |
| 430 | std::string |
| 431 | SampleContextTracker::getContextString(ContextTrieNode *Node) const { |
| 432 | SampleContextFrameVector Res; |
| 433 | if (Node == &RootContext) |
| 434 | return std::string(); |
| 435 | Res.emplace_back(Node->getFuncName(), LineLocation(0, 0)); |
| 436 | |
| 437 | ContextTrieNode *PreNode = Node; |
| 438 | Node = Node->getParentContext(); |
| 439 | while (Node && Node != &RootContext) { |
| 440 | Res.emplace_back(Node->getFuncName(), PreNode->getCallSiteLoc()); |
| 441 | PreNode = Node; |
| 442 | Node = Node->getParentContext(); |
| 443 | } |
| 444 | |
| 445 | std::reverse(Res.begin(), Res.end()); |
| 446 | |
| 447 | return SampleContext::getContextString(Res); |
| 448 | } |
| 449 | #endif |
| 450 | |
| 451 | void SampleContextTracker::dump() { RootContext.dumpTree(); } |
| 452 | |
| 453 | StringRef SampleContextTracker::getFuncNameFor(ContextTrieNode *Node) const { |
| 454 | if (!FunctionSamples::UseMD5) |
| 455 | return Node->getFuncName().stringRef(); |
| 456 | assert(GUIDToFuncNameMap && "GUIDToFuncNameMap needs to be populated first" ); |
| 457 | return GUIDToFuncNameMap->lookup(Val: Node->getFuncName().getHashCode()); |
| 458 | } |
| 459 | |
| 460 | ContextTrieNode * |
| 461 | SampleContextTracker::getContextFor(const SampleContext &Context) { |
| 462 | return getOrCreateContextPath(Context, AllowCreate: false); |
| 463 | } |
| 464 | |
| 465 | ContextTrieNode * |
| 466 | SampleContextTracker::getCalleeContextFor(const DILocation *DIL, |
| 467 | FunctionId CalleeName) { |
| 468 | assert(DIL && "Expect non-null location" ); |
| 469 | |
| 470 | ContextTrieNode *CallContext = getContextFor(DIL); |
| 471 | if (!CallContext) |
| 472 | return nullptr; |
| 473 | |
| 474 | // When CalleeName is empty, the child context profile with max |
| 475 | // total samples will be returned. |
| 476 | return CallContext->getChildContext( |
| 477 | CallSite: FunctionSamples::getCallSiteIdentifier(DIL), CalleeName); |
| 478 | } |
| 479 | |
| 480 | ContextTrieNode *SampleContextTracker::getContextFor(const DILocation *DIL) { |
| 481 | assert(DIL && "Expect non-null location" ); |
| 482 | SmallVector<std::pair<LineLocation, FunctionId>, 10> S; |
| 483 | |
| 484 | // Use C++ linkage name if possible. |
| 485 | const DILocation *PrevDIL = DIL; |
| 486 | for (DIL = DIL->getInlinedAt(); DIL; DIL = DIL->getInlinedAt()) { |
| 487 | StringRef Name = PrevDIL->getScope()->getSubprogram()->getLinkageName(); |
| 488 | if (Name.empty()) |
| 489 | Name = PrevDIL->getScope()->getSubprogram()->getName(); |
| 490 | S.push_back( |
| 491 | Elt: std::make_pair(x: FunctionSamples::getCallSiteIdentifier(DIL), |
| 492 | y: getRepInFormat(Name))); |
| 493 | PrevDIL = DIL; |
| 494 | } |
| 495 | |
| 496 | // Push root node, note that root node like main may only |
| 497 | // a name, but not linkage name. |
| 498 | StringRef RootName = PrevDIL->getScope()->getSubprogram()->getLinkageName(); |
| 499 | if (RootName.empty()) |
| 500 | RootName = PrevDIL->getScope()->getSubprogram()->getName(); |
| 501 | S.push_back(Elt: std::make_pair(x: LineLocation(0, 0), |
| 502 | y: getRepInFormat(Name: RootName))); |
| 503 | |
| 504 | ContextTrieNode *ContextNode = &RootContext; |
| 505 | int I = S.size(); |
| 506 | while (--I >= 0 && ContextNode) { |
| 507 | LineLocation &CallSite = S[I].first; |
| 508 | FunctionId CalleeName = S[I].second; |
| 509 | ContextNode = ContextNode->getChildContext(CallSite, CalleeName); |
| 510 | } |
| 511 | |
| 512 | if (I < 0) |
| 513 | return ContextNode; |
| 514 | |
| 515 | return nullptr; |
| 516 | } |
| 517 | |
| 518 | ContextTrieNode * |
| 519 | SampleContextTracker::getOrCreateContextPath(const SampleContext &Context, |
| 520 | bool AllowCreate) { |
| 521 | ContextTrieNode *ContextNode = &RootContext; |
| 522 | LineLocation CallSiteLoc(0, 0); |
| 523 | |
| 524 | for (const auto &Callsite : Context.getContextFrames()) { |
| 525 | // Create child node at parent line/disc location |
| 526 | if (AllowCreate) { |
| 527 | ContextNode = |
| 528 | ContextNode->getOrCreateChildContext(CallSite: CallSiteLoc, CalleeName: Callsite.Func); |
| 529 | } else { |
| 530 | ContextNode = |
| 531 | ContextNode->getChildContext(CallSite: CallSiteLoc, CalleeName: Callsite.Func); |
| 532 | } |
| 533 | CallSiteLoc = Callsite.Location; |
| 534 | } |
| 535 | |
| 536 | assert((!AllowCreate || ContextNode) && |
| 537 | "Node must exist if creation is allowed" ); |
| 538 | return ContextNode; |
| 539 | } |
| 540 | |
| 541 | ContextTrieNode * |
| 542 | SampleContextTracker::getTopLevelContextNode(FunctionId FName) { |
| 543 | assert(!FName.empty() && "Top level node query must provide valid name" ); |
| 544 | return RootContext.getChildContext(CallSite: LineLocation(0, 0), CalleeName: FName); |
| 545 | } |
| 546 | |
| 547 | ContextTrieNode & |
| 548 | SampleContextTracker::addTopLevelContextNode(FunctionId FName) { |
| 549 | assert(!getTopLevelContextNode(FName) && "Node to add must not exist" ); |
| 550 | return *RootContext.getOrCreateChildContext(CallSite: LineLocation(0, 0), CalleeName: FName); |
| 551 | } |
| 552 | |
| 553 | void SampleContextTracker::mergeContextNode(ContextTrieNode &FromNode, |
| 554 | ContextTrieNode &ToNode) { |
| 555 | FunctionSamples *FromSamples = FromNode.getFunctionSamples(); |
| 556 | FunctionSamples *ToSamples = ToNode.getFunctionSamples(); |
| 557 | if (FromSamples && ToSamples) { |
| 558 | // Merge/duplicate FromSamples into ToSamples |
| 559 | ToSamples->merge(Other: *FromSamples); |
| 560 | ToSamples->getContext().setState(SyntheticContext); |
| 561 | FromSamples->getContext().setState(MergedContext); |
| 562 | if (FromSamples->getContext().hasAttribute(A: ContextShouldBeInlined)) |
| 563 | ToSamples->getContext().setAttribute(ContextShouldBeInlined); |
| 564 | } else if (FromSamples) { |
| 565 | // Transfer FromSamples from FromNode to ToNode |
| 566 | ToNode.setFunctionSamples(FromSamples); |
| 567 | setContextNode(FSample: FromSamples, Node: &ToNode); |
| 568 | FromSamples->getContext().setState(SyntheticContext); |
| 569 | } |
| 570 | } |
| 571 | |
| 572 | ContextTrieNode &SampleContextTracker::promoteMergeContextSamplesTree( |
| 573 | ContextTrieNode &FromNode, ContextTrieNode &ToNodeParent) { |
| 574 | |
| 575 | // Ignore call site location if destination is top level under root |
| 576 | LineLocation NewCallSiteLoc = LineLocation(0, 0); |
| 577 | LineLocation OldCallSiteLoc = FromNode.getCallSiteLoc(); |
| 578 | ContextTrieNode &FromNodeParent = *FromNode.getParentContext(); |
| 579 | ContextTrieNode *ToNode = nullptr; |
| 580 | bool MoveToRoot = (&ToNodeParent == &RootContext); |
| 581 | if (!MoveToRoot) { |
| 582 | NewCallSiteLoc = OldCallSiteLoc; |
| 583 | } |
| 584 | |
| 585 | // Locate destination node, create/move if not existing |
| 586 | ToNode = ToNodeParent.getChildContext(CallSite: NewCallSiteLoc, CalleeName: FromNode.getFuncName()); |
| 587 | if (!ToNode) { |
| 588 | // Do not delete node to move from its parent here because |
| 589 | // caller is iterating over children of that parent node. |
| 590 | ToNode = |
| 591 | &moveContextSamples(ToNodeParent, CallSite: NewCallSiteLoc, NodeToMove: std::move(FromNode)); |
| 592 | LLVM_DEBUG({ |
| 593 | dbgs() << " Context promoted and merged to: " << getContextString(ToNode) |
| 594 | << "\n" ; |
| 595 | }); |
| 596 | } else { |
| 597 | // Destination node exists, merge samples for the context tree |
| 598 | mergeContextNode(FromNode, ToNode&: *ToNode); |
| 599 | LLVM_DEBUG({ |
| 600 | if (ToNode->getFunctionSamples()) |
| 601 | dbgs() << " Context promoted and merged to: " |
| 602 | << getContextString(ToNode) << "\n" ; |
| 603 | }); |
| 604 | |
| 605 | // Recursively promote and merge children |
| 606 | for (auto &It : FromNode.getAllChildContext()) { |
| 607 | ContextTrieNode &FromChildNode = It.second; |
| 608 | promoteMergeContextSamplesTree(FromNode&: FromChildNode, ToNodeParent&: *ToNode); |
| 609 | } |
| 610 | |
| 611 | // Remove children once they're all merged |
| 612 | FromNode.getAllChildContext().clear(); |
| 613 | } |
| 614 | |
| 615 | // For root of subtree, remove itself from old parent too |
| 616 | if (MoveToRoot) |
| 617 | FromNodeParent.removeChildContext(CallSite: OldCallSiteLoc, CalleeName: ToNode->getFuncName()); |
| 618 | |
| 619 | return *ToNode; |
| 620 | } |
| 621 | |
| 622 | void SampleContextTracker::createContextLessProfileMap( |
| 623 | SampleProfileMap &ContextLessProfiles) { |
| 624 | for (auto *Node : *this) { |
| 625 | FunctionSamples *FProfile = Node->getFunctionSamples(); |
| 626 | // Profile's context can be empty, use ContextNode's func name. |
| 627 | if (FProfile) |
| 628 | ContextLessProfiles.create(Ctx: Node->getFuncName()).merge(Other: *FProfile); |
| 629 | } |
| 630 | } |
| 631 | } // namespace llvm |
| 632 | |