[llvm] [SampleProfileMatcher] Match IR direct calls against profile indirect calls (PR #183631)

Wei Wang via llvm-commits llvm-commits at lists.llvm.org
Mon Mar 9 10:33:28 PDT 2026


https://github.com/apolloww updated https://github.com/llvm/llvm-project/pull/183631

>From 87382f0fb07887326721e79bb411f74ad1ba413b Mon Sep 17 00:00:00 2001
From: Wei Wang <apollo.mobility at gmail.com>
Date: Thu, 26 Feb 2026 13:26:40 -0800
Subject: [PATCH 1/2] [SampleProfileMatcher] Match direct calls against
 UnknownIndirectCallee targets

When `findProfileAnchors` encounters multiple callees at the same
profile location (e.g., from body samples and inlined callsite samples),
it collapses them into a single `UnknownIndirectCallee` anchor. This
causes false mismatches when the IR has a direct call to one of the
actual targets, because the matcher compares the direct callee name
against `UnknownIndirectCallee` and finds no match.

There are two cases where this matters:

1. Devirtualized calls: the IR callee is one of the actual targets
   (e.g., `foo<int>`) while the profile has multiple targets
   (`foo<int>`, `foo<double>`).

2. Mis-attributed call targets from corrupted call stacks: when frames
   are missing in the perf data (not due to tail-call elimination), a
   call target from one frame can be attributed to another. For example,
   if a call stack `A -> B at 5 -> C at 5` becomes `A -> C at 5` (B is missing),
   the profile of A ends up with two targets at offset 5 (B and C),
   turning a direct call (B) into an apparent indirect call.

Note that the existing CG matching cannot handle this because the
profile anchor is the synthetic name `UnknownIndirectCallee`, which
fails both base-name demangling and checksum comparison.

Fix this by introducing `AnchorTargetMap`, which saves the individual
call targets when `UnknownIndirectCallee` is created. The map is then
consulted in three places:

1. `longestCommonSequence` (LCS): the anchor comparison checks whether
   the IR callee matches any saved profile target when the profile
   anchor is `UnknownIndirectCallee`.

2. `recordCallsiteMatchStates`: the same target check is applied when
   computing match states for staleness reporting.

3. `functionMatchesProfileHelper`: the target map is passed through to
   `longestCommonSequence` for call-graph matching.

The generic `longestCommonSequence` utility is updated to pass full
`(Loc, Function)` pairs to the comparison callback (instead of just
the `Function` part), so callers can access the location for target-map
lookups.
---
 .../Transforms/IPO/SampleProfileMatcher.h     |  12 +-
 .../Transforms/Utils/LongestCommonSequence.h  |  10 +-
 .../Transforms/IPO/SampleProfileMatcher.cpp   |  94 ++++++++++-----
 .../Transforms/Instrumentation/MemProfUse.cpp |   6 +-
 ...e-profile-matching-direct-to-indirect.prof |   7 ++
 ...ale-profile-matching-direct-to-indirect.ll | 109 ++++++++++++++++++
 6 files changed, 201 insertions(+), 37 deletions(-)
 create mode 100644 llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-matching-direct-to-indirect.prof
 create mode 100644 llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-matching-direct-to-indirect.ll

diff --git a/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h b/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
index c695784641b4e..d7d6da041f257 100644
--- a/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
+++ b/llvm/include/llvm/Transforms/IPO/SampleProfileMatcher.h
@@ -23,6 +23,11 @@ namespace llvm {
 
 using AnchorList = std::vector<std::pair<LineLocation, FunctionId>>;
 using AnchorMap = std::map<LineLocation, FunctionId>;
+// When multiple callees are found at the same location (creating an
+// UnknownIndirectCallee anchor), save the individual targets for later
+// matching. This allows a direct call in the IR to match against one of
+// the actual call targets in the profile.
+using AnchorTargetMap = std::map<LineLocation, SmallVector<FunctionId, 2>>;
 
 // Sample profile matching - fuzzy match.
 class SampleProfileMatcher {
@@ -162,12 +167,13 @@ class SampleProfileMatcher {
                              AnchorList &FilteredProfileAnchorList);
   void runOnFunction(Function &F);
   void findIRAnchors(const Function &F, AnchorMap &IRAnchors) const;
-  void findProfileAnchors(const FunctionSamples &FS,
-                          AnchorMap &ProfileAnchors) const;
+  void findProfileAnchors(const FunctionSamples &FS, AnchorMap &ProfileAnchors,
+                          AnchorTargetMap &ProfileAnchorTargets) const;
   // Record the callsite match states for profile staleness report, the result
   // is saved in FuncCallsiteMatchStates.
   void recordCallsiteMatchStates(const Function &F, const AnchorMap &IRAnchors,
                                  const AnchorMap &ProfileAnchors,
+                                 const AnchorTargetMap &ProfileAnchorTargets,
                                  const LocToLocMap *IRToProfileLocationMap);
 
   bool isMismatchState(const enum MatchState &State) {
@@ -209,12 +215,14 @@ class SampleProfileMatcher {
   void distributeIRToProfileLocationMap(FunctionSamples &FS);
   LocToLocMap longestCommonSequence(const AnchorList &IRCallsiteAnchors,
                                     const AnchorList &ProfileCallsiteAnchors,
+                                    const AnchorTargetMap &ProfileAnchorTargets,
                                     bool MatchUnusedFunction);
   void matchNonCallsiteLocs(const LocToLocMap &AnchorMatchings,
                             const AnchorMap &IRAnchors,
                             LocToLocMap &IRToProfileLocationMap);
   void runStaleProfileMatching(const Function &F, const AnchorMap &IRAnchors,
                                const AnchorMap &ProfileAnchors,
+                               const AnchorTargetMap &ProfileAnchorTargets,
                                LocToLocMap &IRToProfileLocationMap,
                                bool RunCFGMatching, bool RunCGMatching);
   // If the function doesn't have profile, return the pointer to the function.
diff --git a/llvm/include/llvm/Transforms/Utils/LongestCommonSequence.h b/llvm/include/llvm/Transforms/Utils/LongestCommonSequence.h
index 5c94bda6abd2b..c3489ffa6129b 100644
--- a/llvm/include/llvm/Transforms/Utils/LongestCommonSequence.h
+++ b/llvm/include/llvm/Transforms/Utils/LongestCommonSequence.h
@@ -35,8 +35,9 @@ template <typename Loc, typename Function,
           typename AnchorList = ArrayRef<std::pair<Loc, Function>>>
 void longestCommonSequence(
     AnchorList AnchorList1, AnchorList AnchorList2,
-    llvm::function_ref<bool(const Function &, const Function &)>
-        FunctionMatchesProfile,
+    llvm::function_ref<bool(const std::pair<Loc, Function> &,
+                            const std::pair<Loc, Function> &)>
+        ElementsMatch,
     llvm::function_ref<void(Loc, Loc)> InsertMatching) {
   int32_t Size1 = AnchorList1.size(), Size2 = AnchorList2.size(),
           MaxDepth = Size1 + Size2;
@@ -94,9 +95,8 @@ void longestCommonSequence(
       else
         X = V[Index(K - 1)] + 1;
       Y = X - K;
-      while (
-          X < Size1 && Y < Size2 &&
-          FunctionMatchesProfile(AnchorList1[X].second, AnchorList2[Y].second))
+      while (X < Size1 && Y < Size2 &&
+             ElementsMatch(AnchorList1[X], AnchorList2[Y]))
         X++, Y++;
 
       V[Index(K)] = X;
diff --git a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
index b9fb7a3ae4b5b..075ea3a94b44f 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
@@ -131,16 +131,25 @@ void SampleProfileMatcher::findIRAnchors(const Function &F,
   }
 }
 
-void SampleProfileMatcher::findProfileAnchors(const FunctionSamples &FS,
-                                              AnchorMap &ProfileAnchors) const {
+void SampleProfileMatcher::findProfileAnchors(
+    const FunctionSamples &FS, AnchorMap &ProfileAnchors,
+    AnchorTargetMap &ProfileAnchorTargets) const {
   auto isInvalidLineOffset = [](uint32_t LineOffset) {
     return LineOffset & 0x8000;
   };
 
   auto InsertAnchor = [](const LineLocation &Loc, const FunctionId &CalleeName,
-                         AnchorMap &ProfileAnchors) {
+                         AnchorMap &ProfileAnchors,
+                         AnchorTargetMap &ProfileAnchorTargets) {
     auto Ret = ProfileAnchors.try_emplace(Loc, CalleeName);
     if (!Ret.second) {
+      if (Ret.first->second != FunctionId(UnknownIndirectCallee)) {
+        // First conflict: save the previously inserted target.
+        ProfileAnchorTargets[Loc].push_back(Ret.first->second);
+      }
+      // Save the new conflicting target.
+      if (CalleeName != FunctionId(UnknownIndirectCallee))
+        ProfileAnchorTargets[Loc].push_back(CalleeName);
       // For multiple callees, which indicates it's an indirect call, we use a
       // dummy name(UnknownIndirectCallee) as the indrect callee name.
       Ret.first->second = FunctionId(UnknownIndirectCallee);
@@ -152,7 +161,7 @@ void SampleProfileMatcher::findProfileAnchors(const FunctionSamples &FS,
     if (isInvalidLineOffset(Loc.LineOffset))
       continue;
     for (const auto &C : I.second.getCallTargets())
-      InsertAnchor(Loc, C.first, ProfileAnchors);
+      InsertAnchor(Loc, C.first, ProfileAnchors, ProfileAnchorTargets);
   }
 
   for (const auto &I : FS.getCallsiteSamples()) {
@@ -160,7 +169,7 @@ void SampleProfileMatcher::findProfileAnchors(const FunctionSamples &FS,
     if (isInvalidLineOffset(Loc.LineOffset))
       continue;
     for (const auto &C : I.second)
-      InsertAnchor(Loc, C.first, ProfileAnchors);
+      InsertAnchor(Loc, C.first, ProfileAnchors, ProfileAnchorTargets);
   }
 }
 
@@ -198,18 +207,29 @@ bool SampleProfileMatcher::functionMatchesProfile(
                                 FindMatchedProfileOnly);
 }
 
-LocToLocMap
-SampleProfileMatcher::longestCommonSequence(const AnchorList &AnchorList1,
-                                            const AnchorList &AnchorList2,
-                                            bool MatchUnusedFunction) {
+LocToLocMap SampleProfileMatcher::longestCommonSequence(
+    const AnchorList &AnchorList1, const AnchorList &AnchorList2,
+    const AnchorTargetMap &ProfileAnchorTargets, bool MatchUnusedFunction) {
   LocToLocMap MatchedAnchors;
   llvm::longestCommonSequence<LineLocation, FunctionId>(
       AnchorList1, AnchorList2,
-      [&](const FunctionId &A, const FunctionId &B) {
-        return functionMatchesProfile(
-            A, B,
-            !MatchUnusedFunction // Find matched function only
-        );
+      [&](const std::pair<LineLocation, FunctionId> &A,
+          const std::pair<LineLocation, FunctionId> &B) {
+        if (functionMatchesProfile(
+                A.second, B.second,
+                !MatchUnusedFunction // Find matched function only
+                ))
+          return true;
+        // When the profile has UnknownIndirectCallee (multiple callees
+        // collapsed), check if the IR callee matches any of the actual call
+        // targets.
+        if (B.second == FunctionId(UnknownIndirectCallee) &&
+            A.second != FunctionId(UnknownIndirectCallee)) {
+          auto TIt = ProfileAnchorTargets.find(B.first);
+          return TIt != ProfileAnchorTargets.end() &&
+                 llvm::is_contained(TIt->second, A.second);
+        }
+        return false;
       },
       [&](LineLocation A, LineLocation B) {
         MatchedAnchors.try_emplace(A, B);
@@ -306,8 +326,10 @@ void SampleProfileMatcher::getFilteredAnchorList(
 // The output mapping: [2->3, 3->4, 5->7, 6->8, 7->9].
 void SampleProfileMatcher::runStaleProfileMatching(
     const Function &F, const AnchorMap &IRAnchors,
-    const AnchorMap &ProfileAnchors, LocToLocMap &IRToProfileLocationMap,
-    bool RunCFGMatching, bool RunCGMatching) {
+    const AnchorMap &ProfileAnchors,
+    const AnchorTargetMap &ProfileAnchorTargets,
+    LocToLocMap &IRToProfileLocationMap, bool RunCFGMatching,
+    bool RunCGMatching) {
   if (!RunCFGMatching && !RunCGMatching)
     return;
   LLVM_DEBUG(dbgs() << "Run stale profile matching for " << F.getName()
@@ -343,9 +365,9 @@ void SampleProfileMatcher::runStaleProfileMatching(
   // appear on either side to reduce the matching scope. Note that we need to
   // use IR anchor as base(A side) to align with the order of
   // IRToProfileLocationMap.
-  LocToLocMap MatchedAnchors =
-      longestCommonSequence(FilteredIRAnchorsList, FilteredProfileAnchorList,
-                            RunCGMatching /* Match unused functions */);
+  LocToLocMap MatchedAnchors = longestCommonSequence(
+      FilteredIRAnchorsList, FilteredProfileAnchorList, ProfileAnchorTargets,
+      RunCGMatching /* Match unused functions */);
 
   // CFG level matching:
   // Apply the callsite matchings to infer matching for the basic
@@ -385,11 +407,13 @@ void SampleProfileMatcher::runOnFunction(Function &F) {
   // Anchors for profile. It's a map from callsite location to a set of callee
   // name.
   AnchorMap ProfileAnchors;
-  findProfileAnchors(*FSForMatching, ProfileAnchors);
+  AnchorTargetMap ProfileAnchorTargets;
+  findProfileAnchors(*FSForMatching, ProfileAnchors, ProfileAnchorTargets);
 
   // Compute the callsite match states for profile staleness report.
   if (ReportProfileStaleness || PersistProfileStaleness)
-    recordCallsiteMatchStates(F, IRAnchors, ProfileAnchors, nullptr);
+    recordCallsiteMatchStates(F, IRAnchors, ProfileAnchors,
+                              ProfileAnchorTargets, nullptr);
 
   if (!SalvageStaleProfile)
     return;
@@ -410,17 +434,19 @@ void SampleProfileMatcher::runOnFunction(Function &F) {
   // The matching result will be saved to IRToProfileLocationMap, create a
   // new map for each function.
   auto &IRToProfileLocationMap = getIRToProfileLocationMap(F);
-  runStaleProfileMatching(F, IRAnchors, ProfileAnchors, IRToProfileLocationMap,
-                          RunCFGMatching, RunCGMatching);
+  runStaleProfileMatching(F, IRAnchors, ProfileAnchors, ProfileAnchorTargets,
+                          IRToProfileLocationMap, RunCFGMatching,
+                          RunCGMatching);
   // Find and update callsite match states after matching.
   if (RunCFGMatching && (ReportProfileStaleness || PersistProfileStaleness))
     recordCallsiteMatchStates(F, IRAnchors, ProfileAnchors,
-                              &IRToProfileLocationMap);
+                              ProfileAnchorTargets, &IRToProfileLocationMap);
 }
 
 void SampleProfileMatcher::recordCallsiteMatchStates(
     const Function &F, const AnchorMap &IRAnchors,
     const AnchorMap &ProfileAnchors,
+    const AnchorTargetMap &ProfileAnchorTargets,
     const LocToLocMap *IRToProfileLocationMap) {
   bool IsPostMatch = IRToProfileLocationMap != nullptr;
   auto &CallsiteMatchStates =
@@ -446,7 +472,16 @@ void SampleProfileMatcher::recordCallsiteMatchStates(
     if (It == ProfileAnchors.end())
       continue;
     const auto &ProfCalleeId = It->second;
-    if (IRCalleeId == ProfCalleeId) {
+    if (IRCalleeId == ProfCalleeId ||
+        // When the profile has UnknownIndirectCallee (indirect call) but the IR
+        // has a direct call, check if the IR callee matches any of the actual
+        // call targets in the profile.
+        (ProfCalleeId == FunctionId(UnknownIndirectCallee) &&
+         IRCalleeId != FunctionId(UnknownIndirectCallee) && [&]() {
+           auto TIt = ProfileAnchorTargets.find(ProfileLoc);
+           return TIt != ProfileAnchorTargets.end() &&
+                  llvm::is_contained(TIt->second, IRCalleeId);
+         }())) {
       auto It = CallsiteMatchStates.find(ProfileLoc);
       if (It == CallsiteMatchStates.end())
         CallsiteMatchStates.emplace(ProfileLoc, MatchState::InitialMatch);
@@ -805,7 +840,8 @@ bool SampleProfileMatcher::functionMatchesProfileHelper(
   AnchorMap IRAnchors;
   findIRAnchors(IRFunc, IRAnchors);
   AnchorMap ProfileAnchors;
-  findProfileAnchors(*FSForMatching, ProfileAnchors);
+  AnchorTargetMap ProfileAnchorTargets;
+  findProfileAnchors(*FSForMatching, ProfileAnchors, ProfileAnchorTargets);
 
   AnchorList FilteredIRAnchorsList;
   AnchorList FilteredProfileAnchorList;
@@ -822,9 +858,9 @@ bool SampleProfileMatcher::functionMatchesProfileHelper(
   // Don't recursively match the callee function to avoid infinite matching,
   // callee functions will be handled later since it's processed in top-down
   // order .
-  LocToLocMap MatchedAnchors =
-      longestCommonSequence(FilteredIRAnchorsList, FilteredProfileAnchorList,
-                            false /* Match unused functions */);
+  LocToLocMap MatchedAnchors = longestCommonSequence(
+      FilteredIRAnchorsList, FilteredProfileAnchorList, ProfileAnchorTargets,
+      false /* Match unused functions */);
 
   Similarity = static_cast<float>(MatchedAnchors.size()) /
                FilteredProfileAnchorList.size();
diff --git a/llvm/lib/Transforms/Instrumentation/MemProfUse.cpp b/llvm/lib/Transforms/Instrumentation/MemProfUse.cpp
index 704e4117e6a63..b6bb70cc65611 100644
--- a/llvm/lib/Transforms/Instrumentation/MemProfUse.cpp
+++ b/llvm/lib/Transforms/Instrumentation/MemProfUse.cpp
@@ -367,7 +367,11 @@ memprof::computeUndriftMap(Module &M, IndexedInstrProfReader *MemProfReader,
 
     LocToLocMap Matchings;
     longestCommonSequence<LineLocation, GlobalValue::GUID>(
-        ProfileAnchors, IRAnchors, std::equal_to<GlobalValue::GUID>(),
+        ProfileAnchors, IRAnchors,
+        [](const std::pair<LineLocation, GlobalValue::GUID> &A,
+           const std::pair<LineLocation, GlobalValue::GUID> &B) {
+          return A.second == B.second;
+        },
         [&](LineLocation A, LineLocation B) { Matchings.try_emplace(A, B); });
     [[maybe_unused]] bool Inserted =
         UndriftMaps.try_emplace(CallerGUID, std::move(Matchings)).second;
diff --git a/llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-matching-direct-to-indirect.prof b/llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-matching-direct-to-indirect.prof
new file mode 100644
index 0000000000000..ca6753c189f9e
--- /dev/null
+++ b/llvm/test/Transforms/SampleProfile/Inputs/pseudo-probe-stale-profile-matching-direct-to-indirect.prof
@@ -0,0 +1,7 @@
+test_direct_call_matched_to_indirect:500:80
+ 1: 80
+ 2: 83 A:43 B:40
+ 3: 84
+ 4: 84 B:84
+ 5: 91 C:91
+ !CFGChecksum: 123456
diff --git a/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-matching-direct-to-indirect.ll b/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-matching-direct-to-indirect.ll
new file mode 100644
index 0000000000000..033ffce54869a
--- /dev/null
+++ b/llvm/test/Transforms/SampleProfile/pseudo-probe-stale-profile-matching-direct-to-indirect.ll
@@ -0,0 +1,109 @@
+; REQUIRES: asserts && x86-registered-target
+; RUN: opt < %s -passes=sample-profile -sample-profile-file=%S/Inputs/pseudo-probe-stale-profile-matching-direct-to-indirect.prof --salvage-stale-profile -S --debug-only=sample-profile,sample-profile-matcher,sample-profile-impl 2>&1 | FileCheck %s
+
+;; Test that stale profile matching correctly matches a direct call in the IR
+;; against an UnknownIndirectCallee profile anchor when the direct callee is
+;; one of the actual call targets that were collapsed.
+;;
+;; The profile has multiple call targets at location 2 (A:43 B:40), which
+;; findProfileAnchors collapses into UnknownIndirectCallee. After code changes
+;; that shifted probe IDs, the IR has a direct call to @A at probe 3. With the
+;; AnchorTargetMap fix, the LCS matching recognizes that A is one of the saved
+;; targets and aligns the callsites correctly.
+;;
+;; IR (probes):       [1,  2,  3(A),  4,  5(B),  6(C)]
+;; Profile (locs):    [1,  2(A+B),    3,  4(B),  5(C)]
+;;                              \
+;;                               \ (collapsed to UnknownIndirectCallee,
+;;                                  but AnchorTargetMap saves [A, B])
+;;
+;; LCS anchor matching:
+;;   IR anchors:      [3(A),       5(B),  6(C)]
+;;                     |           |       |
+;;   Profile anchors: [2(indirect) 4(B),  5(C)]
+;;
+;; Full matching (anchors + non-anchors):
+;;   [1,  2,  3(A),  4,  5(B),  6(C)]
+;;    |   |     |    |     |      |
+;;   [1,  2,  *2*,   3,   4(B), 5(C)]
+;;          (rematch)
+;;   Output: [3->2, 4->3, 5->4, 6->5]
+
+; CHECK: Run stale profile matching for test_direct_call_matched_to_indirect
+; CHECK-NEXT: Location is matched from 1 to 1
+; CHECK-NEXT: Location is matched from 2 to 2
+; CHECK-NEXT: Callsite with callee:A is matched from 3 to 2
+; CHECK-NEXT: Location is rematched backwards from 2 to 1
+; CHECK-NEXT: Location is matched from 4 to 3
+; CHECK-NEXT: Callsite with callee:B is matched from 5 to 4
+; CHECK-NEXT: Callsite with callee:C is matched from 6 to 5
+
+ at c = external global i32, align 4
+
+define dso_local i32 @test_direct_call_matched_to_indirect(i32 noundef %x) #0 !dbg !10 {
+entry:
+    #dbg_value(i32 %x, !15, !DIExpression(), !16)
+  call void @llvm.pseudoprobe(i64 -3371303159763101805, i64 1, i32 0, i64 -1), !dbg !17
+  call void @llvm.pseudoprobe(i64 -3371303159763101805, i64 2, i32 0, i64 -1), !dbg !18
+  %call = call i32 @A(i32 noundef %x), !dbg !19
+  %add = add nsw i32 %x, %call, !dbg !21
+    #dbg_value(i32 %add, !15, !DIExpression(), !16)
+  call void @llvm.pseudoprobe(i64 -3371303159763101805, i64 4, i32 0, i64 -1), !dbg !22
+  %call1 = call i32 @B(i32 noundef %add), !dbg !23
+  %add2 = add nsw i32 %add, %call1, !dbg !25
+    #dbg_value(i32 %add2, !15, !DIExpression(), !16)
+  %call3 = call i32 @C(i32 noundef %add2), !dbg !26
+  %add4 = add nsw i32 %add2, %call3, !dbg !28
+    #dbg_value(i32 %add4, !15, !DIExpression(), !16)
+  ret i32 %add4, !dbg !29
+}
+
+declare !dbg !30 i32 @A(i32 noundef) #1
+declare !dbg !31 i32 @B(i32 noundef) #1
+declare !dbg !32 i32 @C(i32 noundef) #1
+
+declare void @llvm.pseudoprobe(i64, i64, i32, i64) #2
+
+attributes #0 = { nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" "use-sample-profile" }
+attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+attributes #2 = { mustprogress nocallback nofree nosync nounwind willreturn }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
+!llvm.ident = !{!9}
+!llvm.pseudo_probe_desc = !{!33}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 19.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, debugInfoForProfiling: true, nameTableKind: None)
+!1 = !DIFile(filename: "test.c", directory: "/home/", checksumkind: CSK_MD5, checksum: "be98aa946f37f0ad8d307c9121efe101")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 8, !"PIC Level", i32 2}
+!6 = !{i32 7, !"PIE Level", i32 2}
+!7 = !{i32 7, !"uwtable", i32 2}
+!8 = !{i32 7, !"debug-info-assignment-tracking", i1 true}
+!9 = !{!"clang version 19.0.0"}
+!10 = distinct !DISubprogram(name: "test_direct_call_matched_to_indirect", scope: !1, file: !1, line: 10, type: !11, scopeLine: 10, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !14)
+!11 = !DISubroutineType(types: !12)
+!12 = !{!13, !13}
+!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!14 = !{!15}
+!15 = !DILocalVariable(name: "x", arg: 1, scope: !10, file: !1, line: 10, type: !13)
+!16 = !DILocation(line: 0, scope: !10)
+!17 = !DILocation(line: 11, column: 10, scope: !10)
+!18 = !DILocation(line: 12, column: 10, scope: !10)
+!19 = !DILocation(line: 13, column: 8, scope: !20)
+!20 = !DILexicalBlockFile(scope: !10, file: !1, discriminator: 186646559)
+!21 = !DILocation(line: 13, column: 5, scope: !10)
+!22 = !DILocation(line: 14, column: 10, scope: !10)
+!23 = !DILocation(line: 15, column: 8, scope: !24)
+!24 = !DILexicalBlockFile(scope: !10, file: !1, discriminator: 186646575)
+!25 = !DILocation(line: 15, column: 5, scope: !10)
+!26 = !DILocation(line: 16, column: 8, scope: !27)
+!27 = !DILexicalBlockFile(scope: !10, file: !1, discriminator: 186646583)
+!28 = !DILocation(line: 16, column: 5, scope: !10)
+!29 = !DILocation(line: 17, column: 3, scope: !10)
+!30 = !DISubprogram(name: "A", scope: !1, file: !1, line: 2, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!31 = !DISubprogram(name: "B", scope: !1, file: !1, line: 3, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!32 = !DISubprogram(name: "C", scope: !1, file: !1, line: 4, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!33 = !{i64 -3371303159763101805, i64 281547712924884, !"test_direct_call_matched_to_indirect"}

>From cf6b1acad918f1c13f150a1abdfdee1565e64e39 Mon Sep 17 00:00:00 2001
From: Wei Wang <apollo.mobility at gmail.com>
Date: Mon, 9 Mar 2026 10:21:22 -0700
Subject: [PATCH 2/2]  Treat indirect-call-devirt as match in staleness stats

---
 llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
index 075ea3a94b44f..7770f2dd36902 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
@@ -472,7 +472,16 @@ void SampleProfileMatcher::recordCallsiteMatchStates(
     if (It == ProfileAnchors.end())
       continue;
     const auto &ProfCalleeId = It->second;
-    if (IRCalleeId == ProfCalleeId ||
+    // An indirect call in the IR with a specific callee in the profile may not
+    // be a real mismatch: It's possible that only one of call targets was
+    // sampled and ICP will resolve the indirect call to the profiled target in
+    // a later pass, so the samples will be applied correctly. Treat this as a
+    // match for stats purposes.
+    bool IsIndirectCallDevirt =
+        IRCalleeId.stringRef() == UnknownIndirectCallee &&
+        !ProfCalleeId.stringRef().empty() &&
+        ProfCalleeId.stringRef() != UnknownIndirectCallee;
+    if (IRCalleeId == ProfCalleeId || IsIndirectCallDevirt ||
         // When the profile has UnknownIndirectCallee (indirect call) but the IR
         // has a direct call, check if the IR callee matches any of the actual
         // call targets in the profile.



More information about the llvm-commits mailing list