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