1 //===-- ProfileGenerator.cpp - Profile Generator  ---------------*- C++ -*-===//
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 #include "ProfileGenerator.h"
10 
11 static cl::opt<std::string> OutputFilename("output", cl::value_desc("output"),
12                                            cl::Required,
13                                            cl::desc("Output profile file"));
14 
15 static cl::opt<SampleProfileFormat> OutputFormat(
16     "format", cl::desc("Format of output profile"), cl::init(SPF_Text),
17     cl::values(
18         clEnumValN(SPF_Binary, "binary", "Binary encoding (default)"),
19         clEnumValN(SPF_Compact_Binary, "compbinary", "Compact binary encoding"),
20         clEnumValN(SPF_Ext_Binary, "extbinary", "Extensible binary encoding"),
21         clEnumValN(SPF_Text, "text", "Text encoding"),
22         clEnumValN(SPF_GCC, "gcc",
23                    "GCC encoding (only meaningful for -sample)")));
24 
25 static cl::opt<int32_t, true> RecursionCompression(
26     "compress-recursion",
27     cl::desc("Compressing recursion by deduplicating adjacent frame "
28              "sequences up to the specified size. -1 means no size limit."),
29     cl::Hidden,
30     cl::location(llvm::sampleprof::CSProfileGenerator::MaxCompressionSize));
31 
32 static cl::opt<uint64_t> CSProfColdThres(
33     "csprof-cold-thres", cl::init(100), cl::ZeroOrMore,
34     cl::desc("Specify the total samples threshold for a context profile to "
35              "be considered cold, any cold profiles will be merged into "
36              "context-less base profiles"));
37 
38 static cl::opt<bool> CSProfKeepCold(
39     "csprof-keep-cold", cl::init(false), cl::ZeroOrMore,
40     cl::desc("This works together with --csprof-cold-thres. If the total count "
41              "of the profile after all merge is done is still smaller than the "
42              "csprof-cold-thres, it will be trimmed unless csprof-keep-cold "
43              "flag is specified."));
44 
45 using namespace llvm;
46 using namespace sampleprof;
47 
48 namespace llvm {
49 namespace sampleprof {
50 
51 // Initialize the MaxCompressionSize to -1 which means no size limit
52 int32_t CSProfileGenerator::MaxCompressionSize = -1;
53 
54 static bool
55 usePseudoProbes(const BinarySampleCounterMap &BinarySampleCounters) {
56   return BinarySampleCounters.size() &&
57          BinarySampleCounters.begin()->first->usePseudoProbes();
58 }
59 
60 std::unique_ptr<ProfileGenerator>
61 ProfileGenerator::create(const BinarySampleCounterMap &BinarySampleCounters,
62                          enum PerfScriptType SampleType) {
63   std::unique_ptr<ProfileGenerator> ProfileGenerator;
64   if (SampleType == PERF_LBR_STACK) {
65     if (usePseudoProbes(BinarySampleCounters)) {
66       ProfileGenerator.reset(
67           new PseudoProbeCSProfileGenerator(BinarySampleCounters));
68     } else {
69       ProfileGenerator.reset(new CSProfileGenerator(BinarySampleCounters));
70     }
71   } else {
72     // TODO:
73     llvm_unreachable("Unsupported perfscript!");
74   }
75 
76   return ProfileGenerator;
77 }
78 
79 void ProfileGenerator::write() {
80   auto WriterOrErr = SampleProfileWriter::create(OutputFilename, OutputFormat);
81   if (std::error_code EC = WriterOrErr.getError())
82     exitWithError(EC, OutputFilename);
83   auto Writer = std::move(WriterOrErr.get());
84   mergeAndTrimColdProfile(ProfileMap);
85   Writer->write(ProfileMap);
86 }
87 
88 void ProfileGenerator::findDisjointRanges(RangeSample &DisjointRanges,
89                                           const RangeSample &Ranges) {
90 
91   /*
92   Regions may overlap with each other. Using the boundary info, find all
93   disjoint ranges and their sample count. BoundaryPoint contains the count
94   multiple samples begin/end at this points.
95 
96   |<--100-->|           Sample1
97   |<------200------>|   Sample2
98   A         B       C
99 
100   In the example above,
101   Sample1 begins at A, ends at B, its value is 100.
102   Sample2 beings at A, ends at C, its value is 200.
103   For A, BeginCount is the sum of sample begins at A, which is 300 and no
104   samples ends at A, so EndCount is 0.
105   Then boundary points A, B, and C with begin/end counts are:
106   A: (300, 0)
107   B: (0, 100)
108   C: (0, 200)
109   */
110   struct BoundaryPoint {
111     // Sum of sample counts beginning at this point
112     uint64_t BeginCount;
113     // Sum of sample counts ending at this point
114     uint64_t EndCount;
115 
116     BoundaryPoint() : BeginCount(0), EndCount(0){};
117 
118     void addBeginCount(uint64_t Count) { BeginCount += Count; }
119 
120     void addEndCount(uint64_t Count) { EndCount += Count; }
121   };
122 
123   /*
124   For the above example. With boundary points, follwing logic finds two
125   disjoint region of
126 
127   [A,B]:   300
128   [B+1,C]: 200
129 
130   If there is a boundary point that both begin and end, the point itself
131   becomes a separate disjoint region. For example, if we have original
132   ranges of
133 
134   |<--- 100 --->|
135                 |<--- 200 --->|
136   A             B             C
137 
138   there are three boundary points with their begin/end counts of
139 
140   A: (100, 0)
141   B: (200, 100)
142   C: (0, 200)
143 
144   the disjoint ranges would be
145 
146   [A, B-1]: 100
147   [B, B]:   300
148   [B+1, C]: 200.
149   */
150   std::map<uint64_t, BoundaryPoint> Boundaries;
151 
152   for (auto Item : Ranges) {
153     uint64_t Begin = Item.first.first;
154     uint64_t End = Item.first.second;
155     uint64_t Count = Item.second;
156     if (Boundaries.find(Begin) == Boundaries.end())
157       Boundaries[Begin] = BoundaryPoint();
158     Boundaries[Begin].addBeginCount(Count);
159 
160     if (Boundaries.find(End) == Boundaries.end())
161       Boundaries[End] = BoundaryPoint();
162     Boundaries[End].addEndCount(Count);
163   }
164 
165   uint64_t BeginAddress = 0;
166   int Count = 0;
167   for (auto Item : Boundaries) {
168     uint64_t Address = Item.first;
169     BoundaryPoint &Point = Item.second;
170     if (Point.BeginCount) {
171       if (BeginAddress)
172         DisjointRanges[{BeginAddress, Address - 1}] = Count;
173       Count += Point.BeginCount;
174       BeginAddress = Address;
175     }
176     if (Point.EndCount) {
177       assert(BeginAddress && "First boundary point cannot be 'end' point");
178       DisjointRanges[{BeginAddress, Address}] = Count;
179       Count -= Point.EndCount;
180       BeginAddress = Address + 1;
181     }
182   }
183 }
184 
185 FunctionSamples &
186 CSProfileGenerator::getFunctionProfileForContext(StringRef ContextStr) {
187   auto Ret = ProfileMap.try_emplace(ContextStr, FunctionSamples());
188   if (Ret.second) {
189     SampleContext FContext(Ret.first->first(), RawContext);
190     FunctionSamples &FProfile = Ret.first->second;
191     FProfile.setName(FContext.getNameWithoutContext());
192     FProfile.setContext(FContext);
193   }
194   return Ret.first->second;
195 }
196 
197 void CSProfileGenerator::updateBodySamplesforFunctionProfile(
198     FunctionSamples &FunctionProfile, const FrameLocation &LeafLoc,
199     uint64_t Count) {
200   // Filter out invalid negative(int type) lineOffset
201   if (LeafLoc.second.LineOffset & 0x80000000)
202     return;
203   // Use the maximum count of samples with same line location
204   ErrorOr<uint64_t> R = FunctionProfile.findSamplesAt(
205       LeafLoc.second.LineOffset, LeafLoc.second.Discriminator);
206   uint64_t PreviousCount = R ? R.get() : 0;
207   if (PreviousCount < Count) {
208     FunctionProfile.addBodySamples(LeafLoc.second.LineOffset,
209                                    LeafLoc.second.Discriminator,
210                                    Count - PreviousCount);
211     FunctionProfile.addTotalSamples(Count - PreviousCount);
212   }
213 }
214 
215 void CSProfileGenerator::populateFunctionBodySamples(
216     FunctionSamples &FunctionProfile, const RangeSample &RangeCounter,
217     ProfiledBinary *Binary) {
218   // Compute disjoint ranges first, so we can use MAX
219   // for calculating count for each location.
220   RangeSample Ranges;
221   findDisjointRanges(Ranges, RangeCounter);
222   for (auto Range : Ranges) {
223     uint64_t RangeBegin = Binary->offsetToVirtualAddr(Range.first.first);
224     uint64_t RangeEnd = Binary->offsetToVirtualAddr(Range.first.second);
225     uint64_t Count = Range.second;
226     // Disjoint ranges have introduce zero-filled gap that
227     // doesn't belong to current context, filter them out.
228     if (Count == 0)
229       continue;
230 
231     InstructionPointer IP(Binary, RangeBegin, true);
232 
233     // Disjoint ranges may have range in the middle of two instr,
234     // e.g. If Instr1 at Addr1, and Instr2 at Addr2, disjoint range
235     // can be Addr1+1 to Addr2-1. We should ignore such range.
236     if (IP.Address > RangeEnd)
237       continue;
238 
239     while (IP.Address <= RangeEnd) {
240       uint64_t Offset = Binary->virtualAddrToOffset(IP.Address);
241       const FrameLocation &LeafLoc = Binary->getInlineLeafFrameLoc(Offset);
242       // Recording body sample for this specific context
243       updateBodySamplesforFunctionProfile(FunctionProfile, LeafLoc, Count);
244       // Move to next IP within the range
245       IP.advance();
246     }
247   }
248 }
249 
250 void CSProfileGenerator::populateFunctionBoundarySamples(
251     StringRef ContextId, FunctionSamples &FunctionProfile,
252     const BranchSample &BranchCounters, ProfiledBinary *Binary) {
253 
254   for (auto Entry : BranchCounters) {
255     uint64_t SourceOffset = Entry.first.first;
256     uint64_t TargetOffset = Entry.first.second;
257     uint64_t Count = Entry.second;
258     // Get the callee name by branch target if it's a call branch
259     StringRef CalleeName = FunctionSamples::getCanonicalFnName(
260         Binary->getFuncFromStartOffset(TargetOffset));
261     if (CalleeName.size() == 0)
262       continue;
263 
264     // Record called target sample and its count
265     const FrameLocation &LeafLoc = Binary->getInlineLeafFrameLoc(SourceOffset);
266     FunctionProfile.addCalledTargetSamples(LeafLoc.second.LineOffset,
267                                            LeafLoc.second.Discriminator,
268                                            CalleeName, Count);
269 
270     // Record head sample for called target(callee)
271     // TODO: Cleanup ' @ '
272     std::string CalleeContextId =
273         getCallSite(LeafLoc) + " @ " + CalleeName.str();
274     if (ContextId.find(" @ ") != StringRef::npos) {
275       CalleeContextId =
276           ContextId.rsplit(" @ ").first.str() + " @ " + CalleeContextId;
277     }
278 
279     FunctionSamples &CalleeProfile =
280         getFunctionProfileForContext(CalleeContextId);
281     assert(Count != 0 && "Unexpected zero weight branch");
282     CalleeProfile.addHeadSamples(Count);
283   }
284 }
285 
286 static FrameLocation getCallerContext(StringRef CalleeContext,
287                                       StringRef &CallerNameWithContext) {
288   StringRef CallerContext = CalleeContext.rsplit(" @ ").first;
289   CallerNameWithContext = CallerContext.rsplit(':').first;
290   auto ContextSplit = CallerContext.rsplit(" @ ");
291   StringRef CallerFrameStr = ContextSplit.second.size() == 0
292                                  ? ContextSplit.first
293                                  : ContextSplit.second;
294   FrameLocation LeafFrameLoc = {"", {0, 0}};
295   StringRef Funcname;
296   SampleContext::decodeContextString(CallerFrameStr, Funcname,
297                                      LeafFrameLoc.second);
298   LeafFrameLoc.first = Funcname.str();
299   return LeafFrameLoc;
300 }
301 
302 void CSProfileGenerator::populateInferredFunctionSamples() {
303   for (const auto &Item : ProfileMap) {
304     const StringRef CalleeContext = Item.first();
305     const FunctionSamples &CalleeProfile = Item.second;
306 
307     // If we already have head sample counts, we must have value profile
308     // for call sites added already. Skip to avoid double counting.
309     if (CalleeProfile.getHeadSamples())
310       continue;
311     // If we don't have context, nothing to do for caller's call site.
312     // This could happen for entry point function.
313     if (CalleeContext.find(" @ ") == StringRef::npos)
314       continue;
315 
316     // Infer Caller's frame loc and context ID through string splitting
317     StringRef CallerContextId;
318     FrameLocation &&CallerLeafFrameLoc =
319         getCallerContext(CalleeContext, CallerContextId);
320 
321     // It's possible that we haven't seen any sample directly in the caller,
322     // in which case CallerProfile will not exist. But we can't modify
323     // ProfileMap while iterating it.
324     // TODO: created function profile for those callers too
325     if (ProfileMap.find(CallerContextId) == ProfileMap.end())
326       continue;
327     FunctionSamples &CallerProfile = ProfileMap[CallerContextId];
328 
329     // Since we don't have call count for inlined functions, we
330     // estimate it from inlinee's profile using entry body sample.
331     uint64_t EstimatedCallCount = CalleeProfile.getEntrySamples();
332     // If we don't have samples with location, use 1 to indicate live.
333     if (!EstimatedCallCount && !CalleeProfile.getBodySamples().size())
334       EstimatedCallCount = 1;
335     CallerProfile.addCalledTargetSamples(
336         CallerLeafFrameLoc.second.LineOffset,
337         CallerLeafFrameLoc.second.Discriminator, CalleeProfile.getName(),
338         EstimatedCallCount);
339     CallerProfile.addBodySamples(CallerLeafFrameLoc.second.LineOffset,
340                                  CallerLeafFrameLoc.second.Discriminator,
341                                  EstimatedCallCount);
342     CallerProfile.addTotalSamples(EstimatedCallCount);
343   }
344 }
345 
346 void CSProfileGenerator::mergeAndTrimColdProfile(
347     StringMap<FunctionSamples> &ProfileMap) {
348   // Nothing to merge if sample threshold is zero
349   if (!CSProfColdThres)
350     return;
351 
352   // Filter the cold profiles from ProfileMap and move them into a tmp
353   // container
354   std::vector<std::pair<StringRef, const FunctionSamples *>> ToRemoveVec;
355   for (const auto &I : ProfileMap) {
356     const FunctionSamples &FunctionProfile = I.second;
357     if (FunctionProfile.getTotalSamples() >= CSProfColdThres)
358       continue;
359     ToRemoveVec.emplace_back(I.getKey(), &I.second);
360   }
361 
362   // Remove the code profile from ProfileMap and merge them into BaseProileMap
363   StringMap<FunctionSamples> BaseProfileMap;
364   for (const auto &I : ToRemoveVec) {
365     auto Ret =
366         BaseProfileMap.try_emplace(I.second->getName(), FunctionSamples());
367     FunctionSamples &BaseProfile = Ret.first->second;
368     BaseProfile.merge(*I.second);
369     ProfileMap.erase(I.first);
370   }
371 
372   // Merge the base profiles into ProfileMap;
373   for (const auto &I : BaseProfileMap) {
374     // Filter the cold base profile
375     if (!CSProfKeepCold && I.second.getTotalSamples() < CSProfColdThres &&
376         ProfileMap.find(I.getKey()) == ProfileMap.end())
377       continue;
378     // Merge the profile if the original profile exists, otherwise just insert
379     // as a new profile
380     FunctionSamples &OrigProfile = getFunctionProfileForContext(I.getKey());
381     StringRef TmpName = OrigProfile.getName();
382     OrigProfile.merge(I.second);
383     // Should use the name ref from ProfileMap's key to avoid name being freed
384     // from BaseProfileMap
385     OrigProfile.setName(TmpName);
386   }
387 }
388 
389 // Helper function to extract context prefix string stack
390 // Extract context stack for reusing, leaf context stack will
391 // be added compressed while looking up function profile
392 static void
393 extractPrefixContextStack(SmallVectorImpl<std::string> &ContextStrStack,
394                           const SmallVectorImpl<const PseudoProbe *> &Probes,
395                           ProfiledBinary *Binary) {
396   for (const auto *P : Probes) {
397     Binary->getInlineContextForProbe(P, ContextStrStack, true);
398   }
399 }
400 
401 void PseudoProbeCSProfileGenerator::generateProfile() {
402   // Enable CS and pseudo probe functionalities in SampleProf
403   FunctionSamples::ProfileIsCS = true;
404   FunctionSamples::ProfileIsProbeBased = true;
405   for (const auto &BI : BinarySampleCounters) {
406     ProfiledBinary *Binary = BI.first;
407     for (const auto &CI : BI.second) {
408       const ProbeBasedCtxKey *CtxKey =
409           dyn_cast<ProbeBasedCtxKey>(CI.first.getPtr());
410       SmallVector<std::string, 16> ContextStrStack;
411       extractPrefixContextStack(ContextStrStack, CtxKey->Probes, Binary);
412       // Fill in function body samples from probes, also infer caller's samples
413       // from callee's probe
414       populateBodySamplesWithProbes(CI.second.RangeCounter, ContextStrStack,
415                                     Binary);
416       // Fill in boundary samples for a call probe
417       populateBoundarySamplesWithProbes(CI.second.BranchCounter,
418                                         ContextStrStack, Binary);
419     }
420   }
421 }
422 
423 void PseudoProbeCSProfileGenerator::extractProbesFromRange(
424     const RangeSample &RangeCounter, ProbeCounterMap &ProbeCounter,
425     ProfiledBinary *Binary) {
426   RangeSample Ranges;
427   findDisjointRanges(Ranges, RangeCounter);
428   for (const auto &Range : Ranges) {
429     uint64_t RangeBegin = Binary->offsetToVirtualAddr(Range.first.first);
430     uint64_t RangeEnd = Binary->offsetToVirtualAddr(Range.first.second);
431     uint64_t Count = Range.second;
432     // Disjoint ranges have introduce zero-filled gap that
433     // doesn't belong to current context, filter them out.
434     if (Count == 0)
435       continue;
436 
437     InstructionPointer IP(Binary, RangeBegin, true);
438 
439     // Disjoint ranges may have range in the middle of two instr,
440     // e.g. If Instr1 at Addr1, and Instr2 at Addr2, disjoint range
441     // can be Addr1+1 to Addr2-1. We should ignore such range.
442     if (IP.Address > RangeEnd)
443       continue;
444 
445     while (IP.Address <= RangeEnd) {
446       const AddressProbesMap &Address2ProbesMap =
447           Binary->getAddress2ProbesMap();
448       auto It = Address2ProbesMap.find(IP.Address);
449       if (It != Address2ProbesMap.end()) {
450         for (const auto &Probe : It->second) {
451           if (!Probe.isBlock())
452             continue;
453           ProbeCounter[&Probe] += Count;
454         }
455       }
456 
457       IP.advance();
458     }
459   }
460 }
461 
462 void PseudoProbeCSProfileGenerator::populateBodySamplesWithProbes(
463     const RangeSample &RangeCounter,
464     SmallVectorImpl<std::string> &ContextStrStack, ProfiledBinary *Binary) {
465   ProbeCounterMap ProbeCounter;
466   // Extract the top frame probes by looking up each address among the range in
467   // the Address2ProbeMap
468   extractProbesFromRange(RangeCounter, ProbeCounter, Binary);
469   for (auto PI : ProbeCounter) {
470     const PseudoProbe *Probe = PI.first;
471     uint64_t Count = PI.second;
472     FunctionSamples &FunctionProfile =
473         getFunctionProfileForLeafProbe(ContextStrStack, Probe, Binary);
474 
475     FunctionProfile.addBodySamples(Probe->Index, 0, Count);
476     FunctionProfile.addTotalSamples(Count);
477     if (Probe->isEntry()) {
478       FunctionProfile.addHeadSamples(Count);
479       // Look up for the caller's function profile
480       const auto *InlinerDesc = Binary->getInlinerDescForProbe(Probe);
481       if (InlinerDesc != nullptr) {
482         // Since the context id will be compressed, we have to use callee's
483         // context id to infer caller's context id to ensure they share the
484         // same context prefix.
485         StringRef CalleeContextId =
486             FunctionProfile.getContext().getNameWithContext(true);
487         StringRef CallerContextId;
488         FrameLocation &&CallerLeafFrameLoc =
489             getCallerContext(CalleeContextId, CallerContextId);
490         uint64_t CallerIndex = CallerLeafFrameLoc.second.LineOffset;
491         assert(CallerIndex &&
492                "Inferred caller's location index shouldn't be zero!");
493         FunctionSamples &CallerProfile =
494             getFunctionProfileForContext(CallerContextId);
495         CallerProfile.setFunctionHash(InlinerDesc->FuncHash);
496         CallerProfile.addBodySamples(CallerIndex, 0, Count);
497         CallerProfile.addTotalSamples(Count);
498         CallerProfile.addCalledTargetSamples(CallerIndex, 0,
499                                              FunctionProfile.getName(), Count);
500       }
501     }
502   }
503 }
504 
505 void PseudoProbeCSProfileGenerator::populateBoundarySamplesWithProbes(
506     const BranchSample &BranchCounter,
507     SmallVectorImpl<std::string> &ContextStrStack, ProfiledBinary *Binary) {
508   for (auto BI : BranchCounter) {
509     uint64_t SourceOffset = BI.first.first;
510     uint64_t TargetOffset = BI.first.second;
511     uint64_t Count = BI.second;
512     uint64_t SourceAddress = Binary->offsetToVirtualAddr(SourceOffset);
513     const PseudoProbe *CallProbe = Binary->getCallProbeForAddr(SourceAddress);
514     if (CallProbe == nullptr)
515       continue;
516     FunctionSamples &FunctionProfile =
517         getFunctionProfileForLeafProbe(ContextStrStack, CallProbe, Binary);
518     FunctionProfile.addBodySamples(CallProbe->Index, 0, Count);
519     FunctionProfile.addTotalSamples(Count);
520     StringRef CalleeName = FunctionSamples::getCanonicalFnName(
521         Binary->getFuncFromStartOffset(TargetOffset));
522     if (CalleeName.size() == 0)
523       continue;
524     FunctionProfile.addCalledTargetSamples(CallProbe->Index, 0, CalleeName,
525                                            Count);
526   }
527 }
528 
529 FunctionSamples &PseudoProbeCSProfileGenerator::getFunctionProfileForLeafProbe(
530     SmallVectorImpl<std::string> &ContextStrStack,
531     const PseudoProbeFuncDesc *LeafFuncDesc) {
532   assert(ContextStrStack.size() && "Profile context must have the leaf frame");
533   // Compress the context string except for the leaf frame
534   std::string LeafFrame = ContextStrStack.back();
535   ContextStrStack.pop_back();
536   CSProfileGenerator::compressRecursionContext(ContextStrStack);
537 
538   std::ostringstream OContextStr;
539   for (uint32_t I = 0; I < ContextStrStack.size(); I++) {
540     if (OContextStr.str().size())
541       OContextStr << " @ ";
542     OContextStr << ContextStrStack[I];
543   }
544   // For leaf inlined context with the top frame, we should strip off the top
545   // frame's probe id, like:
546   // Inlined stack: [foo:1, bar:2], the ContextId will be "foo:1 @ bar"
547   if (OContextStr.str().size())
548     OContextStr << " @ ";
549   OContextStr << StringRef(LeafFrame).split(":").first.str();
550 
551   FunctionSamples &FunctionProile =
552       getFunctionProfileForContext(OContextStr.str());
553   FunctionProile.setFunctionHash(LeafFuncDesc->FuncHash);
554   return FunctionProile;
555 }
556 
557 FunctionSamples &PseudoProbeCSProfileGenerator::getFunctionProfileForLeafProbe(
558     SmallVectorImpl<std::string> &ContextStrStack, const PseudoProbe *LeafProbe,
559     ProfiledBinary *Binary) {
560   // Explicitly copy the context for appending the leaf context
561   SmallVector<std::string, 16> ContextStrStackCopy(ContextStrStack.begin(),
562                                                    ContextStrStack.end());
563   Binary->getInlineContextForProbe(LeafProbe, ContextStrStackCopy);
564   // Note that the context from probe doesn't include leaf frame,
565   // hence we need to retrieve and append the leaf frame.
566   const auto *FuncDesc = Binary->getFuncDescForGUID(LeafProbe->GUID);
567   ContextStrStackCopy.emplace_back(FuncDesc->FuncName + ":" +
568                                    Twine(LeafProbe->Index).str());
569   return getFunctionProfileForLeafProbe(ContextStrStackCopy, FuncDesc);
570 }
571 
572 } // end namespace sampleprof
573 } // end namespace llvm
574