[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