[llvm] [SampleProfileMatcher] Move fuzzy base name matching to a later stage after more precise CG matching (PR #210798)

via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 30 21:56:54 PDT 2026


https://github.com/HighW4y2H3ll updated https://github.com/llvm/llvm-project/pull/210798

>From 704547905fadfaf54a595137497c9debae1c32d4 Mon Sep 17 00:00:00 2001
From: h2h <h2h at meta.com>
Date: Fri, 10 Jul 2026 14:48:50 -0700
Subject: [PATCH 1/8] [NFC][SampleProfileMatcher] Fix naming conventions and
 comments following PR202460

---
 .../Transforms/IPO/SampleProfileMatcher.h     |  7 ++-
 .../Transforms/IPO/SampleProfileMatcher.cpp   | 52 +++++++++----------
 2 files changed, 31 insertions(+), 28 deletions(-)

diff --git a/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h b/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
index 1cce006e260ab..251d5bc7e479b 100644
--- a/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
+++ b/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
@@ -37,8 +37,6 @@ class SampleProfileMatcher {
   // mapping from the source location of current build to the source location
   // in the profile.
   StringMap<LocToLocMap> FuncMappings;
-  // Hash mapping cache for matched anchor pairs in stale profile matching
-  DenseMap<FunctionId, const Function *> MatchedAnchorCache;
 
   // Match state for an anchor/callsite.
   enum class MatchState {
@@ -71,6 +69,11 @@ class SampleProfileMatcher {
   // the new(renamed) function pointer and the value is old(unused) profile
   // name.
   MapVector<Function *, FunctionId> FuncToProfileNameMap;
+  // Mapping from matched(renamed) profile name to IR function to during call
+  // graph matching. This is a reversed FuncToProfileNameMap to track duplicated
+  // profile matching and resolve multiple IR functions being mapped to a single
+  // profile.
+  DenseMap<FunctionId, const Function *> ProfileNameToFuncMap;
 
   // A map pointer to the FuncNameToProfNameMap in SampleProfileLoader,
   // which maps the function name to the matched profile name. This is used
diff --git a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
index 2a6c9ee61efd4..5aef593e561d8 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
@@ -396,59 +396,59 @@ void SampleProfileMatcher::runStaleProfileMatching(
 
   // Scan through the matched anchors to make sure functions and profiles are
   // 1:1 mapped. If the profile has already been mapped to another function
-  // during previous fuzzy matching, create a new profile with the same sample
-  // counts and assumed to be pre-inlined.
+  // during previous fuzzy matching, create a new profile the same as the
+  // flattened profile as a top-level function.
   for (const auto &IR : IRAnchors) {
     bool ProfileConflicted = false;
-    const auto &Loc = IR.first;
-    Function *Callee = M.getFunction(IR.second.stringRef());
-    if (!Callee)
+    const auto &IRLoc = IR.first;
+    Function *IRFunc = M.getFunction(IR.second.stringRef());
+    if (!IRFunc)
       continue;
-    FunctionId ProfAnchor;
-    auto AnchorLoc = MatchedAnchors.find(Loc);
-    if (AnchorLoc == MatchedAnchors.end()) {
+    FunctionId ProfId;
+    auto MatchedLoc = MatchedAnchors.find(IRLoc);
+    if (MatchedLoc == MatchedAnchors.end()) {
       // Search within the module and find if we have conflicts in pre-matched
       // profiles for this anchor
-      auto PreMatched = FuncToProfileNameMap.find(Callee);
+      auto PreMatched = FuncToProfileNameMap.find(IRFunc);
       if (PreMatched == FuncToProfileNameMap.end())
         continue;
-      ProfAnchor = PreMatched->second;
+      ProfId = PreMatched->second;
     } else {
-      const auto &Prof = ProfileAnchors.find(AnchorLoc->second);
-      if (Prof == ProfileAnchors.end())
+      const auto &ProfAnchor = ProfileAnchors.find(MatchedLoc->second);
+      if (ProfAnchor == ProfileAnchors.end())
         continue;
-      ProfAnchor = Prof->second;
+      ProfId = ProfAnchor->second;
     }
 
     // Conflicting profile previously matched
-    auto Cached = MatchedAnchorCache.find(ProfAnchor);
-    if (Cached == MatchedAnchorCache.end())
-      MatchedAnchorCache[ProfAnchor] = Callee;
-    else if (Cached->second != Callee)
+    auto PrevMatched = ProfileNameToFuncMap.find(ProfId);
+    if (PrevMatched == ProfileNameToFuncMap.end())
+      ProfileNameToFuncMap[ProfId] = IRFunc;
+    else if (PrevMatched->second != IRFunc)
       ProfileConflicted = true;
 
     if (ProfileConflicted) {
       // Create a flattened profile using the IR function name to avoid profile
       // name conflicts
-      const auto *FSForMatching = getFlattenedSamplesFor(ProfAnchor);
+      const auto *FSForMatching = getFlattenedSamplesFor(ProfId);
       if (!FSForMatching)
-        FSForMatching = Reader.getSamplesFor(ProfAnchor.stringRef());
+        FSForMatching = Reader.getSamplesFor(ProfId.stringRef());
       if (!FSForMatching)
         continue;
 
-      FunctionId NewAnchor(
+      FunctionId NewProfId(
           FunctionSamples::getCanonicalFnName(IR.second.stringRef()));
-      auto R = FuncProfileMatchCache.find({Callee, NewAnchor});
+      auto R = FuncProfileMatchCache.find({IRFunc, NewProfId});
       if (R != FuncProfileMatchCache.end() && R->second)
         continue;
-      FunctionSamples &NewFS = FlattenedProfiles.create(NewAnchor);
+      FunctionSamples &NewFS = FlattenedProfiles.create(NewProfId);
       NewFS.merge(*FSForMatching);
-      FuncToProfileNameMap[Callee] = NewAnchor;
-      FuncProfileMatchCache[{Callee, NewAnchor}] = true;
+      FuncToProfileNameMap[IRFunc] = NewProfId;
+      FuncProfileMatchCache[{IRFunc, NewProfId}] = true;
 
       // Update profile in the sample profile reader
       SampleProfileMap &Profiles = Reader.getProfiles();
-      SampleContext FContext(NewAnchor);
+      SampleContext FContext(NewProfId);
       auto Res = Profiles.try_emplace(FContext.getHashCode(), FContext, NewFS);
       FunctionSamples &FProfile = Res.first->second;
       FProfile.setContext(FContext);
@@ -928,7 +928,7 @@ void SampleProfileMatcher::matchFunctionsWithoutProfileByBasename() {
       continue;
 
     FuncToProfileNameMap[OrphanFunc] = ProfId;
-    MatchedAnchorCache[ProfId] = OrphanFunc;
+    ProfileNameToFuncMap[ProfId] = OrphanFunc;
     if (const auto *FS = Reader.getSamplesFor(ProfId.stringRef()))
       NewlyLoadedProfiles.create(FS->getFunction()).merge(*FS);
     MatchCount++;

>From f07485310e7de6ebb7df37d73237455941fe77da Mon Sep 17 00:00:00 2001
From: h2h <h2h at meta.com>
Date: Mon, 29 Jun 2026 23:37:06 -0700
Subject: [PATCH 2/8] Fix inconsistent anchor matching in stale profile
 matching

---
 .../Transforms/IPO/SampleProfileMatcher.cpp   |   2 +-
 .../stale-profile-lcs-anchor-overwrite.prof   |   7 ++
 .../stale-profile-lcs-anchor-overwrite.ll     | 118 ++++++++++++++++++
 3 files changed, 126 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/Transforms/SampleProfile/Inputs/stale-profile-lcs-anchor-overwrite.prof
 create mode 100644 llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll

diff --git a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
index 5aef593e561d8..ef9f45552b07f 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
@@ -1065,7 +1065,7 @@ bool SampleProfileMatcher::functionMatchesProfile(Function &IRFunc,
   bool Matched = functionMatchesProfileHelper(IRFunc, ProfFunc);
   FuncProfileMatchCache[{&IRFunc, ProfFunc}] = Matched;
   if (Matched) {
-    FuncToProfileNameMap[&IRFunc] = ProfFunc;
+    FuncToProfileNameMap.try_emplace(&IRFunc, ProfFunc);
     LLVM_DEBUG(dbgs() << "Function:" << IRFunc.getName()
                       << " matches profile:" << ProfFunc << "\n");
   }
diff --git a/llvm/test/Transforms/SampleProfile/Inputs/stale-profile-lcs-anchor-overwrite.prof b/llvm/test/Transforms/SampleProfile/Inputs/stale-profile-lcs-anchor-overwrite.prof
new file mode 100644
index 0000000000000..5f9bc7be80d51
--- /dev/null
+++ b/llvm/test/Transforms/SampleProfile/Inputs/stale-profile-lcs-anchor-overwrite.prof
@@ -0,0 +1,7 @@
+_Z3fooi:29916:31
+ 349: 2 A:1 B:1
+ 350: 1 C:1
+ 6: _Z3bari:1843
+ 380: _Z3barl:323
+  3: _Z6calleei:306
+ !CFGChecksum: 1
diff --git a/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll b/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll
new file mode 100644
index 0000000000000..e01447c29e3de
--- /dev/null
+++ b/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll
@@ -0,0 +1,118 @@
+; REQUIRES: x86_64-linux
+; REQUIRES: asserts
+; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/stale-profile-lcs-anchor-overwrite.prof -o %t.prof
+; RUN: opt < %s -passes=sample-profile -sample-profile-file=%t.prof --salvage-stale-profile --salvage-unused-profile -S --debug-only=sample-profile,sample-profile-matcher,sample-profile-impl 2>&1 | FileCheck %s
+
+; CHECK: Function:_Z3barv matches profile:_Z3bari
+; CHECK: The functions _Z3barv(IR) and _Z3barl(Profile) share the same base name: bar.
+; CHECK: Function:_Z3barv matches profile:_Z3barl
+; CHECK: The functions _Z3barPv(IR) and _Z3barl(Profile) share the same base name: bar.
+; CHECK: Function:_Z3barPv matches profile:_Z3barl
+
+; CHECK: Run stale profile matching for _Z3barPv
+; CHECK: The functions _Z6calleePv(IR) and _Z6calleei(Profile) share the same base name: callee.
+; CHECK: Function:_Z6calleePv matches profile:_Z6calleei
+; CHECK: Location is matched from 1 to 1
+; CHECK: Callsite with callee:_Z6calleePv is matched from 2 to 3
+; CHECK: Run stale profile matching for _Z3barv
+; CHECK: Run stale profile matching for _Z6calleePv
+; CHECK: Function processing order:
+; CHECK: _Z3foov
+; CHECK: _Z3barPv
+; CHECK: _Z6calleePv
+; CHECK: _Z3barv
+
+
+target triple = "x86_64-linux-gnu"
+
+define dso_local noundef ptr @_Z6calleePv(ptr noundef %ptr) #0 !dbg !15 {
+entry:
+  %ptr.addr = alloca ptr, align 8
+  store ptr %ptr, ptr %ptr.addr, align 8
+    #dbg_declare(ptr %ptr.addr, !20, !DIExpression(), !21)
+  call void @llvm.pseudoprobe(i64 7108221232740920931, i64 1, i32 0, i64 -1), !dbg !22
+  ret ptr null, !dbg !22
+}
+
+define dso_local void @_Z3barv() #0 !dbg !23 {
+entry:
+  call void @llvm.pseudoprobe(i64 -1069303473483922844, i64 1, i32 0, i64 -1), !dbg !24
+  ret void, !dbg !24
+}
+
+define dso_local noundef ptr @_Z3barPv(ptr noundef %ptr) #0 !dbg !25 {
+entry:
+  %ptr.addr = alloca ptr, align 8
+  store ptr %ptr, ptr %ptr.addr, align 8
+    #dbg_declare(ptr %ptr.addr, !26, !DIExpression(), !27)
+  call void @llvm.pseudoprobe(i64 5678655469166311522, i64 1, i32 0, i64 -1), !dbg !28
+  %call = call noundef ptr @_Z6calleePv(ptr noundef null), !dbg !29
+  ret ptr %call, !dbg !31
+}
+
+define dso_local noundef ptr @_Z3foov() #0 !dbg !32 {
+entry:
+  call void @llvm.pseudoprobe(i64 9191153033785521275, i64 1, i32 0, i64 -1), !dbg !35
+  call void null(), !dbg !36
+  call void @_Z3barv(), !dbg !38
+  call void null(), !dbg !40
+  %call = call noundef ptr @_Z3barPv(ptr noundef null), !dbg !42
+  ret ptr %call, !dbg !44
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: readwrite)
+declare void @llvm.pseudoprobe(i64, i64, i32, i64) #1
+
+attributes #0 = { "use-sample-profile" }
+attributes #1 = { nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: readwrite) }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!6, !7, !8, !9}
+!llvm.ident = !{!10}
+!llvm.pseudo_probe_desc = !{!11, !12, !13, !14}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, retainedTypes: !2, splitDebugInlining: false, debugInfoForProfiling: true, nameTableKind: None)
+!1 = !DIFile(filename: "test.cc", directory: "/tmp", checksumkind: CSK_MD5, checksum: "e16862f6a655f30cd332532a91f867b6")
+!2 = !{!3}
+!3 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !4, size: 64)
+!4 = !DISubroutineType(types: !5)
+!5 = !{null}
+!6 = !{i32 7, !"Dwarf Version", i32 5}
+!7 = !{i32 2, !"Debug Info Version", i32 3}
+!8 = !{i32 7, !"uwtable", i32 2}
+!9 = !{i32 7, !"frame-pointer", i32 2}
+!10 = !{!"clang"}
+!11 = !{i64 7108221232740920931, i64 4294967295, !"_Z6calleePv"}
+!12 = !{i64 -1069303473483922844, i64 4294967295, !"_Z3barv"}
+!13 = !{i64 5678655469166311522, i64 281479271677951, !"_Z3barPv"}
+!14 = !{i64 9191153033785521275, i64 1125904201809919, !"_Z3foov"}
+!15 = distinct !DISubprogram(name: "callee", linkageName: "_Z6calleePv", scope: !1, file: !1, line: 1, type: !16, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !19)
+!16 = !DISubroutineType(types: !17)
+!17 = !{!18, !18}
+!18 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
+!19 = !{}
+!20 = !DILocalVariable(name: "ptr", arg: 1, scope: !15, file: !1, line: 1, type: !18)
+!21 = !DILocation(line: 1, column: 20, scope: !15)
+!22 = !DILocation(line: 2, column: 5, scope: !15)
+!23 = distinct !DISubprogram(name: "bar", linkageName: "_Z3barv", scope: !1, file: !1, line: 5, type: !4, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!24 = !DILocation(line: 5, column: 13, scope: !23)
+!25 = distinct !DISubprogram(name: "bar", linkageName: "_Z3barPv", scope: !1, file: !1, line: 7, type: !16, scopeLine: 7, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !19)
+!26 = !DILocalVariable(name: "ptr", arg: 1, scope: !25, file: !1, line: 7, type: !18)
+!27 = !DILocation(line: 7, column: 17, scope: !25)
+!28 = !DILocation(line: 8, column: 12, scope: !25)
+!29 = !DILocation(line: 8, column: 12, scope: !30)
+!30 = !DILexicalBlockFile(scope: !25, file: !1, discriminator: 455082007)
+!31 = !DILocation(line: 8, column: 5, scope: !25)
+!32 = distinct !DISubprogram(name: "foo", linkageName: "_Z3foov", scope: !1, file: !1, line: 11, type: !33, scopeLine: 11, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!33 = !DISubroutineType(types: !34)
+!34 = !{!18}
+!35 = !DILocation(line: 12, column: 5, scope: !32)
+!36 = !DILocation(line: 12, column: 5, scope: !37)
+!37 = !DILexicalBlockFile(scope: !32, file: !1, discriminator: 387973143)
+!38 = !DILocation(line: 13, column: 5, scope: !39)
+!39 = !DILexicalBlockFile(scope: !32, file: !1, discriminator: 455082015)
+!40 = !DILocation(line: 14, column: 5, scope: !41)
+!41 = !DILexicalBlockFile(scope: !32, file: !1, discriminator: 387973159)
+!42 = !DILocation(line: 15, column: 12, scope: !43)
+!43 = !DILexicalBlockFile(scope: !32, file: !1, discriminator: 455082031)
+!44 = !DILocation(line: 15, column: 5, scope: !32)

>From 2a8aa932aa5c1f8c7a898b691743320354b40da9 Mon Sep 17 00:00:00 2001
From: h2h <h2h at meta.com>
Date: Sun, 5 Jul 2026 22:32:39 -0700
Subject: [PATCH 3/8] Move FuncToProfileNameMap outside of LCS matching

---
 .../Transforms/IPO/SampleProfileMatcher.cpp   | 12 ++++++------
 .../stale-profile-lcs-anchor-overwrite.ll     | 19 ++++++++++++++++---
 2 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
index ef9f45552b07f..d5677f1ee1784 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
@@ -418,6 +418,10 @@ void SampleProfileMatcher::runStaleProfileMatching(
       if (ProfAnchor == ProfileAnchors.end())
         continue;
       ProfId = ProfAnchor->second;
+      FuncProfileMatchCache[{IRFunc, ProfId}] = true;
+      FuncToProfileNameMap[Callee] = ProfId;
+      LLVM_DEBUG(dbgs() << "Function:" << IRFunc->getName()
+                        << " matches profile:" << ProfId << "\n");
     }
 
     // Conflicting profile previously matched
@@ -1063,12 +1067,8 @@ bool SampleProfileMatcher::functionMatchesProfile(Function &IRFunc,
     return false;
 
   bool Matched = functionMatchesProfileHelper(IRFunc, ProfFunc);
-  FuncProfileMatchCache[{&IRFunc, ProfFunc}] = Matched;
-  if (Matched) {
-    FuncToProfileNameMap.try_emplace(&IRFunc, ProfFunc);
-    LLVM_DEBUG(dbgs() << "Function:" << IRFunc.getName()
-                      << " matches profile:" << ProfFunc << "\n");
-  }
+  if (!Matched)
+    FuncProfileMatchCache[{&IRFunc, ProfFunc}] = Matched;
 
   return Matched;
 }
diff --git a/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll b/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll
index e01447c29e3de..b0d6694312178 100644
--- a/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll
+++ b/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll
@@ -3,12 +3,25 @@
 ; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/stale-profile-lcs-anchor-overwrite.prof -o %t.prof
 ; RUN: opt < %s -passes=sample-profile -sample-profile-file=%t.prof --salvage-stale-profile --salvage-unused-profile -S --debug-only=sample-profile,sample-profile-matcher,sample-profile-impl 2>&1 | FileCheck %s
 
-; CHECK: Function:_Z3barv matches profile:_Z3bari
+; CHECK: Function _Z6calleePv is not in profile or profile symbol list.
+; CHECK: Function _Z3barv is not in profile or profile symbol list.
+; CHECK: Function _Z3barPv is not in profile or profile symbol list.
+; CHECK: Function _Z3foov is not in profile or profile symbol list.
+; CHECK: Direct basename match: _Z6calleePv (IR) -> _Z6calleei (Profile) [basename: callee]
+; CHECK: Direct basename match: _Z3foov (IR) -> _Z3fooi (Profile) [basename: foo]
+; CHECK: Direct basename matching found 2 matches
+; CHECK: Run stale profile matching for _Z3foov
+; CHECK: The functions _Z3barv(IR) and _Z3bari(Profile) share the same base name: bar.
 ; CHECK: The functions _Z3barv(IR) and _Z3barl(Profile) share the same base name: bar.
-; CHECK: Function:_Z3barv matches profile:_Z3barl
 ; CHECK: The functions _Z3barPv(IR) and _Z3barl(Profile) share the same base name: bar.
+; CHECK: Function:_Z3barv matches profile:_Z3bari
 ; CHECK: Function:_Z3barPv matches profile:_Z3barl
-
+; CHECK: Location is matched from 1 to 1
+; CHECK: Location is matched from 2 to 2
+; CHECK: Callsite with callee:_Z3barv is matched from 3 to 6
+; CHECK: Location is rematched backwards from 2 to 5
+; CHECK: Callsite with callee:unknown.indirect.callee is matched from 4 to 349
+; CHECK: Callsite with callee:_Z3barPv is matched from 5 to 380
 ; CHECK: Run stale profile matching for _Z3barPv
 ; CHECK: The functions _Z6calleePv(IR) and _Z6calleei(Profile) share the same base name: callee.
 ; CHECK: Function:_Z6calleePv matches profile:_Z6calleei

>From e1261de7f1c21d76b0f045569895fe8400f896c3 Mon Sep 17 00:00:00 2001
From: h2h <h2h at meta.com>
Date: Mon, 6 Jul 2026 16:21:35 -0700
Subject: [PATCH 4/8] Test fixes

---
 .../Transforms/IPO/SampleProfileMatcher.cpp   | 18 ++++++++---
 ...o-probe-stale-profile-orphan-conflict.prof |  1 +
 ...udo-probe-stale-profile-orphan-conflict.ll | 32 ++++++-------------
 3 files changed, 23 insertions(+), 28 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
index d5677f1ee1784..f3c05b1d5acba 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
@@ -418,10 +418,12 @@ void SampleProfileMatcher::runStaleProfileMatching(
       if (ProfAnchor == ProfileAnchors.end())
         continue;
       ProfId = ProfAnchor->second;
-      FuncProfileMatchCache[{IRFunc, ProfId}] = true;
-      FuncToProfileNameMap[Callee] = ProfId;
-      LLVM_DEBUG(dbgs() << "Function:" << IRFunc->getName()
-                        << " matches profile:" << ProfId << "\n");
+      if (IRFunc->getName() != ProfId.stringRef()) {
+        FuncProfileMatchCache[{IRFunc, ProfId}] = true;
+        FuncToProfileNameMap[Callee] = ProfId;
+        LLVM_DEBUG(dbgs() << "Function:" << IRFunc->getName()
+                          << " matches profile:" << ProfId << "\n");
+      }
     }
 
     // Conflicting profile previously matched
@@ -448,8 +450,14 @@ void SampleProfileMatcher::runStaleProfileMatching(
       FunctionSamples &NewFS = FlattenedProfiles.create(NewProfId);
       NewFS.merge(*FSForMatching);
       FuncToProfileNameMap[IRFunc] = NewProfId;
+      FuncProfileMatchCache[{IRFunc, ProfId}] = false;
       FuncProfileMatchCache[{IRFunc, NewProfId}] = true;
 
+      LLVM_DEBUG(dbgs() << "Function:" << Callee->getName()
+                        << " encounters conflicting profile matchings, "
+                           "remapping to new profile:"
+                        << NewAnchor << "\n");
+
       // Update profile in the sample profile reader
       SampleProfileMap &Profiles = Reader.getProfiles();
       SampleContext FContext(NewProfId);
@@ -717,7 +725,7 @@ void SampleProfileMatcher::computeAndReportProfileStaleness() {
   }
 
   // Count profile mismatches for profile staleness report.
-  for (const auto &F : M) {
+  for (auto &F : M) {
     if (skipProfileForFunction(F))
       continue;
     // As the stats will be merged by linker, skip reporting the metrics for
diff --git a/llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-orphan-conflict.prof b/llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-orphan-conflict.prof
index 4aae1f1f38b3a..bdb08a13db59b 100644
--- a/llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-orphan-conflict.prof
+++ b/llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-orphan-conflict.prof
@@ -1,4 +1,5 @@
 _Z3fooi:1:0
+ 30: _Z3topi:1
  57: _Z3bari:1
  72: _Z3topi:1
   2: _Z3midi:1
diff --git a/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll b/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll
index d7886cc72dc8f..f33eac6249db3 100644
--- a/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll
+++ b/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll
@@ -1,6 +1,6 @@
 ; Test direct basename matching for orphan functions where multiple callee anchors may be
 ; matched to one same profile during stale profile matching. In this test case, both of the
-; `mid` functions will be matched to the same `_Z3midi` function in the profile during stale
+; `top` functions will be matched to the same `_Z3topi` function in the profile during stale
 ; profile matching. This ends up causing an assertation error because only one profile
 ; function is supposed to be matched to an IR function.
 ;
@@ -10,21 +10,14 @@
 ;                          |_ sub
 ; 
 ; Profile Function:
-;   foo: bar ; top ; mid
-;                     |_ sub
+;   foo: top; bar ; top
+;                     |_ mid
+;                          |_ sub
 ; 
 ; Stale profile match order:
 ;   foo:  top<ir>   ; bar<ir>   ; top(2)<ir>
 ;            |           |           |
 ;         top<prof> ; bar<prof> ; top<prof>
-;
-;   top(2)<ir>:  mid(2)<ir>
-;                  |
-;                mid<prof>
-;
-;   top<ir>:  mid<ir>
-;               |
-;             mid<prof>    => (Assertation error)
 
 
 ; REQUIRES: x86_64-linux
@@ -32,23 +25,15 @@
 ; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/pseudo-probe-stale-profile-orphan-conflict.prof -o %t.prof
 ; RUN: opt < %s -passes=sample-profile -sample-profile-file=%t.prof --salvage-stale-profile --salvage-unused-profile -S --debug-only=sample-profile,sample-profile-matcher,sample-profile-impl 2>&1 | FileCheck %s
 
-; CHECK: Function _Z3midl is not in profile or profile symbol list.
-; CHECK: Function _Z3midll is not in profile or profile symbol list.
-; CHECK: Function _Z3topl is not in profile or profile symbol list.
-; CHECK: Function _Z3barl is not in profile or profile symbol list.
-; CHECK: Function _Z3topll is not in profile or profile symbol list.
-; CHECK: Function _Z3fool is not in profile or profile symbol list.
-; CHECK: Direct basename match: _Z3barl (IR) -> _Z3bari (Profile) [basename: bar]
-; CHECK: Direct basename match: _Z3fool (IR) -> _Z3fooi (Profile) [basename: foo]
-; CHECK: Direct basename matching found 2 matches
 ; CHECK: Run stale profile matching for _Z3fool
 ; CHECK: The functions _Z3topl(IR) and _Z3topi(Profile) share the same base name: top.
-; CHECK: Function:_Z3topl matches profile:_Z3topi
 ; CHECK: The functions _Z3barl(IR) and _Z3bari(Profile) share the same base name: bar.
-; CHECK: Function:_Z3barl matches profile:_Z3bari
 ; CHECK: The functions _Z3topll(IR) and _Z3topi(Profile) share the same base name: top.
+; CHECK: Function:_Z3topl matches profile:_Z3topi
+; CHECK: Function:_Z3barl matches profile:_Z3bari
 ; CHECK: Function:_Z3topll matches profile:_Z3topi
-; CHECK: Location is matched from 2 to 2
+; CHECK: Function:_Z3topll encounters conflicting profile matchings, remapping to new profile:_Z3topll
+; CHECK: Callsite with callee:_Z3topl is matched from 2 to 30
 ; CHECK: Callsite with callee:_Z3barl is matched from 3 to 57
 ; CHECK: Callsite with callee:_Z3topll is matched from 4 to 72
 ; CHECK: Run stale profile matching for _Z3topll
@@ -59,6 +44,7 @@
 ; CHECK: Run stale profile matching for _Z3topl
 ; CHECK: The functions _Z3midl(IR) and _Z3midi(Profile) share the same base name: mid.
 ; CHECK: Function:_Z3midl matches profile:_Z3midi
+; CHECK: Function:_Z3midl encounters conflicting profile matchings, remapping to new profile:_Z3midl
 ; CHECK: Callsite with callee:_Z3midl is matched from 1 to 2
 ; CHECK: Run stale profile matching for _Z3midll
 ; CHECK: Callsite with callee:_Z3subi is matched from 1 to 11

>From dd52337287ecfd3c17805a1644feb5803d3c5d29 Mon Sep 17 00:00:00 2001
From: h2h <h2h at meta.com>
Date: Mon, 6 Jul 2026 16:46:41 -0700
Subject: [PATCH 5/8] revert unnecessary changes

---
 llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
index f3c05b1d5acba..6d1e3a2aa7074 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
@@ -725,7 +725,7 @@ void SampleProfileMatcher::computeAndReportProfileStaleness() {
   }
 
   // Count profile mismatches for profile staleness report.
-  for (auto &F : M) {
+  for (const auto &F : M) {
     if (skipProfileForFunction(F))
       continue;
     // As the stats will be merged by linker, skip reporting the metrics for

>From da58ad112c6ad4875768cfb179816bd382175966 Mon Sep 17 00:00:00 2001
From: h2h <h2h at meta.com>
Date: Mon, 20 Jul 2026 10:55:17 -0700
Subject: [PATCH 6/8] Move basename matching after CG matching as a fallback

---
 .../Transforms/IPO/SampleProfileMatcher.h     |  1 +
 .../Transforms/IPO/SampleProfileMatcher.cpp   | 52 +++++++++++++------
 ...udo-probe-stale-profile-orphan-conflict.ll |  1 -
 .../stale-profile-lcs-anchor-overwrite.ll     |  1 -
 4 files changed, 36 insertions(+), 19 deletions(-)

diff --git a/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h b/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
index 251d5bc7e479b..955495edc5363 100644
--- a/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
+++ b/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
@@ -69,6 +69,7 @@ class SampleProfileMatcher {
   // the new(renamed) function pointer and the value is old(unused) profile
   // name.
   MapVector<Function *, FunctionId> FuncToProfileNameMap;
+  MapVector<Function *, FunctionId> FuncToProfileNameMapByBase;
   // Mapping from matched(renamed) profile name to IR function to during call
   // graph matching. This is a reversed FuncToProfileNameMap to track duplicated
   // profile matching and resolve multiple IR functions being mapped to a single
diff --git a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
index 6d1e3a2aa7074..f17960599107e 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
@@ -420,7 +420,7 @@ void SampleProfileMatcher::runStaleProfileMatching(
       ProfId = ProfAnchor->second;
       if (IRFunc->getName() != ProfId.stringRef()) {
         FuncProfileMatchCache[{IRFunc, ProfId}] = true;
-        FuncToProfileNameMap[Callee] = ProfId;
+        FuncToProfileNameMap[IRFunc] = ProfId;
         LLVM_DEBUG(dbgs() << "Function:" << IRFunc->getName()
                           << " matches profile:" << ProfId << "\n");
       }
@@ -453,10 +453,10 @@ void SampleProfileMatcher::runStaleProfileMatching(
       FuncProfileMatchCache[{IRFunc, ProfId}] = false;
       FuncProfileMatchCache[{IRFunc, NewProfId}] = true;
 
-      LLVM_DEBUG(dbgs() << "Function:" << Callee->getName()
+      LLVM_DEBUG(dbgs() << "Function:" << IRFunc->getName()
                         << " encounters conflicting profile matchings, "
                            "remapping to new profile:"
-                        << NewAnchor << "\n");
+                        << NewProfId << "\n");
 
       // Update profile in the sample profile reader
       SampleProfileMap &Profiles = Reader.getProfiles();
@@ -941,6 +941,8 @@ void SampleProfileMatcher::matchFunctionsWithoutProfileByBasename() {
 
     FuncToProfileNameMap[OrphanFunc] = ProfId;
     ProfileNameToFuncMap[ProfId] = OrphanFunc;
+    FuncProfileMatchCache[{OrphanFunc, ProfId}] = true;
+
     if (const auto *FS = Reader.getSamplesFor(ProfId.stringRef()))
       NewlyLoadedProfiles.create(FS->getFunction()).merge(*FS);
     MatchCount++;
@@ -966,18 +968,6 @@ bool SampleProfileMatcher::functionMatchesProfileHelper(
   // two sequences are.
   float Similarity = 0.0;
 
-  // Match the functions if they have the same base name(after demangling) and
-  // skip the similarity check.
-  ItaniumPartialDemangler Demangler;
-  auto IRBaseName = getDemangledBaseName(Demangler, IRFunc.getName());
-  auto ProfBaseName = getDemangledBaseName(Demangler, ProfFunc.stringRef());
-  if (!IRBaseName.empty() && IRBaseName == ProfBaseName) {
-    LLVM_DEBUG(dbgs() << "The functions " << IRFunc.getName() << "(IR) and "
-                      << ProfFunc << "(Profile) share the same base name: "
-                      << IRBaseName << ".\n");
-    return true;
-  }
-
   const auto *FSForMatching = getFlattenedSamplesFor(ProfFunc);
   // With extbinary profile format, initial profile loading only reads profile
   // based on current function names in the module.
@@ -1075,8 +1065,21 @@ bool SampleProfileMatcher::functionMatchesProfile(Function &IRFunc,
     return false;
 
   bool Matched = functionMatchesProfileHelper(IRFunc, ProfFunc);
-  if (!Matched)
+  if (!Matched) {
+    // Match the functions if they have the same base name(after demangling) and
+    // skip the similarity check.
+    ItaniumPartialDemangler Demangler;
+    auto IRBaseName = getDemangledBaseName(Demangler, IRFunc.getName());
+    auto ProfBaseName = getDemangledBaseName(Demangler, ProfFunc.stringRef());
+    if (!IRBaseName.empty() && IRBaseName == ProfBaseName) {
+      LLVM_DEBUG(dbgs() << "The functions " << IRFunc.getName() << "(IR) and "
+                        << ProfFunc << "(Profile) share the same base name: "
+                        << IRBaseName << ".\n");
+      return true;
+    }
+    // Cache IR/Prof function pairs that don't match
     FuncProfileMatchCache[{&IRFunc, ProfFunc}] = Matched;
+  }
 
   return Matched;
 }
@@ -1119,11 +1122,11 @@ void SampleProfileMatcher::runOnModule() {
 
   if (SalvageUnusedProfile) {
     findFunctionsWithoutProfile();
-    matchFunctionsWithoutProfileByBasename();
   }
 
   // Process the matching in top-down order so that the caller matching result
   // can be used to the callee matching.
+  DenseSet<Function *> FunctionProcessedInStage1;
   std::vector<Function *> TopDownFunctionList;
   TopDownFunctionList.reserve(M.size());
   buildTopDownFuncOrder(CG, TopDownFunctionList);
@@ -1131,6 +1134,21 @@ void SampleProfileMatcher::runOnModule() {
     if (skipProfileForFunction(*F))
       continue;
     runOnFunction(*F);
+    if (getFlattenedSamplesFor(*F) || FuncToProfileNameMap.contains(F))
+      FunctionProcessedInStage1.insert(F);
+  }
+
+  // Stage 2 stale profile matching after rematching all the remaining functions
+  // without profile with fuzzy basename matching
+  if (SalvageUnusedProfile) {
+    matchFunctionsWithoutProfileByBasename();
+    for (auto *F : TopDownFunctionList) {
+      if (skipProfileForFunction(*F))
+        continue;
+      if (FunctionProcessedInStage1.contains(F))
+        continue;
+      runOnFunction(*F);
+    }
   }
 
   if (SalvageUnusedProfile)
diff --git a/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll b/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll
index f33eac6249db3..42036ad2856e9 100644
--- a/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll
+++ b/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll
@@ -27,7 +27,6 @@
 
 ; CHECK: Run stale profile matching for _Z3fool
 ; CHECK: The functions _Z3topl(IR) and _Z3topi(Profile) share the same base name: top.
-; CHECK: The functions _Z3barl(IR) and _Z3bari(Profile) share the same base name: bar.
 ; CHECK: The functions _Z3topll(IR) and _Z3topi(Profile) share the same base name: top.
 ; CHECK: Function:_Z3topl matches profile:_Z3topi
 ; CHECK: Function:_Z3barl matches profile:_Z3bari
diff --git a/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll b/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll
index b0d6694312178..4316848283d68 100644
--- a/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll
+++ b/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll
@@ -23,7 +23,6 @@
 ; CHECK: Callsite with callee:unknown.indirect.callee is matched from 4 to 349
 ; CHECK: Callsite with callee:_Z3barPv is matched from 5 to 380
 ; CHECK: Run stale profile matching for _Z3barPv
-; CHECK: The functions _Z6calleePv(IR) and _Z6calleei(Profile) share the same base name: callee.
 ; CHECK: Function:_Z6calleePv matches profile:_Z6calleei
 ; CHECK: Location is matched from 1 to 1
 ; CHECK: Callsite with callee:_Z6calleePv is matched from 2 to 3

>From 232a1920aed847860bb6155b1856c6bb4b213f00 Mon Sep 17 00:00:00 2001
From: h2h <h2h at meta.com>
Date: Mon, 20 Jul 2026 11:34:50 -0700
Subject: [PATCH 7/8] remove unused member variable

---
 llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h b/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
index 955495edc5363..251d5bc7e479b 100644
--- a/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
+++ b/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
@@ -69,7 +69,6 @@ class SampleProfileMatcher {
   // the new(renamed) function pointer and the value is old(unused) profile
   // name.
   MapVector<Function *, FunctionId> FuncToProfileNameMap;
-  MapVector<Function *, FunctionId> FuncToProfileNameMapByBase;
   // Mapping from matched(renamed) profile name to IR function to during call
   // graph matching. This is a reversed FuncToProfileNameMap to track duplicated
   // profile matching and resolve multiple IR functions being mapped to a single

>From 3fdea6602a5c96a291c3ad9822a336ea99983553 Mon Sep 17 00:00:00 2001
From: h2h <h2h at meta.com>
Date: Wed, 30 Sep 2026 21:30:42 -0700
Subject: [PATCH 8/8] refactor & fixings

---
 .../Transforms/IPO/SampleProfileMatcher.h     |   9 ++
 .../Transforms/IPO/SampleProfileMatcher.cpp   | 141 ++++++++++--------
 ...e-profile-orphan-conflict-cross-stage.prof |  11 ++
 ...file-lcs-anchor-overwrite-cross-stage.prof |  11 ++
 ...udo-probe-stale-profile-orphan-conflict.ll |  38 +++--
 .../stale-profile-lcs-anchor-overwrite.ll     |  27 +++-
 6 files changed, 160 insertions(+), 77 deletions(-)
 create mode 100644 llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-orphan-conflict-cross-stage.prof
 create mode 100644 llvm/test/Transforms/SampleProfile/Inputs/stale-profile-lcs-anchor-overwrite-cross-stage.prof

diff --git a/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h b/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
index 251d5bc7e479b..1c387135a6668 100644
--- a/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
+++ b/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
@@ -235,6 +235,15 @@ class SampleProfileMatcher {
   // which are supposed to be new functions. We use them as the targets for
   // call graph matching.
   void findFunctionsWithoutProfile();
+  // Setter function for recording stale profile matching results into
+  // `FuncToProfileNameMap` and `ProfileNameToFuncMap`. The IR function and
+  // Profile function are checked and guaranteed to be 1:1 mapping. A new
+  // profile clone will be created in the SampleReader if multiple IR
+  // functions are found to be mapping to one Profile. Return the updated
+  // profile name that is mapped to the IR function. And update
+  // FuncProfileMatchCache correspondingly.
+  FunctionId recordMatchedStaleProfile(Function *IRFunc,
+                                       FunctionId ProfFuncName);
   // Match orphan IR functions to unused top-level profile entries by demangled
   // basename, without requiring a matched caller in the call graph.
   void matchFunctionsWithoutProfileByBasename();
diff --git a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
index f17960599107e..7aa16f3415186 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
@@ -406,64 +406,28 @@ void SampleProfileMatcher::runStaleProfileMatching(
       continue;
     FunctionId ProfId;
     auto MatchedLoc = MatchedAnchors.find(IRLoc);
-    if (MatchedLoc == MatchedAnchors.end()) {
-      // Search within the module and find if we have conflicts in pre-matched
-      // profiles for this anchor
-      auto PreMatched = FuncToProfileNameMap.find(IRFunc);
-      if (PreMatched == FuncToProfileNameMap.end())
-        continue;
-      ProfId = PreMatched->second;
-    } else {
-      const auto &ProfAnchor = ProfileAnchors.find(MatchedLoc->second);
-      if (ProfAnchor == ProfileAnchors.end())
-        continue;
-      ProfId = ProfAnchor->second;
-      if (IRFunc->getName() != ProfId.stringRef()) {
-        FuncProfileMatchCache[{IRFunc, ProfId}] = true;
-        FuncToProfileNameMap[IRFunc] = ProfId;
-        LLVM_DEBUG(dbgs() << "Function:" << IRFunc->getName()
-                          << " matches profile:" << ProfId << "\n");
-      }
-    }
-
-    // Conflicting profile previously matched
-    auto PrevMatched = ProfileNameToFuncMap.find(ProfId);
-    if (PrevMatched == ProfileNameToFuncMap.end())
-      ProfileNameToFuncMap[ProfId] = IRFunc;
-    else if (PrevMatched->second != IRFunc)
-      ProfileConflicted = true;
-
-    if (ProfileConflicted) {
-      // Create a flattened profile using the IR function name to avoid profile
-      // name conflicts
-      const auto *FSForMatching = getFlattenedSamplesFor(ProfId);
-      if (!FSForMatching)
-        FSForMatching = Reader.getSamplesFor(ProfId.stringRef());
-      if (!FSForMatching)
-        continue;
+    if (MatchedLoc == MatchedAnchors.end())
+      continue;
 
-      FunctionId NewProfId(
-          FunctionSamples::getCanonicalFnName(IR.second.stringRef()));
-      auto R = FuncProfileMatchCache.find({IRFunc, NewProfId});
-      if (R != FuncProfileMatchCache.end() && R->second)
-        continue;
-      FunctionSamples &NewFS = FlattenedProfiles.create(NewProfId);
-      NewFS.merge(*FSForMatching);
-      FuncToProfileNameMap[IRFunc] = NewProfId;
-      FuncProfileMatchCache[{IRFunc, ProfId}] = false;
-      FuncProfileMatchCache[{IRFunc, NewProfId}] = true;
-
-      LLVM_DEBUG(dbgs() << "Function:" << IRFunc->getName()
-                        << " encounters conflicting profile matchings, "
-                           "remapping to new profile:"
-                        << NewProfId << "\n");
-
-      // Update profile in the sample profile reader
-      SampleProfileMap &Profiles = Reader.getProfiles();
-      SampleContext FContext(NewProfId);
-      auto Res = Profiles.try_emplace(FContext.getHashCode(), FContext, NewFS);
-      FunctionSamples &FProfile = Res.first->second;
-      FProfile.setContext(FContext);
+    const auto &ProfAnchor = ProfileAnchors.find(MatchedLoc->second);
+    if (ProfAnchor == ProfileAnchors.end())
+      continue;
+    ProfId = recordMatchedStaleProfile(IRFunc, ProfAnchor->second);
+    if (ProfId != ProfAnchor->second) {
+      // Only load the flattened profile if a new profile is created in the
+      // SampleProfReader
+      FunctionSamples &NewFS = FlattenedProfiles.create(ProfId);
+      SampleProfileMap UpdatedProfile;
+      if (const auto *FS = Reader.getSamplesFor(ProfId.stringRef())) {
+        UpdatedProfile.create(ProfId).merge(*FS);
+        ProfileConverter::flattenProfile(UpdatedProfile,
+                                         FunctionSamples::ProfileIsCS);
+        auto It = UpdatedProfile.find(ProfId);
+        if (It != UpdatedProfile.end()) {
+          FunctionSamples &NewFlatFS = FlattenedProfiles.create(ProfId);
+          NewFlatFS.merge(It->second);
+        }
+      }
     }
   }
 
@@ -844,6 +808,62 @@ void SampleProfileMatcher::findFunctionsWithoutProfile() {
   }
 }
 
+FunctionId
+SampleProfileMatcher::recordMatchedStaleProfile(Function *IRFunc,
+                                                FunctionId ProfFuncName) {
+  // Early return if an IR function has already been matched
+  StringRef IRFuncName = FunctionSamples::getCanonicalFnName(IRFunc->getName());
+  if (IRFuncName == ProfFuncName.stringRef())
+    return ProfFuncName;
+  auto PreMatched = FuncToProfileNameMap.find(IRFunc);
+  if (PreMatched != FuncToProfileNameMap.end())
+    return ProfFuncName;
+
+  // Detect conflicting profile previously matched
+  auto PrevMatched = ProfileNameToFuncMap.find(ProfFuncName);
+  if (PrevMatched == ProfileNameToFuncMap.end()) {
+    FuncProfileMatchCache[{IRFunc, ProfFuncName}] = true;
+    FuncToProfileNameMap[IRFunc] = ProfFuncName;
+    ProfileNameToFuncMap[ProfFuncName] = IRFunc;
+
+    LLVM_DEBUG(dbgs() << "Function:" << IRFunc->getName()
+                      << " matches profile:" << ProfFuncName << "\n");
+
+  } else if (PrevMatched->second != IRFunc) {
+    // Create a flattened profile using the IR function name to avoid profile
+    // name conflicts
+    const auto *FSForMatching = getFlattenedSamplesFor(ProfFuncName);
+    if (!FSForMatching)
+      FSForMatching = Reader.getSamplesFor(ProfFuncName.stringRef());
+    if (!FSForMatching)
+      return ProfFuncName;
+
+    FunctionId NewProfId(IRFuncName);
+    auto R = FuncProfileMatchCache.find({IRFunc, NewProfId});
+    if (R != FuncProfileMatchCache.end() && R->second)
+      return ProfFuncName;
+
+    // Update profile in the sample profile reader
+    SampleProfileMap &Profiles = Reader.getProfiles();
+    SampleContext FContext(NewProfId);
+    auto Res = Profiles.try_emplace(FContext.getHashCode(), FContext,
+                                    FunctionSamples());
+    FunctionSamples &NewFS = Res.first->second;
+    NewFS.setContext(FContext);
+    NewFS.merge(*FSForMatching);
+
+    FuncProfileMatchCache[{IRFunc, ProfFuncName}] = false;
+
+    LLVM_DEBUG(dbgs() << "Function:" << IRFunc->getName()
+                      << " encounters conflicting profile matchings, "
+                         "remapping to new profile:"
+                      << NewProfId << "\n");
+
+    return NewProfId;
+  }
+  return ProfFuncName;
+}
+
 // Demangle \p FName and return the base function name (stripping namespaces,
 // templates, and parameter types). Returns an empty string on failure.
 static std::string getDemangledBaseName(ItaniumPartialDemangler &Demangler,
@@ -882,6 +902,9 @@ void SampleProfileMatcher::matchFunctionsWithoutProfileByBasename() {
   StringMap<Function *> OrphansByBaseName;
   StringSet<> AmbiguousBaseNames;
   for (auto &[FuncId, Func] : FunctionsWithoutProfile) {
+    // Skip functions that already been matched through stale profile matching
+    if (FuncToProfileNameMap.contains(Func))
+      continue;
     std::string BaseName = getDemangledBaseName(Demangler, Func->getName());
     if (BaseName.empty() || AmbiguousBaseNames.count(BaseName))
       continue;
@@ -939,9 +962,7 @@ void SampleProfileMatcher::matchFunctionsWithoutProfileByBasename() {
     if (!OrphanFunc)
       continue;
 
-    FuncToProfileNameMap[OrphanFunc] = ProfId;
-    ProfileNameToFuncMap[ProfId] = OrphanFunc;
-    FuncProfileMatchCache[{OrphanFunc, ProfId}] = true;
+    ProfId = recordMatchedStaleProfile(OrphanFunc, ProfId);
 
     if (const auto *FS = Reader.getSamplesFor(ProfId.stringRef()))
       NewlyLoadedProfiles.create(FS->getFunction()).merge(*FS);
diff --git a/llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-orphan-conflict-cross-stage.prof b/llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-orphan-conflict-cross-stage.prof
new file mode 100644
index 0000000000000..5be0cc53774d2
--- /dev/null
+++ b/llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-orphan-conflict-cross-stage.prof
@@ -0,0 +1,11 @@
+_Z3fool:1:0
+ 30: _Z3bari:1
+  1: 1
+  !CFGChecksum: 281479271677951
+ 57: _Z3quxi:1
+  1: 1
+  !CFGChecksum: 4294967295
+ 72: _Z3topi:1
+  2: _Z3midi:1
+   11: _Z3subi:1
+ !CFGChecksum: 1
diff --git a/llvm/test/Transforms/SampleProfile/Inputs/stale-profile-lcs-anchor-overwrite-cross-stage.prof b/llvm/test/Transforms/SampleProfile/Inputs/stale-profile-lcs-anchor-overwrite-cross-stage.prof
new file mode 100644
index 0000000000000..d276f2630a43b
--- /dev/null
+++ b/llvm/test/Transforms/SampleProfile/Inputs/stale-profile-lcs-anchor-overwrite-cross-stage.prof
@@ -0,0 +1,11 @@
+_Z3foov:29916:31
+ 1: 31
+ 349: 2 A:1 B:1
+ 350: 1 C:1
+ 6: _Z3bari:1843
+  1: 1843
+  !CFGChecksum: 1
+ 380: _Z6calleei:323
+  1: 323
+  !CFGChecksum: 281479271677951
+ !CFGChecksum: 1
diff --git a/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll b/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll
index 42036ad2856e9..33ba87c0c082c 100644
--- a/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll
+++ b/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-orphan-conflict.ll
@@ -24,13 +24,13 @@
 ; REQUIRES: asserts
 ; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/pseudo-probe-stale-profile-orphan-conflict.prof -o %t.prof
 ; RUN: opt < %s -passes=sample-profile -sample-profile-file=%t.prof --salvage-stale-profile --salvage-unused-profile -S --debug-only=sample-profile,sample-profile-matcher,sample-profile-impl 2>&1 | FileCheck %s
+; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/pseudo-probe-stale-profile-orphan-conflict-cross-stage.prof -o %t.cross-stage.prof
+; RUN: opt < %s -passes=sample-profile -sample-profile-file=%t.cross-stage.prof --salvage-stale-profile --salvage-unused-profile --min-func-count-for-cg-matching=0 -S --debug-only=sample-profile,sample-profile-matcher,sample-profile-impl 2>&1 | FileCheck %s --check-prefix=CROSS-STAGE
 
 ; CHECK: Run stale profile matching for _Z3fool
 ; CHECK: The functions _Z3topl(IR) and _Z3topi(Profile) share the same base name: top.
 ; CHECK: The functions _Z3topll(IR) and _Z3topi(Profile) share the same base name: top.
 ; CHECK: Function:_Z3topl matches profile:_Z3topi
-; CHECK: Function:_Z3barl matches profile:_Z3bari
-; CHECK: Function:_Z3topll matches profile:_Z3topi
 ; CHECK: Function:_Z3topll encounters conflicting profile matchings, remapping to new profile:_Z3topll
 ; CHECK: Callsite with callee:_Z3topl is matched from 2 to 30
 ; CHECK: Callsite with callee:_Z3barl is matched from 3 to 57
@@ -42,21 +42,37 @@
 ; CHECK: Run stale profile matching for _Z3barl
 ; CHECK: Run stale profile matching for _Z3topl
 ; CHECK: The functions _Z3midl(IR) and _Z3midi(Profile) share the same base name: mid.
-; CHECK: Function:_Z3midl matches profile:_Z3midi
 ; CHECK: Function:_Z3midl encounters conflicting profile matchings, remapping to new profile:_Z3midl
 ; CHECK: Callsite with callee:_Z3midl is matched from 1 to 2
 ; CHECK: Run stale profile matching for _Z3midll
 ; CHECK: Callsite with callee:_Z3subi is matched from 1 to 11
 ; CHECK: Run stale profile matching for _Z3subi
 ; CHECK: Run stale profile matching for _Z3midl
-; CHECK: Function processing order:
-; CHECK: _Z3topll
-; CHECK: _Z3midl
-; CHECK: _Z3fool
-; CHECK: _Z3topl
-; CHECK: _Z3midll
-; CHECK: _Z3subi
-; CHECK: _Z3barl
+
+; Test the conflict between the two stages of stale profile matching.
+; CROSS-STAGE: Run stale profile matching for _Z3fool
+; CROSS-STAGE: The checksums for _Z3topl(IR) and _Z3bari(Profile) match.
+; CROSS-STAGE: The checksums for _Z3barl(IR) and _Z3quxi(Profile) match.
+; CROSS-STAGE: The functions _Z3topll(IR) and _Z3topi(Profile) share the same base name: top.
+; CROSS-STAGE: Function:_Z3topl matches profile:_Z3bari
+; CROSS-STAGE: Function:_Z3barl matches profile:_Z3quxi
+; CROSS-STAGE: Function:_Z3topll matches profile:_Z3topi
+; CROSS-STAGE: Callsite with callee:_Z3topl is matched from 3 to 30
+; CROSS-STAGE: Callsite with callee:_Z3barl is matched from 5 to 57
+; CROSS-STAGE: Callsite with callee:_Z3topll is matched from 6 to 72
+; CROSS-STAGE: Run stale profile matching for _Z3topll
+; CROSS-STAGE: The functions _Z3midll(IR) and _Z3midi(Profile) share the same base name: mid.
+; CROSS-STAGE: Function:_Z3midll matches profile:_Z3midi
+; CROSS-STAGE: Callsite with callee:_Z3midll is matched from 2 to 2
+; CROSS-STAGE: Run stale profile matching for _Z3barl
+; CROSS-STAGE: Run stale profile matching for _Z3topl
+; CROSS-STAGE: Run stale profile matching for _Z3midll
+; CROSS-STAGE: Callsite with callee:_Z3subi is matched from 2 to 11
+; CROSS-STAGE: Run stale profile matching for _Z3subi
+; CROSS-STAGE: Function:_Z3midl encounters conflicting profile matchings, remapping to new profile:_Z3midl
+; CROSS-STAGE: Direct basename match: _Z3midl (IR) -> _Z3midl (Profile) [basename: mid]
+; CROSS-STAGE: Direct basename matching found 1 matches
+; CROSS-STAGE: Run stale profile matching for _Z3midl
 
 target triple = "x86_64-redhat-linux-gnu"
 
diff --git a/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll b/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll
index 4316848283d68..f148028110bd7 100644
--- a/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll
+++ b/llvm/test/Transforms/SampleProfile/stale-profile-lcs-anchor-overwrite.ll
@@ -2,6 +2,8 @@
 ; REQUIRES: asserts
 ; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/stale-profile-lcs-anchor-overwrite.prof -o %t.prof
 ; RUN: opt < %s -passes=sample-profile -sample-profile-file=%t.prof --salvage-stale-profile --salvage-unused-profile -S --debug-only=sample-profile,sample-profile-matcher,sample-profile-impl 2>&1 | FileCheck %s
+; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/stale-profile-lcs-anchor-overwrite-cross-stage.prof -o %t.cross-stage.prof
+; RUN: opt < %s -passes=sample-profile -sample-profile-file=%t.cross-stage.prof --salvage-stale-profile --salvage-unused-profile --min-func-count-for-cg-matching=0 -S --debug-only=sample-profile,sample-profile-matcher,sample-profile-impl 2>&1 | FileCheck %s --check-prefix=CROSS-STAGE
 
 ; CHECK: Function _Z6calleePv is not in profile or profile symbol list.
 ; CHECK: Function _Z3barv is not in profile or profile symbol list.
@@ -23,16 +25,29 @@
 ; CHECK: Callsite with callee:unknown.indirect.callee is matched from 4 to 349
 ; CHECK: Callsite with callee:_Z3barPv is matched from 5 to 380
 ; CHECK: Run stale profile matching for _Z3barPv
-; CHECK: Function:_Z6calleePv matches profile:_Z6calleei
 ; CHECK: Location is matched from 1 to 1
 ; CHECK: Callsite with callee:_Z6calleePv is matched from 2 to 3
 ; CHECK: Run stale profile matching for _Z3barv
 ; CHECK: Run stale profile matching for _Z6calleePv
-; CHECK: Function processing order:
-; CHECK: _Z3foov
-; CHECK: _Z3barPv
-; CHECK: _Z6calleePv
-; CHECK: _Z3barv
+
+; Test the conflict between the two stages of stale profile matching.
+; CROSS-STAGE: Function _Z6calleePv is not in profile or profile symbol list.
+; CROSS-STAGE: Function _Z3barv is not in profile or profile symbol list.
+; CROSS-STAGE: Function _Z3barPv is not in profile or profile symbol list.
+; CROSS-STAGE: Run stale profile matching for _Z3foov
+; CROSS-STAGE: The functions _Z3barv(IR) and _Z3bari(Profile) share the same base name: bar.
+; CROSS-STAGE: The checksums for _Z3barPv(IR) and _Z6calleei(Profile) match.
+; CROSS-STAGE: Function:_Z3barv matches profile:_Z3bari
+; CROSS-STAGE: Function:_Z3barPv matches profile:_Z6calleei
+; CROSS-STAGE: Callsite with callee:_Z3barv is matched from 3 to 6
+; CROSS-STAGE: Callsite with callee:unknown.indirect.callee is matched from 4 to 349
+; CROSS-STAGE: Callsite with callee:_Z3barPv is matched from 5 to 380
+; CROSS-STAGE: Run stale profile matching for _Z3barPv
+; CROSS-STAGE: Run stale profile matching for _Z3barv
+; CROSS-STAGE: Function:_Z6calleePv encounters conflicting profile matchings, remapping to new profile:_Z6calleePv
+; CROSS-STAGE: Direct basename match: _Z6calleePv (IR) -> _Z6calleePv (Profile) [basename: callee]
+; CROSS-STAGE: Direct basename matching found 1 matches
+; CROSS-STAGE: Run stale profile matching for _Z6calleePv
 
 
 target triple = "x86_64-linux-gnu"



More information about the llvm-commits mailing list