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 | AllChildContext[Hash] = NodeToMove; |
75 | ContextTrieNode &NewNode = AllChildContext[Hash]; |
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 | 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.second.getContext(); |
204 | LLVM_DEBUG(dbgs() << "Tracking Context for function: " << Context.toString() |
205 | << "\n" ); |
206 | ContextTrieNode *NewNode = getOrCreateContextPath(Context, AllowCreate: true); |
207 | assert(!NewNode->getFunctionSamples() && |
208 | "New node can't have sample profile" ); |
209 | NewNode->setFunctionSamples(FSamples); |
210 | } |
211 | populateFuncToCtxtMap(); |
212 | } |
213 | |
214 | void SampleContextTracker::populateFuncToCtxtMap() { |
215 | for (auto *Node : *this) { |
216 | FunctionSamples *FSamples = Node->getFunctionSamples(); |
217 | if (FSamples) { |
218 | FSamples->getContext().setState(RawContext); |
219 | setContextNode(FSample: FSamples, Node); |
220 | FuncToCtxtProfiles[Node->getFuncName()].push_back(x: FSamples); |
221 | } |
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(FnName: CalleeName); |
234 | |
235 | FunctionId FName = getRepInFormat(Name: CalleeName); |
236 | |
237 | // For indirect call, CalleeName will be empty, in which case the context |
238 | // profile for callee with largest total samples will be returned. |
239 | ContextTrieNode *CalleeContext = getCalleeContextFor(DIL, CalleeName: FName); |
240 | if (CalleeContext) { |
241 | FunctionSamples *FSamples = CalleeContext->getFunctionSamples(); |
242 | LLVM_DEBUG(if (FSamples) { |
243 | dbgs() << " Callee context found: " << getContextString(CalleeContext) |
244 | << "\n" ; |
245 | }); |
246 | return FSamples; |
247 | } |
248 | |
249 | return nullptr; |
250 | } |
251 | |
252 | std::vector<const FunctionSamples *> |
253 | SampleContextTracker::getIndirectCalleeContextSamplesFor( |
254 | const DILocation *DIL) { |
255 | std::vector<const FunctionSamples *> R; |
256 | if (!DIL) |
257 | return R; |
258 | |
259 | ContextTrieNode *CallerNode = getContextFor(DIL); |
260 | LineLocation CallSite = FunctionSamples::getCallSiteIdentifier(DIL); |
261 | for (auto &It : CallerNode->getAllChildContext()) { |
262 | ContextTrieNode &ChildNode = It.second; |
263 | if (ChildNode.getCallSiteLoc() != CallSite) |
264 | continue; |
265 | if (FunctionSamples *CalleeSamples = ChildNode.getFunctionSamples()) |
266 | R.push_back(x: CalleeSamples); |
267 | } |
268 | |
269 | return R; |
270 | } |
271 | |
272 | FunctionSamples * |
273 | SampleContextTracker::getContextSamplesFor(const DILocation *DIL) { |
274 | assert(DIL && "Expect non-null location" ); |
275 | |
276 | ContextTrieNode *ContextNode = getContextFor(DIL); |
277 | if (!ContextNode) |
278 | return nullptr; |
279 | |
280 | // We may have inlined callees during pre-LTO compilation, in which case |
281 | // we need to rely on the inline stack from !dbg to mark context profile |
282 | // as inlined, instead of `MarkContextSamplesInlined` during inlining. |
283 | // Sample profile loader walks through all instructions to get profile, |
284 | // which calls this function. So once that is done, all previously inlined |
285 | // context profile should be marked properly. |
286 | FunctionSamples *Samples = ContextNode->getFunctionSamples(); |
287 | if (Samples && ContextNode->getParentContext() != &RootContext) |
288 | Samples->getContext().setState(InlinedContext); |
289 | |
290 | return Samples; |
291 | } |
292 | |
293 | FunctionSamples * |
294 | SampleContextTracker::getContextSamplesFor(const SampleContext &Context) { |
295 | ContextTrieNode *Node = getContextFor(Context); |
296 | if (!Node) |
297 | return nullptr; |
298 | |
299 | return Node->getFunctionSamples(); |
300 | } |
301 | |
302 | SampleContextTracker::ContextSamplesTy & |
303 | SampleContextTracker::getAllContextSamplesFor(const Function &Func) { |
304 | StringRef CanonName = FunctionSamples::getCanonicalFnName(F: Func); |
305 | return FuncToCtxtProfiles[getRepInFormat(Name: CanonName)]; |
306 | } |
307 | |
308 | SampleContextTracker::ContextSamplesTy & |
309 | SampleContextTracker::getAllContextSamplesFor(StringRef Name) { |
310 | return FuncToCtxtProfiles[getRepInFormat(Name)]; |
311 | } |
312 | |
313 | FunctionSamples *SampleContextTracker::getBaseSamplesFor(const Function &Func, |
314 | bool MergeContext) { |
315 | StringRef CanonName = FunctionSamples::getCanonicalFnName(F: Func); |
316 | return getBaseSamplesFor(Name: getRepInFormat(Name: CanonName), MergeContext); |
317 | } |
318 | |
319 | FunctionSamples *SampleContextTracker::getBaseSamplesFor(FunctionId Name, |
320 | bool MergeContext) { |
321 | LLVM_DEBUG(dbgs() << "Getting base profile for function: " << Name << "\n" ); |
322 | |
323 | // Base profile is top-level node (child of root node), so try to retrieve |
324 | // existing top-level node for given function first. If it exists, it could be |
325 | // that we've merged base profile before, or there's actually context-less |
326 | // profile from the input (e.g. due to unreliable stack walking). |
327 | ContextTrieNode *Node = getTopLevelContextNode(FName: Name); |
328 | if (MergeContext) { |
329 | LLVM_DEBUG(dbgs() << " Merging context profile into base profile: " << Name |
330 | << "\n" ); |
331 | |
332 | // We have profile for function under different contexts, |
333 | // create synthetic base profile and merge context profiles |
334 | // into base profile. |
335 | for (auto *CSamples : FuncToCtxtProfiles[Name]) { |
336 | SampleContext &Context = CSamples->getContext(); |
337 | // Skip inlined context profile and also don't re-merge any context |
338 | if (Context.hasState(S: InlinedContext) || Context.hasState(S: MergedContext)) |
339 | continue; |
340 | |
341 | ContextTrieNode *FromNode = getContextNodeForProfile(FSamples: CSamples); |
342 | if (FromNode == Node) |
343 | continue; |
344 | |
345 | ContextTrieNode &ToNode = promoteMergeContextSamplesTree(NodeToPromo&: *FromNode); |
346 | assert((!Node || Node == &ToNode) && "Expect only one base profile" ); |
347 | Node = &ToNode; |
348 | } |
349 | } |
350 | |
351 | // Still no profile even after merge/promotion (if allowed) |
352 | if (!Node) |
353 | return nullptr; |
354 | |
355 | return Node->getFunctionSamples(); |
356 | } |
357 | |
358 | void SampleContextTracker::markContextSamplesInlined( |
359 | const FunctionSamples *InlinedSamples) { |
360 | assert(InlinedSamples && "Expect non-null inlined samples" ); |
361 | LLVM_DEBUG(dbgs() << "Marking context profile as inlined: " |
362 | << getContextString(*InlinedSamples) << "\n" ); |
363 | InlinedSamples->getContext().setState(InlinedContext); |
364 | } |
365 | |
366 | ContextTrieNode &SampleContextTracker::getRootContext() { return RootContext; } |
367 | |
368 | void SampleContextTracker::promoteMergeContextSamplesTree( |
369 | const Instruction &Inst, FunctionId CalleeName) { |
370 | LLVM_DEBUG(dbgs() << "Promoting and merging context tree for instr: \n" |
371 | << Inst << "\n" ); |
372 | // Get the caller context for the call instruction, we don't use callee |
373 | // name from call because there can be context from indirect calls too. |
374 | DILocation *DIL = Inst.getDebugLoc(); |
375 | ContextTrieNode *CallerNode = getContextFor(DIL); |
376 | if (!CallerNode) |
377 | return; |
378 | |
379 | // Get the context that needs to be promoted |
380 | LineLocation CallSite = FunctionSamples::getCallSiteIdentifier(DIL); |
381 | // For indirect call, CalleeName will be empty, in which case we need to |
382 | // promote all non-inlined child context profiles. |
383 | if (CalleeName.empty()) { |
384 | for (auto &It : CallerNode->getAllChildContext()) { |
385 | ContextTrieNode *NodeToPromo = &It.second; |
386 | if (CallSite != NodeToPromo->getCallSiteLoc()) |
387 | continue; |
388 | FunctionSamples *FromSamples = NodeToPromo->getFunctionSamples(); |
389 | if (FromSamples && FromSamples->getContext().hasState(S: InlinedContext)) |
390 | continue; |
391 | promoteMergeContextSamplesTree(NodeToPromo&: *NodeToPromo); |
392 | } |
393 | return; |
394 | } |
395 | |
396 | // Get the context for the given callee that needs to be promoted |
397 | ContextTrieNode *NodeToPromo = |
398 | CallerNode->getChildContext(CallSite, CalleeName); |
399 | if (!NodeToPromo) |
400 | return; |
401 | |
402 | promoteMergeContextSamplesTree(NodeToPromo&: *NodeToPromo); |
403 | } |
404 | |
405 | ContextTrieNode &SampleContextTracker::promoteMergeContextSamplesTree( |
406 | ContextTrieNode &NodeToPromo) { |
407 | // Promote the input node to be directly under root. This can happen |
408 | // when we decided to not inline a function under context represented |
409 | // by the input node. The promote and merge is then needed to reflect |
410 | // the context profile in the base (context-less) profile. |
411 | FunctionSamples *FromSamples = NodeToPromo.getFunctionSamples(); |
412 | assert(FromSamples && "Shouldn't promote a context without profile" ); |
413 | (void)FromSamples; // Unused in release build. |
414 | |
415 | LLVM_DEBUG(dbgs() << " Found context tree root to promote: " |
416 | << getContextString(&NodeToPromo) << "\n" ); |
417 | |
418 | assert(!FromSamples->getContext().hasState(InlinedContext) && |
419 | "Shouldn't promote inlined context profile" ); |
420 | return promoteMergeContextSamplesTree(FromNode&: NodeToPromo, ToNodeParent&: RootContext); |
421 | } |
422 | |
423 | #ifndef NDEBUG |
424 | std::string |
425 | SampleContextTracker::getContextString(const FunctionSamples &FSamples) const { |
426 | return getContextString(getContextNodeForProfile(&FSamples)); |
427 | } |
428 | |
429 | std::string |
430 | SampleContextTracker::getContextString(ContextTrieNode *Node) const { |
431 | SampleContextFrameVector Res; |
432 | if (Node == &RootContext) |
433 | return std::string(); |
434 | Res.emplace_back(Node->getFuncName(), LineLocation(0, 0)); |
435 | |
436 | ContextTrieNode *PreNode = Node; |
437 | Node = Node->getParentContext(); |
438 | while (Node && Node != &RootContext) { |
439 | Res.emplace_back(Node->getFuncName(), PreNode->getCallSiteLoc()); |
440 | PreNode = Node; |
441 | Node = Node->getParentContext(); |
442 | } |
443 | |
444 | std::reverse(Res.begin(), Res.end()); |
445 | |
446 | return SampleContext::getContextString(Res); |
447 | } |
448 | #endif |
449 | |
450 | void SampleContextTracker::dump() { RootContext.dumpTree(); } |
451 | |
452 | StringRef SampleContextTracker::getFuncNameFor(ContextTrieNode *Node) const { |
453 | if (!FunctionSamples::UseMD5) |
454 | return Node->getFuncName().stringRef(); |
455 | assert(GUIDToFuncNameMap && "GUIDToFuncNameMap needs to be populated first" ); |
456 | return GUIDToFuncNameMap->lookup(Val: Node->getFuncName().getHashCode()); |
457 | } |
458 | |
459 | ContextTrieNode * |
460 | SampleContextTracker::getContextFor(const SampleContext &Context) { |
461 | return getOrCreateContextPath(Context, AllowCreate: false); |
462 | } |
463 | |
464 | ContextTrieNode * |
465 | SampleContextTracker::getCalleeContextFor(const DILocation *DIL, |
466 | FunctionId CalleeName) { |
467 | assert(DIL && "Expect non-null location" ); |
468 | |
469 | ContextTrieNode *CallContext = getContextFor(DIL); |
470 | if (!CallContext) |
471 | return nullptr; |
472 | |
473 | // When CalleeName is empty, the child context profile with max |
474 | // total samples will be returned. |
475 | return CallContext->getChildContext( |
476 | CallSite: FunctionSamples::getCallSiteIdentifier(DIL), CalleeName); |
477 | } |
478 | |
479 | ContextTrieNode *SampleContextTracker::getContextFor(const DILocation *DIL) { |
480 | assert(DIL && "Expect non-null location" ); |
481 | SmallVector<std::pair<LineLocation, FunctionId>, 10> S; |
482 | |
483 | // Use C++ linkage name if possible. |
484 | const DILocation *PrevDIL = DIL; |
485 | for (DIL = DIL->getInlinedAt(); DIL; DIL = DIL->getInlinedAt()) { |
486 | StringRef Name = PrevDIL->getScope()->getSubprogram()->getLinkageName(); |
487 | if (Name.empty()) |
488 | Name = PrevDIL->getScope()->getSubprogram()->getName(); |
489 | S.push_back( |
490 | Elt: std::make_pair(x: FunctionSamples::getCallSiteIdentifier(DIL), |
491 | y: getRepInFormat(Name))); |
492 | PrevDIL = DIL; |
493 | } |
494 | |
495 | // Push root node, note that root node like main may only |
496 | // a name, but not linkage name. |
497 | StringRef RootName = PrevDIL->getScope()->getSubprogram()->getLinkageName(); |
498 | if (RootName.empty()) |
499 | RootName = PrevDIL->getScope()->getSubprogram()->getName(); |
500 | S.push_back(Elt: std::make_pair(x: LineLocation(0, 0), |
501 | y: getRepInFormat(Name: RootName))); |
502 | |
503 | ContextTrieNode *ContextNode = &RootContext; |
504 | int I = S.size(); |
505 | while (--I >= 0 && ContextNode) { |
506 | LineLocation &CallSite = S[I].first; |
507 | FunctionId CalleeName = S[I].second; |
508 | ContextNode = ContextNode->getChildContext(CallSite, CalleeName); |
509 | } |
510 | |
511 | if (I < 0) |
512 | return ContextNode; |
513 | |
514 | return nullptr; |
515 | } |
516 | |
517 | ContextTrieNode * |
518 | SampleContextTracker::getOrCreateContextPath(const SampleContext &Context, |
519 | bool AllowCreate) { |
520 | ContextTrieNode *ContextNode = &RootContext; |
521 | LineLocation CallSiteLoc(0, 0); |
522 | |
523 | for (const auto &Callsite : Context.getContextFrames()) { |
524 | // Create child node at parent line/disc location |
525 | if (AllowCreate) { |
526 | ContextNode = |
527 | ContextNode->getOrCreateChildContext(CallSite: CallSiteLoc, CalleeName: Callsite.Func); |
528 | } else { |
529 | ContextNode = |
530 | ContextNode->getChildContext(CallSite: CallSiteLoc, CalleeName: Callsite.Func); |
531 | } |
532 | CallSiteLoc = Callsite.Location; |
533 | } |
534 | |
535 | assert((!AllowCreate || ContextNode) && |
536 | "Node must exist if creation is allowed" ); |
537 | return ContextNode; |
538 | } |
539 | |
540 | ContextTrieNode * |
541 | SampleContextTracker::getTopLevelContextNode(FunctionId FName) { |
542 | assert(!FName.empty() && "Top level node query must provide valid name" ); |
543 | return RootContext.getChildContext(CallSite: LineLocation(0, 0), CalleeName: FName); |
544 | } |
545 | |
546 | ContextTrieNode & |
547 | SampleContextTracker::addTopLevelContextNode(FunctionId FName) { |
548 | assert(!getTopLevelContextNode(FName) && "Node to add must not exist" ); |
549 | return *RootContext.getOrCreateChildContext(CallSite: LineLocation(0, 0), CalleeName: FName); |
550 | } |
551 | |
552 | void SampleContextTracker::mergeContextNode(ContextTrieNode &FromNode, |
553 | ContextTrieNode &ToNode) { |
554 | FunctionSamples *FromSamples = FromNode.getFunctionSamples(); |
555 | FunctionSamples *ToSamples = ToNode.getFunctionSamples(); |
556 | if (FromSamples && ToSamples) { |
557 | // Merge/duplicate FromSamples into ToSamples |
558 | ToSamples->merge(Other: *FromSamples); |
559 | ToSamples->getContext().setState(SyntheticContext); |
560 | FromSamples->getContext().setState(MergedContext); |
561 | if (FromSamples->getContext().hasAttribute(A: ContextShouldBeInlined)) |
562 | ToSamples->getContext().setAttribute(ContextShouldBeInlined); |
563 | } else if (FromSamples) { |
564 | // Transfer FromSamples from FromNode to ToNode |
565 | ToNode.setFunctionSamples(FromSamples); |
566 | setContextNode(FSample: FromSamples, Node: &ToNode); |
567 | FromSamples->getContext().setState(SyntheticContext); |
568 | } |
569 | } |
570 | |
571 | ContextTrieNode &SampleContextTracker::promoteMergeContextSamplesTree( |
572 | ContextTrieNode &FromNode, ContextTrieNode &ToNodeParent) { |
573 | |
574 | // Ignore call site location if destination is top level under root |
575 | LineLocation NewCallSiteLoc = LineLocation(0, 0); |
576 | LineLocation OldCallSiteLoc = FromNode.getCallSiteLoc(); |
577 | ContextTrieNode &FromNodeParent = *FromNode.getParentContext(); |
578 | ContextTrieNode *ToNode = nullptr; |
579 | bool MoveToRoot = (&ToNodeParent == &RootContext); |
580 | if (!MoveToRoot) { |
581 | NewCallSiteLoc = OldCallSiteLoc; |
582 | } |
583 | |
584 | // Locate destination node, create/move if not existing |
585 | ToNode = ToNodeParent.getChildContext(CallSite: NewCallSiteLoc, CalleeName: FromNode.getFuncName()); |
586 | if (!ToNode) { |
587 | // Do not delete node to move from its parent here because |
588 | // caller is iterating over children of that parent node. |
589 | ToNode = |
590 | &moveContextSamples(ToNodeParent, CallSite: NewCallSiteLoc, NodeToMove: std::move(FromNode)); |
591 | LLVM_DEBUG({ |
592 | dbgs() << " Context promoted and merged to: " << getContextString(ToNode) |
593 | << "\n" ; |
594 | }); |
595 | } else { |
596 | // Destination node exists, merge samples for the context tree |
597 | mergeContextNode(FromNode, ToNode&: *ToNode); |
598 | LLVM_DEBUG({ |
599 | if (ToNode->getFunctionSamples()) |
600 | dbgs() << " Context promoted and merged to: " |
601 | << getContextString(ToNode) << "\n" ; |
602 | }); |
603 | |
604 | // Recursively promote and merge children |
605 | for (auto &It : FromNode.getAllChildContext()) { |
606 | ContextTrieNode &FromChildNode = It.second; |
607 | promoteMergeContextSamplesTree(FromNode&: FromChildNode, ToNodeParent&: *ToNode); |
608 | } |
609 | |
610 | // Remove children once they're all merged |
611 | FromNode.getAllChildContext().clear(); |
612 | } |
613 | |
614 | // For root of subtree, remove itself from old parent too |
615 | if (MoveToRoot) |
616 | FromNodeParent.removeChildContext(CallSite: OldCallSiteLoc, CalleeName: ToNode->getFuncName()); |
617 | |
618 | return *ToNode; |
619 | } |
620 | |
621 | void SampleContextTracker::createContextLessProfileMap( |
622 | SampleProfileMap &ContextLessProfiles) { |
623 | for (auto *Node : *this) { |
624 | FunctionSamples *FProfile = Node->getFunctionSamples(); |
625 | // Profile's context can be empty, use ContextNode's func name. |
626 | if (FProfile) |
627 | ContextLessProfiles.create(Ctx: Node->getFuncName()).merge(Other: *FProfile); |
628 | } |
629 | } |
630 | } // namespace llvm |
631 | |