1//===- SampleProfileMatcher.cpp - Sampling-based Stale Profile Matcher ----===//
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 SampleProfileMatcher used for stale
10// profile matching.
11//
12//===----------------------------------------------------------------------===//
13
14#include "llvm/Transforms/IPO/SampleProfileMatcher.h"
15#include "llvm/ADT/Statistic.h"
16#include "llvm/Demangle/Demangle.h"
17#include "llvm/IR/IntrinsicInst.h"
18#include "llvm/IR/MDBuilder.h"
19#include "llvm/Support/CommandLine.h"
20#include "llvm/Transforms/Utils/LongestCommonSequence.h"
21
22#include <unordered_set>
23
24using namespace llvm;
25using namespace sampleprof;
26
27#define DEBUG_TYPE "sample-profile-matcher"
28
29STATISTIC(NumDirectProfileMatch,
30 "Number of functions matched by demangled basename");
31
32namespace llvm {
33
34static cl::opt<unsigned> FuncProfileSimilarityThreshold(
35 "func-profile-similarity-threshold", cl::Hidden, cl::init(Val: 80),
36 cl::desc("Consider a profile matches a function if the similarity of their "
37 "callee sequences is above the specified percentile."));
38
39static cl::opt<unsigned> MinFuncCountForCGMatching(
40 "min-func-count-for-cg-matching", cl::Hidden, cl::init(Val: 5),
41 cl::desc("The minimum number of basic blocks required for a function to "
42 "run stale profile call graph matching."));
43
44static cl::opt<unsigned> MinCallCountForCGMatching(
45 "min-call-count-for-cg-matching", cl::Hidden, cl::init(Val: 3),
46 cl::desc("The minimum number of call anchors required for a function to "
47 "run stale profile call graph matching."));
48
49static cl::opt<bool> LoadFuncProfileforCGMatching(
50 "load-func-profile-for-cg-matching", cl::Hidden, cl::init(Val: true),
51 cl::desc(
52 "Load top-level profiles that the sample reader initially skipped for "
53 "the call-graph matching (only meaningful for extended binary "
54 "format)"));
55
56extern cl::opt<bool> SalvageStaleProfile;
57extern cl::opt<bool> SalvageUnusedProfile;
58extern cl::opt<bool> PersistProfileStaleness;
59extern cl::opt<bool> ReportProfileStaleness;
60
61static cl::opt<unsigned> SalvageUnusedProfileMaxFunctions(
62 "salvage-unused-profile-max-functions", cl::Hidden, cl::init(UINT_MAX),
63 cl::desc("The maximum number of functions in a module, above which salvage "
64 "unused profile will be skipped."));
65
66static cl::opt<unsigned> SalvageStaleProfileMaxCallsites(
67 "salvage-stale-profile-max-callsites", cl::Hidden, cl::init(UINT_MAX),
68 cl::desc("The maximum number of callsites in a function, above which stale "
69 "profile matching will be skipped."));
70
71} // end namespace llvm
72
73void SampleProfileMatcher::findIRAnchors(const Function &F,
74 AnchorMap &IRAnchors) const {
75 // For inlined code, recover the original callsite and callee by finding the
76 // top-level inline frame. e.g. For frame stack "main:1 @ foo:2 @ bar:3", the
77 // top-level frame is "main:1", the callsite is "1" and the callee is "foo".
78 auto FindTopLevelInlinedCallsite = [](const DILocation *DIL) {
79 assert((DIL && DIL->getInlinedAt()) && "No inlined callsite");
80 const DILocation *PrevDIL = nullptr;
81 do {
82 PrevDIL = DIL;
83 DIL = DIL->getInlinedAt();
84 } while (DIL->getInlinedAt());
85
86 LineLocation Callsite = FunctionSamples::getCallSiteIdentifier(
87 DIL, ProfileIsFS: FunctionSamples::ProfileIsFS);
88 StringRef CalleeName = PrevDIL->getSubprogramLinkageName();
89 return std::make_pair(x&: Callsite, y: FunctionId(CalleeName));
90 };
91
92 auto GetCanonicalCalleeName = [](const CallBase *CB) {
93 StringRef CalleeName = UnknownIndirectCallee;
94 if (Function *Callee = CB->getCalledFunction())
95 CalleeName = FunctionSamples::getCanonicalFnName(FnName: Callee->getName());
96 return CalleeName;
97 };
98
99 // Extract profile matching anchors in the IR.
100 for (auto &BB : F) {
101 for (auto &I : BB) {
102 DILocation *DIL = I.getDebugLoc();
103 if (!DIL)
104 continue;
105
106 if (FunctionSamples::ProfileIsProbeBased) {
107 if (auto Probe = extractProbe(Inst: I)) {
108 // Flatten inlined IR for the matching.
109 if (DIL->getInlinedAt()) {
110 IRAnchors.emplace(args: FindTopLevelInlinedCallsite(DIL));
111 } else {
112 // Use empty StringRef for basic block probe.
113 StringRef CalleeName;
114 if (const auto *CB = dyn_cast<CallBase>(Val: &I)) {
115 // Skip the probe inst whose callee name is "llvm.pseudoprobe".
116 if (!isa<IntrinsicInst>(Val: &I))
117 CalleeName = GetCanonicalCalleeName(CB);
118 }
119 LineLocation Loc = LineLocation(Probe->Id, 0);
120 IRAnchors.emplace(args&: Loc, args: FunctionId(CalleeName));
121 }
122 }
123 } else {
124 // TODO: For line-number based profile(AutoFDO), currently only support
125 // find callsite anchors. In future, we need to parse all the non-call
126 // instructions to extract the line locations for profile matching.
127 if (!isa<CallBase>(Val: &I) || isa<IntrinsicInst>(Val: &I))
128 continue;
129
130 if (DIL->getInlinedAt()) {
131 IRAnchors.emplace(args: FindTopLevelInlinedCallsite(DIL));
132 } else {
133 LineLocation Callsite = FunctionSamples::getCallSiteIdentifier(
134 DIL, ProfileIsFS: FunctionSamples::ProfileIsFS);
135 StringRef CalleeName = GetCanonicalCalleeName(dyn_cast<CallBase>(Val: &I));
136 IRAnchors.emplace(args&: Callsite, args: FunctionId(CalleeName));
137 }
138 }
139 }
140 }
141}
142
143void SampleProfileMatcher::findProfileAnchors(const FunctionSamples &FS,
144 AnchorMap &ProfileAnchors) const {
145 auto isInvalidLineOffset = [](uint32_t LineOffset) {
146 return LineOffset & 0x8000;
147 };
148
149 auto InsertAnchor = [](const LineLocation &Loc, const FunctionId &CalleeName,
150 AnchorMap &ProfileAnchors) {
151 auto Ret = ProfileAnchors.try_emplace(k: Loc, args: CalleeName);
152 if (!Ret.second) {
153 // For multiple callees, which indicates it's an indirect call, we use a
154 // dummy name(UnknownIndirectCallee) as the indrect callee name.
155 Ret.first->second = FunctionId(UnknownIndirectCallee);
156 }
157 };
158
159 for (const auto &I : FS.getBodySamples()) {
160 const LineLocation &Loc = I.first;
161 if (isInvalidLineOffset(Loc.LineOffset))
162 continue;
163 for (const auto &C : I.second.getCallTargets())
164 InsertAnchor(Loc, C.first, ProfileAnchors);
165 }
166
167 for (const auto &I : FS.getCallsiteSamples()) {
168 const LineLocation &Loc = I.first;
169 if (isInvalidLineOffset(Loc.LineOffset))
170 continue;
171 for (const auto &C : I.second)
172 InsertAnchor(Loc, C.first, ProfileAnchors);
173 }
174}
175
176bool SampleProfileMatcher::functionHasProfile(const FunctionId &IRFuncName,
177 Function *&FuncWithoutProfile) {
178 FuncWithoutProfile = nullptr;
179 auto R = FunctionsWithoutProfile.find(Key: IRFuncName);
180 if (R != FunctionsWithoutProfile.end())
181 FuncWithoutProfile = R->second;
182 return !FuncWithoutProfile;
183}
184
185bool SampleProfileMatcher::isProfileUnused(const FunctionId &ProfileFuncName) {
186 // In post-link, the profiled function may have been optimized away from the
187 // module. Check if the function name exists in the pseudo_probe descriptors.
188 return (SymbolMap->find(Key: ProfileFuncName) == SymbolMap->end()) &&
189 (LTOPhase == ThinOrFullLTOPhase::ThinLTOPreLink ||
190 !ProfileFuncName.isStringRef() ||
191 (ProbeManager->getDesc(FProfileName: ProfileFuncName.stringRef()) == nullptr));
192}
193
194bool SampleProfileMatcher::functionMatchesProfile(
195 const FunctionId &IRFuncName, const FunctionId &ProfileFuncName,
196 bool FindMatchedProfileOnly) {
197 if (IRFuncName == ProfileFuncName)
198 return true;
199 if (!SalvageUnusedProfile)
200 return false;
201
202 // If IR function doesn't have profile and the profile is unused, try
203 // matching them.
204 Function *IRFunc = nullptr;
205 if (functionHasProfile(IRFuncName, FuncWithoutProfile&: IRFunc) ||
206 !isProfileUnused(ProfileFuncName))
207 return false;
208
209 assert(FunctionId(IRFunc->getName()) != ProfileFuncName &&
210 "IR function should be different from profile function to match");
211 return functionMatchesProfile(IRFunc&: *IRFunc, ProfFunc: ProfileFuncName,
212 FindMatchedProfileOnly);
213}
214
215LocToLocMap
216SampleProfileMatcher::longestCommonSequence(const AnchorList &AnchorList1,
217 const AnchorList &AnchorList2,
218 bool MatchUnusedFunction) {
219 LocToLocMap MatchedAnchors;
220 llvm::longestCommonSequence<LineLocation, FunctionId>(
221 AnchorList1, AnchorList2,
222 FunctionMatchesProfile: [&](const FunctionId &A, const FunctionId &B) {
223 return functionMatchesProfile(
224 IRFuncName: A, ProfileFuncName: B,
225 FindMatchedProfileOnly: !MatchUnusedFunction // Find matched function only
226 );
227 },
228 InsertMatching: [&](LineLocation A, LineLocation B) {
229 MatchedAnchors.try_emplace(k: A, args&: B);
230 });
231 return MatchedAnchors;
232}
233
234void SampleProfileMatcher::matchNonCallsiteLocs(
235 const LocToLocMap &MatchedAnchors, const AnchorMap &IRAnchors,
236 LocToLocMap &IRToProfileLocationMap) {
237 auto InsertMatching = [&](const LineLocation &From, const LineLocation &To) {
238 // Skip the unchanged location mapping to save memory.
239 if (From != To)
240 IRToProfileLocationMap.insert(x: {From, To});
241 };
242
243 // Use function's beginning location as the initial anchor.
244 int32_t LocationDelta = 0;
245 SmallVector<LineLocation> LastMatchedNonAnchors;
246 for (const auto &IR : IRAnchors) {
247 const auto &Loc = IR.first;
248 bool IsMatchedAnchor = false;
249 // Match the anchor location in lexical order.
250 auto R = MatchedAnchors.find(x: Loc);
251 if (R != MatchedAnchors.end()) {
252 const auto &Candidate = R->second;
253 InsertMatching(Loc, Candidate);
254 LLVM_DEBUG(dbgs() << "Callsite with callee:" << IR.second.stringRef()
255 << " is matched from " << Loc << " to " << Candidate
256 << "\n");
257 LocationDelta = Candidate.LineOffset - Loc.LineOffset;
258
259 // Match backwards for non-anchor locations.
260 // The locations in LastMatchedNonAnchors have been matched forwards
261 // based on the previous anchor, spilt it evenly and overwrite the
262 // second half based on the current anchor.
263 for (size_t I = (LastMatchedNonAnchors.size() + 1) / 2;
264 I < LastMatchedNonAnchors.size(); I++) {
265 const auto &L = LastMatchedNonAnchors[I];
266 uint32_t CandidateLineOffset = L.LineOffset + LocationDelta;
267 LineLocation Candidate(CandidateLineOffset, L.Discriminator);
268 InsertMatching(L, Candidate);
269 LLVM_DEBUG(dbgs() << "Location is rematched backwards from " << L
270 << " to " << Candidate << "\n");
271 }
272
273 IsMatchedAnchor = true;
274 LastMatchedNonAnchors.clear();
275 }
276
277 // Match forwards for non-anchor locations.
278 if (!IsMatchedAnchor) {
279 uint32_t CandidateLineOffset = Loc.LineOffset + LocationDelta;
280 LineLocation Candidate(CandidateLineOffset, Loc.Discriminator);
281 InsertMatching(Loc, Candidate);
282 LLVM_DEBUG(dbgs() << "Location is matched from " << Loc << " to "
283 << Candidate << "\n");
284 LastMatchedNonAnchors.emplace_back(Args: Loc);
285 }
286 }
287}
288
289// Filter the non-call locations from IRAnchors and ProfileAnchors and write
290// them into a list for random access later.
291void SampleProfileMatcher::getFilteredAnchorList(
292 const AnchorMap &IRAnchors, const AnchorMap &ProfileAnchors,
293 AnchorList &FilteredIRAnchorsList, AnchorList &FilteredProfileAnchorList) {
294 for (const auto &I : IRAnchors) {
295 if (I.second.stringRef().empty())
296 continue;
297 FilteredIRAnchorsList.emplace_back(args: I);
298 }
299
300 for (const auto &I : ProfileAnchors)
301 FilteredProfileAnchorList.emplace_back(args: I);
302}
303
304// Call target name anchor based profile fuzzy matching.
305// Input:
306// For IR locations, the anchor is the callee name of direct callsite; For
307// profile locations, it's the call target name for BodySamples or inlinee's
308// profile name for CallsiteSamples.
309// Matching heuristic:
310// First match all the anchors using the diff algorithm, then split the
311// non-anchor locations between the two anchors evenly, first half are matched
312// based on the start anchor, second half are matched based on the end anchor.
313// For example, given:
314// IR locations: [1, 2(foo), 3, 5, 6(bar), 7]
315// Profile locations: [1, 2, 3(foo), 4, 7, 8(bar), 9]
316// The matching gives:
317// [1, 2(foo), 3, 5, 6(bar), 7]
318// | | | | | |
319// [1, 2, 3(foo), 4, 7, 8(bar), 9]
320// The output mapping: [2->3, 3->4, 5->7, 6->8, 7->9].
321void SampleProfileMatcher::runStaleProfileMatching(
322 const Function &F, const AnchorMap &IRAnchors,
323 const AnchorMap &ProfileAnchors, LocToLocMap &IRToProfileLocationMap,
324 bool RunCFGMatching, bool RunCGMatching) {
325 if (!RunCFGMatching && !RunCGMatching)
326 return;
327 LLVM_DEBUG(dbgs() << "Run stale profile matching for " << F.getName()
328 << "\n");
329 assert(IRToProfileLocationMap.empty() &&
330 "Run stale profile matching only once per function");
331
332 AnchorList FilteredProfileAnchorList;
333 AnchorList FilteredIRAnchorsList;
334 getFilteredAnchorList(IRAnchors, ProfileAnchors, FilteredIRAnchorsList,
335 FilteredProfileAnchorList);
336
337 if (FilteredIRAnchorsList.empty() || FilteredProfileAnchorList.empty())
338 return;
339
340 if (FilteredIRAnchorsList.size() > SalvageStaleProfileMaxCallsites ||
341 FilteredProfileAnchorList.size() > SalvageStaleProfileMaxCallsites) {
342 LLVM_DEBUG(dbgs() << "Skip stale profile matching for " << F.getName()
343 << " because the number of callsites in the IR is "
344 << FilteredIRAnchorsList.size()
345 << " and in the profile is "
346 << FilteredProfileAnchorList.size() << "\n");
347 return;
348 }
349
350 // Match the callsite anchors by finding the longest common subsequence
351 // between IR and profile.
352 // Define a match between two anchors as follows:
353 // 1) The function names of anchors are the same.
354 // 2) The similarity between the anchor functions is above a threshold if
355 // RunCGMatching is set.
356 // For 2), we only consider the anchor functions from IR and profile don't
357 // appear on either side to reduce the matching scope. Note that we need to
358 // use IR anchor as base(A side) to align with the order of
359 // IRToProfileLocationMap.
360 LocToLocMap MatchedAnchors =
361 longestCommonSequence(AnchorList1: FilteredIRAnchorsList, AnchorList2: FilteredProfileAnchorList,
362 MatchUnusedFunction: RunCGMatching /* Match unused functions */);
363
364 // CFG level matching:
365 // Apply the callsite matchings to infer matching for the basic
366 // block(non-callsite) locations and write the result to
367 // IRToProfileLocationMap.
368 if (RunCFGMatching)
369 matchNonCallsiteLocs(MatchedAnchors, IRAnchors, IRToProfileLocationMap);
370}
371
372void SampleProfileMatcher::runOnFunction(Function &F) {
373 // We need to use flattened function samples for matching.
374 // Unlike IR, which includes all callsites from the source code, the callsites
375 // in profile only show up when they are hit by samples, i,e. the profile
376 // callsites in one context may differ from those in another context. To get
377 // the maximum number of callsites, we merge the function profiles from all
378 // contexts, aka, the flattened profile to find profile anchors.
379 const auto *FSForMatching = getFlattenedSamplesFor(F);
380 if (SalvageUnusedProfile && !FSForMatching) {
381 // Apply the matching in place to find the new function's matched profile.
382 auto R = FuncToProfileNameMap.find(Key: &F);
383 if (R != FuncToProfileNameMap.end()) {
384 FSForMatching = getFlattenedSamplesFor(Fname: R->second);
385 // Fallback for profiles loaded by functionMatchesProfileHelper but not
386 // yet in FlattenedProfiles. This should be rare now that
387 // functionMatchesProfileHelper flattens after loading.
388 if (!FSForMatching && LoadFuncProfileforCGMatching)
389 FSForMatching = Reader.getSamplesFor(Fname: R->second.stringRef());
390 }
391 }
392 if (!FSForMatching)
393 return;
394
395 // Anchors for IR. It's a map from IR location to callee name, callee name is
396 // empty for non-call instruction and use a dummy name(UnknownIndirectCallee)
397 // for unknown indrect callee name.
398 AnchorMap IRAnchors;
399 findIRAnchors(F, IRAnchors);
400 // Anchors for profile. It's a map from callsite location to a set of callee
401 // name.
402 AnchorMap ProfileAnchors;
403 findProfileAnchors(FS: *FSForMatching, ProfileAnchors);
404
405 // Compute the callsite match states for profile staleness report.
406 if (ReportProfileStaleness || PersistProfileStaleness)
407 recordCallsiteMatchStates(F, IRAnchors, ProfileAnchors, IRToProfileLocationMap: nullptr);
408
409 if (!SalvageStaleProfile)
410 return;
411 // For probe-based profiles, run matching only when profile checksum is
412 // mismatched.
413 bool ChecksumMismatch = FunctionSamples::ProfileIsProbeBased &&
414 !ProbeManager->profileIsValid(F, Samples: *FSForMatching);
415 bool RunCFGMatching =
416 !FunctionSamples::ProfileIsProbeBased || ChecksumMismatch;
417 bool RunCGMatching = SalvageUnusedProfile;
418 // For imported functions, the checksum metadata(pseudo_probe_desc) are
419 // dropped, so we leverage function attribute(profile-checksum-mismatch) to
420 // transfer the info: add the attribute during pre-link phase and check it
421 // during post-link phase(see "profileIsValid").
422 if (ChecksumMismatch && LTOPhase == ThinOrFullLTOPhase::ThinLTOPreLink)
423 F.addFnAttr(Kind: "profile-checksum-mismatch");
424
425 // The matching result will be saved to IRToProfileLocationMap, create a
426 // new map for each function.
427 auto &IRToProfileLocationMap = getIRToProfileLocationMap(FS: *FSForMatching);
428 runStaleProfileMatching(F, IRAnchors, ProfileAnchors, IRToProfileLocationMap,
429 RunCFGMatching, RunCGMatching);
430 // Find and update callsite match states after matching.
431 if (RunCFGMatching && (ReportProfileStaleness || PersistProfileStaleness))
432 recordCallsiteMatchStates(F, IRAnchors, ProfileAnchors,
433 IRToProfileLocationMap: &IRToProfileLocationMap);
434}
435
436void SampleProfileMatcher::recordCallsiteMatchStates(
437 const Function &F, const AnchorMap &IRAnchors,
438 const AnchorMap &ProfileAnchors,
439 const LocToLocMap *IRToProfileLocationMap) {
440 bool IsPostMatch = IRToProfileLocationMap != nullptr;
441 auto &CallsiteMatchStates =
442 FuncCallsiteMatchStates[FunctionSamples::getCanonicalFnName(FnName: F.getName())];
443
444 auto MapIRLocToProfileLoc = [&](const LineLocation &IRLoc) {
445 // IRToProfileLocationMap is null in pre-match phrase.
446 if (!IRToProfileLocationMap)
447 return IRLoc;
448 const auto &ProfileLoc = IRToProfileLocationMap->find(x: IRLoc);
449 if (ProfileLoc != IRToProfileLocationMap->end())
450 return ProfileLoc->second;
451 else
452 return IRLoc;
453 };
454
455 for (const auto &I : IRAnchors) {
456 // After fuzzy profile matching, use the matching result to remap the
457 // current IR callsite.
458 const auto &ProfileLoc = MapIRLocToProfileLoc(I.first);
459 const auto &IRCalleeId = I.second;
460 const auto &It = ProfileAnchors.find(x: ProfileLoc);
461 if (It == ProfileAnchors.end())
462 continue;
463 const auto &ProfCalleeId = It->second;
464 if (IRCalleeId == ProfCalleeId) {
465 auto It = CallsiteMatchStates.find(x: ProfileLoc);
466 if (It == CallsiteMatchStates.end())
467 CallsiteMatchStates.emplace(args: ProfileLoc, args: MatchState::InitialMatch);
468 else if (IsPostMatch) {
469 if (It->second == MatchState::InitialMatch)
470 It->second = MatchState::UnchangedMatch;
471 else if (It->second == MatchState::InitialMismatch)
472 It->second = MatchState::RecoveredMismatch;
473 }
474 }
475 }
476
477 // Check if there are any callsites in the profile that does not match to any
478 // IR callsites.
479 for (const auto &I : ProfileAnchors) {
480 const auto &Loc = I.first;
481 assert(!I.second.stringRef().empty() && "Callees should not be empty");
482 auto It = CallsiteMatchStates.find(x: Loc);
483 if (It == CallsiteMatchStates.end())
484 CallsiteMatchStates.emplace(args: Loc, args: MatchState::InitialMismatch);
485 else if (IsPostMatch) {
486 // Update the state if it's not matched(UnchangedMatch or
487 // RecoveredMismatch).
488 if (It->second == MatchState::InitialMismatch)
489 It->second = MatchState::UnchangedMismatch;
490 else if (It->second == MatchState::InitialMatch)
491 It->second = MatchState::RemovedMatch;
492 }
493 }
494}
495
496void SampleProfileMatcher::countMismatchedFuncSamples(const FunctionSamples &FS,
497 bool IsTopLevel) {
498 const auto *FuncDesc = ProbeManager->getDesc(GUID: FS.getGUID());
499 // Skip the function that is external or renamed.
500 if (!FuncDesc)
501 return;
502
503 if (ProbeManager->profileIsHashMismatched(FuncDesc: *FuncDesc, Samples: FS)) {
504 if (IsTopLevel)
505 NumStaleProfileFunc++;
506 // Given currently all probe ids are after block probe ids, once the
507 // checksum is mismatched, it's likely all the callites are mismatched and
508 // dropped. We conservatively count all the samples as mismatched and stop
509 // counting the inlinees' profiles.
510 MismatchedFunctionSamples += FS.getTotalSamples();
511 return;
512 }
513
514 // Even the current-level function checksum is matched, it's possible that the
515 // nested inlinees' checksums are mismatched that affect the inlinee's sample
516 // loading, we need to go deeper to check the inlinees' function samples.
517 // Similarly, count all the samples as mismatched if the inlinee's checksum is
518 // mismatched using this recursive function.
519 for (const auto &I : FS.getCallsiteSamples())
520 for (const auto &CS : I.second)
521 countMismatchedFuncSamples(FS: CS.second, IsTopLevel: false);
522}
523
524void SampleProfileMatcher::countMismatchedCallsiteSamples(
525 const FunctionSamples &FS) {
526 auto It = FuncCallsiteMatchStates.find(Key: FS.getFuncName());
527 // Skip it if no mismatched callsite or this is an external function.
528 if (It == FuncCallsiteMatchStates.end() || It->second.empty())
529 return;
530 const auto &CallsiteMatchStates = It->second;
531
532 auto findMatchState = [&](const LineLocation &Loc) {
533 auto It = CallsiteMatchStates.find(x: Loc);
534 if (It == CallsiteMatchStates.end())
535 return MatchState::Unknown;
536 return It->second;
537 };
538
539 auto AttributeMismatchedSamples = [&](const enum MatchState &State,
540 uint64_t Samples) {
541 if (isMismatchState(State))
542 MismatchedCallsiteSamples += Samples;
543 else if (State == MatchState::RecoveredMismatch)
544 RecoveredCallsiteSamples += Samples;
545 };
546
547 // The non-inlined callsites are saved in the body samples of function
548 // profile, go through it to count the non-inlined callsite samples.
549 for (const auto &I : FS.getBodySamples())
550 AttributeMismatchedSamples(findMatchState(I.first), I.second.getSamples());
551
552 // Count the inlined callsite samples.
553 for (const auto &I : FS.getCallsiteSamples()) {
554 auto State = findMatchState(I.first);
555 uint64_t CallsiteSamples = 0;
556 for (const auto &CS : I.second)
557 CallsiteSamples += CS.second.getTotalSamples();
558 AttributeMismatchedSamples(State, CallsiteSamples);
559
560 if (isMismatchState(State))
561 continue;
562
563 // When the current level of inlined call site matches the profiled call
564 // site, we need to go deeper along the inline tree to count mismatches from
565 // lower level inlinees.
566 for (const auto &CS : I.second)
567 countMismatchedCallsiteSamples(FS: CS.second);
568 }
569}
570
571void SampleProfileMatcher::countMismatchCallsites(const FunctionSamples &FS) {
572 auto It = FuncCallsiteMatchStates.find(Key: FS.getFuncName());
573 // Skip it if no mismatched callsite or this is an external function.
574 if (It == FuncCallsiteMatchStates.end() || It->second.empty())
575 return;
576 const auto &MatchStates = It->second;
577 [[maybe_unused]] bool OnInitialState =
578 isInitialState(State: MatchStates.begin()->second);
579 for (const auto &I : MatchStates) {
580 TotalProfiledCallsites++;
581 assert(
582 (OnInitialState ? isInitialState(I.second) : isFinalState(I.second)) &&
583 "Profile matching state is inconsistent");
584
585 if (isMismatchState(State: I.second))
586 NumMismatchedCallsites++;
587 else if (I.second == MatchState::RecoveredMismatch)
588 NumRecoveredCallsites++;
589 }
590}
591
592void SampleProfileMatcher::countCallGraphRecoveredSamples(
593 const FunctionSamples &FS,
594 std::unordered_set<FunctionId> &CallGraphRecoveredProfiles) {
595 if (CallGraphRecoveredProfiles.count(x: FS.getFunction())) {
596 NumCallGraphRecoveredFuncSamples += FS.getTotalSamples();
597 return;
598 }
599
600 for (const auto &CM : FS.getCallsiteSamples()) {
601 for (const auto &CS : CM.second) {
602 countCallGraphRecoveredSamples(FS: CS.second, CallGraphRecoveredProfiles);
603 }
604 }
605}
606
607void SampleProfileMatcher::computeAndReportProfileStaleness() {
608 if (!ReportProfileStaleness && !PersistProfileStaleness)
609 return;
610
611 std::unordered_set<FunctionId> CallGraphRecoveredProfiles;
612 if (SalvageUnusedProfile) {
613 for (const auto &I : FuncToProfileNameMap) {
614 CallGraphRecoveredProfiles.insert(x: I.second);
615 if (GlobalValue::isAvailableExternallyLinkage(Linkage: I.first->getLinkage()))
616 continue;
617 NumCallGraphRecoveredProfiledFunc++;
618 }
619 }
620
621 // Count profile mismatches for profile staleness report.
622 for (const auto &F : M) {
623 if (skipProfileForFunction(F))
624 continue;
625 // As the stats will be merged by linker, skip reporting the metrics for
626 // imported functions to avoid repeated counting.
627 if (GlobalValue::isAvailableExternallyLinkage(Linkage: F.getLinkage()))
628 continue;
629 const auto *FS = Reader.getSamplesFor(F);
630 if (!FS)
631 continue;
632 TotalProfiledFunc++;
633 TotalFunctionSamples += FS->getTotalSamples();
634
635 if (SalvageUnusedProfile && !CallGraphRecoveredProfiles.empty())
636 countCallGraphRecoveredSamples(FS: *FS, CallGraphRecoveredProfiles);
637
638 // Checksum mismatch is only used in pseudo-probe mode.
639 if (FunctionSamples::ProfileIsProbeBased)
640 countMismatchedFuncSamples(FS: *FS, IsTopLevel: true);
641
642 // Count mismatches and samples for calliste.
643 countMismatchCallsites(FS: *FS);
644 countMismatchedCallsiteSamples(FS: *FS);
645 }
646
647 if (ReportProfileStaleness) {
648 if (FunctionSamples::ProfileIsProbeBased) {
649 errs() << "(" << NumStaleProfileFunc << "/" << TotalProfiledFunc
650 << ") of functions' profile are invalid and ("
651 << MismatchedFunctionSamples << "/" << TotalFunctionSamples
652 << ") of samples are discarded due to function hash mismatch.\n";
653 }
654 if (SalvageUnusedProfile) {
655 errs() << "(" << NumCallGraphRecoveredProfiledFunc << "/"
656 << TotalProfiledFunc << ") of functions' profile are matched and ("
657 << NumCallGraphRecoveredFuncSamples << "/" << TotalFunctionSamples
658 << ") of samples are reused by call graph matching.\n";
659 }
660
661 errs() << "(" << (NumMismatchedCallsites + NumRecoveredCallsites) << "/"
662 << TotalProfiledCallsites
663 << ") of callsites' profile are invalid and ("
664 << (MismatchedCallsiteSamples + RecoveredCallsiteSamples) << "/"
665 << TotalFunctionSamples
666 << ") of samples are discarded due to callsite location mismatch.\n";
667 errs() << "(" << NumRecoveredCallsites << "/"
668 << (NumRecoveredCallsites + NumMismatchedCallsites)
669 << ") of callsites and (" << RecoveredCallsiteSamples << "/"
670 << (RecoveredCallsiteSamples + MismatchedCallsiteSamples)
671 << ") of samples are recovered by stale profile matching.\n";
672 }
673
674 if (PersistProfileStaleness) {
675 LLVMContext &Ctx = M.getContext();
676 MDBuilder MDB(Ctx);
677
678 SmallVector<std::pair<StringRef, uint64_t>> ProfStatsVec;
679 if (FunctionSamples::ProfileIsProbeBased) {
680 ProfStatsVec.emplace_back(Args: "NumStaleProfileFunc", Args&: NumStaleProfileFunc);
681 ProfStatsVec.emplace_back(Args: "TotalProfiledFunc", Args&: TotalProfiledFunc);
682 ProfStatsVec.emplace_back(Args: "MismatchedFunctionSamples",
683 Args&: MismatchedFunctionSamples);
684 ProfStatsVec.emplace_back(Args: "TotalFunctionSamples", Args&: TotalFunctionSamples);
685 }
686
687 if (SalvageUnusedProfile) {
688 ProfStatsVec.emplace_back(Args: "NumCallGraphRecoveredProfiledFunc",
689 Args&: NumCallGraphRecoveredProfiledFunc);
690 ProfStatsVec.emplace_back(Args: "NumCallGraphRecoveredFuncSamples",
691 Args&: NumCallGraphRecoveredFuncSamples);
692 }
693
694 ProfStatsVec.emplace_back(Args: "NumMismatchedCallsites", Args&: NumMismatchedCallsites);
695 ProfStatsVec.emplace_back(Args: "NumRecoveredCallsites", Args&: NumRecoveredCallsites);
696 ProfStatsVec.emplace_back(Args: "TotalProfiledCallsites", Args&: TotalProfiledCallsites);
697 ProfStatsVec.emplace_back(Args: "MismatchedCallsiteSamples",
698 Args&: MismatchedCallsiteSamples);
699 ProfStatsVec.emplace_back(Args: "RecoveredCallsiteSamples",
700 Args&: RecoveredCallsiteSamples);
701
702 auto *MD = MDB.createLLVMStats(LLVMStatsVec: ProfStatsVec);
703 auto *NMD = M.getOrInsertNamedMetadata(Name: "llvm.stats");
704 NMD->addOperand(M: MD);
705 }
706}
707
708void SampleProfileMatcher::findFunctionsWithoutProfile() {
709 // TODO: Support MD5 profile.
710 if (FunctionSamples::UseMD5)
711 return;
712 StringSet<> NamesInProfile;
713 if (auto NameTable = Reader.getNameTable()) {
714 for (auto Name : *NameTable)
715 NamesInProfile.insert(key: Name.stringRef());
716 }
717
718 for (auto &F : M) {
719 // Skip declarations, as even if the function can be matched, we have
720 // nothing to do with it.
721 if (F.isDeclaration())
722 continue;
723
724 StringRef CanonFName = FunctionSamples::getCanonicalFnName(FnName: F.getName());
725 const auto *FS = getFlattenedSamplesFor(F);
726 if (FS)
727 continue;
728
729 // For extended binary, functions fully inlined may not be loaded in the
730 // top-level profile, so check the NameTable which has the all symbol names
731 // in profile.
732 if (NamesInProfile.count(Key: CanonFName))
733 continue;
734
735 // For extended binary, non-profiled function symbols are in the profile
736 // symbol list table.
737 if (PSL && PSL->contains(Name: CanonFName))
738 continue;
739
740 LLVM_DEBUG(dbgs() << "Function " << CanonFName
741 << " is not in profile or profile symbol list.\n");
742 FunctionsWithoutProfile[FunctionId(CanonFName)] = &F;
743 }
744}
745
746// Demangle \p FName and return the base function name (stripping namespaces,
747// templates, and parameter types). Returns an empty string on failure.
748static std::string getDemangledBaseName(ItaniumPartialDemangler &Demangler,
749 StringRef FName) {
750 auto FunctionName = FName.str();
751 if (Demangler.partialDemangle(MangledName: FunctionName.c_str()))
752 return std::string();
753 size_t BaseNameSize = 0;
754 // The demangler API follows the __cxa_demangle one, and thus needs a
755 // pointer that originates from malloc (or nullptr) and the caller is
756 // responsible for free()-ing the buffer.
757 char *BaseNamePtr = Demangler.getFunctionBaseName(Buf: nullptr, N: &BaseNameSize);
758 std::string Result = (BaseNamePtr && BaseNameSize)
759 ? std::string(BaseNamePtr, BaseNameSize)
760 : std::string();
761 free(ptr: BaseNamePtr);
762 // Trim trailing whitespace/null — getFunctionBaseName may include trailing
763 // characters in the reported size.
764 while (!Result.empty() && (Result.back() == ' ' || Result.back() == '\0'))
765 Result.pop_back();
766 return Result;
767}
768
769void SampleProfileMatcher::matchFunctionsWithoutProfileByBasename() {
770 if (FunctionsWithoutProfile.empty() || !LoadFuncProfileforCGMatching)
771 return;
772 auto *NameTable = Reader.getNameTable();
773 if (!NameTable)
774 return;
775
776 ItaniumPartialDemangler Demangler;
777
778 // Build a map from demangled basename to orphan function. Only keep
779 // basenames that map to exactly one orphan — ambiguous basenames like
780 // "get" or "operator()" would produce false positives.
781 StringMap<Function *> OrphansByBaseName;
782 StringSet<> AmbiguousBaseNames;
783 for (auto &[FuncId, Func] : FunctionsWithoutProfile) {
784 std::string BaseName = getDemangledBaseName(Demangler, FName: Func->getName());
785 if (BaseName.empty() || AmbiguousBaseNames.count(Key: BaseName))
786 continue;
787 auto [It, Inserted] = OrphansByBaseName.try_emplace(Key: BaseName, Args&: Func);
788 if (!Inserted) {
789 // More than one orphan shares this basename — mark ambiguous.
790 OrphansByBaseName.erase(I: It);
791 AmbiguousBaseNames.insert(key: BaseName);
792 }
793 }
794 if (OrphansByBaseName.empty())
795 return;
796
797 // Scan the profile NameTable for candidates whose demangled basename matches
798 // a unique orphan. Use a quick substring check to avoid demangling every
799 // entry. Only keep 1:1 basename matches (exactly one profile candidate).
800 // Maps basename -> profile FunctionId; entries with multiple candidates are
801 // removed.
802 StringMap<FunctionId> CandidateByBaseName;
803 for (auto &ProfileFuncId : *NameTable) {
804 StringRef ProfName = ProfileFuncId.stringRef();
805 if (ProfName.empty())
806 continue;
807 for (auto &[BaseName, _] : OrphansByBaseName) {
808 if (AmbiguousBaseNames.count(Key: BaseName) || !ProfName.contains(Other: BaseName))
809 continue;
810 std::string ProfBaseName = getDemangledBaseName(Demangler, FName: ProfName);
811 if (ProfBaseName != BaseName)
812 continue;
813 auto [It, Inserted] =
814 CandidateByBaseName.try_emplace(Key: BaseName, Args&: ProfileFuncId);
815 if (!Inserted) {
816 // More than one profile entry shares this basename — mark ambiguous.
817 CandidateByBaseName.erase(I: It);
818 AmbiguousBaseNames.insert(key: BaseName);
819 }
820 break;
821 }
822 }
823 if (CandidateByBaseName.empty())
824 return;
825
826 // Load candidate profiles on demand, match, and flatten.
827 DenseSet<StringRef> ToLoad;
828 for (auto &[BaseName, ProfId] : CandidateByBaseName)
829 ToLoad.insert(V: ProfId.stringRef());
830 Reader.read(FuncsToUse: ToLoad);
831
832 unsigned MatchCount = 0;
833 SampleProfileMap NewlyLoadedProfiles;
834 for (auto &[BaseName, ProfId] : CandidateByBaseName) {
835 if (!isProfileUnused(ProfileFuncName: ProfId))
836 continue;
837 Function *OrphanFunc = OrphansByBaseName.lookup(Key: BaseName);
838 if (!OrphanFunc)
839 continue;
840
841 FuncToProfileNameMap[OrphanFunc] = ProfId;
842 if (const auto *FS = Reader.getSamplesFor(Fname: ProfId.stringRef()))
843 NewlyLoadedProfiles.create(Ctx: FS->getFunction()).merge(Other: *FS);
844 MatchCount++;
845 LLVM_DEBUG(dbgs() << "Direct basename match: " << OrphanFunc->getName()
846 << " (IR) -> " << ProfId << " (Profile)"
847 << " [basename: " << BaseName << "]\n");
848 }
849
850 // Flatten newly loaded profiles so inlined callees are available for
851 // subsequent LCS-based CG matching.
852 if (!NewlyLoadedProfiles.empty())
853 ProfileConverter::flattenProfile(InputProfiles: NewlyLoadedProfiles, OutputProfiles&: FlattenedProfiles,
854 ProfileIsCS: FunctionSamples::ProfileIsCS);
855
856 NumDirectProfileMatch += MatchCount;
857 LLVM_DEBUG(dbgs() << "Direct basename matching found " << MatchCount
858 << " matches\n");
859}
860
861bool SampleProfileMatcher::functionMatchesProfileHelper(
862 const Function &IRFunc, const FunctionId &ProfFunc) {
863 // The value is in the range [0, 1]. The bigger the value is, the more similar
864 // two sequences are.
865 float Similarity = 0.0;
866
867 // Match the functions if they have the same base name(after demangling) and
868 // skip the similarity check.
869 ItaniumPartialDemangler Demangler;
870 auto IRBaseName = getDemangledBaseName(Demangler, FName: IRFunc.getName());
871 auto ProfBaseName = getDemangledBaseName(Demangler, FName: ProfFunc.stringRef());
872 if (!IRBaseName.empty() && IRBaseName == ProfBaseName) {
873 LLVM_DEBUG(dbgs() << "The functions " << IRFunc.getName() << "(IR) and "
874 << ProfFunc << "(Profile) share the same base name: "
875 << IRBaseName << ".\n");
876 return true;
877 }
878
879 const auto *FSForMatching = getFlattenedSamplesFor(Fname: ProfFunc);
880 // With extbinary profile format, initial profile loading only reads profile
881 // based on current function names in the module.
882 // However, if a function is renamed, sample loader skips to load its original
883 // profile(which has a different name), we will miss this case. To address
884 // this, we load the top-level profile candidate explicitly for the matching.
885 if (!FSForMatching && LoadFuncProfileforCGMatching) {
886 DenseSet<StringRef> TopLevelFunc({ProfFunc.stringRef()});
887 if (std::error_code EC = Reader.read(FuncsToUse: TopLevelFunc))
888 return false;
889 FSForMatching = Reader.getSamplesFor(Fname: ProfFunc.stringRef());
890 // Flatten the newly loaded profile so its inlined callees get their own
891 // entries in FlattenedProfiles, making them discoverable by subsequent
892 // CG matching steps.
893 if (FSForMatching) {
894 SampleProfileMap TempProfiles;
895 TempProfiles.create(Ctx: FSForMatching->getFunction()).merge(Other: *FSForMatching);
896 ProfileConverter::flattenProfile(InputProfiles: TempProfiles, OutputProfiles&: FlattenedProfiles,
897 ProfileIsCS: FunctionSamples::ProfileIsCS);
898 FSForMatching = getFlattenedSamplesFor(Fname: ProfFunc);
899 }
900 LLVM_DEBUG({
901 if (FSForMatching)
902 dbgs() << "Read top-level function " << ProfFunc
903 << " for call-graph matching\n";
904 });
905 }
906 if (!FSForMatching)
907 return false;
908 // The check for similarity or checksum may not be reliable if the function is
909 // tiny, we use the number of basic block as a proxy for the function
910 // complexity and skip the matching if it's too small.
911 if (IRFunc.size() < MinFuncCountForCGMatching ||
912 FSForMatching->getBodySamples().size() < MinFuncCountForCGMatching)
913 return false;
914
915 // For probe-based function, we first trust the checksum info. If the checksum
916 // doesn't match, we continue checking for similarity.
917 if (FunctionSamples::ProfileIsProbeBased) {
918 const auto *FuncDesc = ProbeManager->getDesc(F: IRFunc);
919 if (FuncDesc &&
920 !ProbeManager->profileIsHashMismatched(FuncDesc: *FuncDesc, Samples: *FSForMatching)) {
921 LLVM_DEBUG(dbgs() << "The checksums for " << IRFunc.getName()
922 << "(IR) and " << ProfFunc << "(Profile) match.\n");
923
924 return true;
925 }
926 }
927
928 AnchorMap IRAnchors;
929 findIRAnchors(F: IRFunc, IRAnchors);
930 AnchorMap ProfileAnchors;
931 findProfileAnchors(FS: *FSForMatching, ProfileAnchors);
932
933 AnchorList FilteredIRAnchorsList;
934 AnchorList FilteredProfileAnchorList;
935 getFilteredAnchorList(IRAnchors, ProfileAnchors, FilteredIRAnchorsList,
936 FilteredProfileAnchorList);
937
938 // Similarly skip the matching if the num of anchors is not enough.
939 if (FilteredIRAnchorsList.size() < MinCallCountForCGMatching ||
940 FilteredProfileAnchorList.size() < MinCallCountForCGMatching)
941 return false;
942
943 // Use the diff algorithm to find the LCS between IR and profile.
944
945 // Don't recursively match the callee function to avoid infinite matching,
946 // callee functions will be handled later since it's processed in top-down
947 // order .
948 LocToLocMap MatchedAnchors =
949 longestCommonSequence(AnchorList1: FilteredIRAnchorsList, AnchorList2: FilteredProfileAnchorList,
950 MatchUnusedFunction: false /* Match unused functions */);
951
952 Similarity = static_cast<float>(MatchedAnchors.size()) /
953 FilteredProfileAnchorList.size();
954
955 LLVM_DEBUG(dbgs() << "The similarity between " << IRFunc.getName()
956 << "(IR) and " << ProfFunc << "(profile) is "
957 << format("%.2f", Similarity) << "\n");
958 assert((Similarity >= 0 && Similarity <= 1.0) &&
959 "Similarity value should be in [0, 1]");
960 return Similarity * 100 > FuncProfileSimilarityThreshold;
961}
962
963// If FindMatchedProfileOnly is set to true, only use the processed function
964// results. This is used for skipping the repeated recursive matching.
965bool SampleProfileMatcher::functionMatchesProfile(Function &IRFunc,
966 const FunctionId &ProfFunc,
967 bool FindMatchedProfileOnly) {
968 auto R = FuncProfileMatchCache.find(x: {&IRFunc, ProfFunc});
969 if (R != FuncProfileMatchCache.end())
970 return R->second;
971
972 if (FindMatchedProfileOnly)
973 return false;
974
975 bool Matched = functionMatchesProfileHelper(IRFunc, ProfFunc);
976 FuncProfileMatchCache[{&IRFunc, ProfFunc}] = Matched;
977 if (Matched) {
978 FuncToProfileNameMap[&IRFunc] = ProfFunc;
979 LLVM_DEBUG(dbgs() << "Function:" << IRFunc.getName()
980 << " matches profile:" << ProfFunc << "\n");
981 }
982
983 return Matched;
984}
985
986void SampleProfileMatcher::UpdateWithSalvagedProfiles() {
987 DenseSet<StringRef> ProfileSalvagedFuncs;
988 // Update FuncNameToProfNameMap and SymbolMap.
989 for (auto &I : FuncToProfileNameMap) {
990 assert(I.first && "New function is null");
991 FunctionId FuncName(I.first->getName());
992 ProfileSalvagedFuncs.insert(V: I.second.stringRef());
993 FuncNameToProfNameMap->emplace(Args&: FuncName, Args&: I.second);
994
995 // We need to remove the old entry to avoid duplicating the function
996 // processing.
997 SymbolMap->erase(Ctx: FuncName);
998 [[maybe_unused]] auto Ret = SymbolMap->emplace(Args&: I.second, Args&: I.first);
999 LLVM_DEBUG({
1000 if (!Ret.second)
1001 dbgs() << "Profile Function " << I.second
1002 << " has already been matched to another IR function.\n";
1003 });
1004 }
1005
1006 // With extbinary profile format, initial profile loading only reads profile
1007 // based on current function names in the module, so we need to load top-level
1008 // profiles for functions with different profile name explicitly after
1009 // function-profile name map is established with stale profile matching.
1010 Reader.read(FuncsToUse: ProfileSalvagedFuncs);
1011 Reader.setFuncNameToProfNameMap(*FuncNameToProfNameMap);
1012}
1013
1014void SampleProfileMatcher::runOnModule() {
1015 ProfileConverter::flattenProfile(InputProfiles: Reader.getProfiles(), OutputProfiles&: FlattenedProfiles,
1016 ProfileIsCS: FunctionSamples::ProfileIsCS);
1017 // Disable SalvageUnusedProfile if the module has an extremely large number of
1018 // functions to limit compile time.
1019 SalvageUnusedProfile =
1020 SalvageUnusedProfile && M.size() < SalvageUnusedProfileMaxFunctions;
1021
1022 if (SalvageUnusedProfile) {
1023 findFunctionsWithoutProfile();
1024 matchFunctionsWithoutProfileByBasename();
1025 }
1026
1027 // Process the matching in top-down order so that the caller matching result
1028 // can be used to the callee matching.
1029 std::vector<Function *> TopDownFunctionList;
1030 TopDownFunctionList.reserve(n: M.size());
1031 buildTopDownFuncOrder(CG, FunctionOrderList&: TopDownFunctionList);
1032 for (auto *F : TopDownFunctionList) {
1033 if (skipProfileForFunction(F: *F))
1034 continue;
1035 runOnFunction(F&: *F);
1036 }
1037
1038 if (SalvageUnusedProfile)
1039 UpdateWithSalvagedProfiles();
1040
1041 if (SalvageStaleProfile)
1042 distributeIRToProfileLocationMap();
1043
1044 computeAndReportProfileStaleness();
1045}
1046
1047void SampleProfileMatcher::distributeIRToProfileLocationMap(
1048 FunctionSamples &FS) {
1049 const auto ProfileMappings = FuncMappings.find(Key: FS.getFuncName());
1050 if (ProfileMappings != FuncMappings.end()) {
1051 FS.setIRToProfileLocationMap(&(ProfileMappings->second));
1052 }
1053
1054 for (auto &Callees :
1055 const_cast<CallsiteSampleMap &>(FS.getCallsiteSamples())) {
1056 for (auto &FS : Callees.second) {
1057 distributeIRToProfileLocationMap(FS&: FS.second);
1058 }
1059 }
1060}
1061
1062// Use a central place to distribute the matching results. Outlined and inlined
1063// profile with the function name will be set to the same pointer.
1064void SampleProfileMatcher::distributeIRToProfileLocationMap() {
1065 for (auto &I : Reader.getProfiles()) {
1066 distributeIRToProfileLocationMap(FS&: I.second);
1067 }
1068}
1069