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