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