[llvm] [MergeFunctions] Preserve import GUIDs when folding functions (PR #207003)
Alok Kumar Sharma via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 30 08:30:11 PDT 2026
https://github.com/alokkrsharma updated https://github.com/llvm/llvm-project/pull/207003
>From 58c117a6333f51cac36b8505d5fa2eb709275d2f Mon Sep 17 00:00:00 2001
From: Alok Kumar Sharma <AlokKumar.Sharma at amd.com>
Date: Wed, 1 Jul 2026 12:46:38 +0530
Subject: [PATCH 1/3] [MergeFunctions] Preserve import GUIDs when folding
functions
Computes the union of SamplePGO/ThinLTO import GUIDs from both
inputs and attaches it to the surviving function.
---
llvm/lib/Transforms/IPO/MergeFunctions.cpp | 33 +++++--
.../merge-functions-entry-count-imports.ll | 85 +++++++++++++++++++
2 files changed, 110 insertions(+), 8 deletions(-)
create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-entry-count-imports.ll
diff --git a/llvm/lib/Transforms/IPO/MergeFunctions.cpp b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
index c4e56855ea2fb..3b98dadc68451 100644
--- a/llvm/lib/Transforms/IPO/MergeFunctions.cpp
+++ b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
@@ -90,6 +90,7 @@
#include "llvm/Transforms/IPO/MergeFunctions.h"
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/IR/Argument.h"
@@ -880,12 +881,27 @@ static bool isODR(const Function *F) {
return F->hasWeakODRLinkage() || F->hasLinkOnceODRLinkage();
}
-static void mergeEntryCountsInto(Function *F, std::optional<uint64_t> FC,
- std::optional<uint64_t> GC) {
- if (!FC && !GC)
+static DenseSet<GlobalValue::GUID> unionImportGUIDs(const Function *F,
+ const Function *G) {
+ DenseSet<GlobalValue::GUID> AllImports = F->getImportGUIDs();
+ DenseSet<GlobalValue::GUID> GImports = G->getImportGUIDs();
+ AllImports.insert(GImports.begin(), GImports.end());
+ return AllImports;
+}
+
+static void
+mergeEntryCountsAndImportsInto(Function *F, std::optional<uint64_t> FC,
+ std::optional<uint64_t> GC,
+ const DenseSet<GlobalValue::GUID> &Imports) {
+ if (!FC && !GC && Imports.empty())
return;
- uint64_t Sum = SaturatingAdd(FC ? *FC : uint64_t{0}, GC ? *GC : uint64_t{0});
- F->setEntryCount(Sum);
+
+ uint64_t Sum;
+ if (!FC && !GC)
+ Sum = static_cast<uint64_t>(-1);
+ else
+ Sum = SaturatingAdd(FC ? *FC : uint64_t{0}, GC ? *GC : uint64_t{0});
+ F->setEntryCount(Sum, Imports.empty() ? nullptr : &Imports);
}
// Merge two equivalent functions. Upon completion, Function G is deleted.
@@ -893,6 +909,7 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
std::optional<uint64_t> FEC = F->getEntryCount();
std::optional<uint64_t> GEC = G->getEntryCount();
+ DenseSet<GlobalValue::GUID> AllImports = unionImportGUIDs(F, G);
// Create a new thunk that both F and G can call, if F cannot call G directly.
// That is the case if F is either interposable or if G is either weak_odr or
@@ -947,7 +964,7 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
F->setLinkage(GlobalValue::PrivateLinkage);
// The private shared implementation accumulates both symbols' entries
// (FEC + GEC), while each ODR thunk retains its own per-symbol entry count.
- mergeEntryCountsInto(F, FEC, GEC);
+ mergeEntryCountsAndImportsInto(F, FEC, GEC, AllImports);
++NumDoubleWeak;
++NumFunctionsMerged;
} else {
@@ -975,14 +992,14 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
// stop here and delete G. There's no need for a thunk. (See note on
// MergeFunctionsPDI above).
if (G->isDiscardableIfUnused() && G->use_empty() && !MergeFunctionsPDI) {
- mergeEntryCountsInto(F, FEC, GEC);
+ mergeEntryCountsAndImportsInto(F, FEC, GEC, AllImports);
G->eraseFromParent();
++NumFunctionsMerged;
return;
}
if (writeThunkOrAliasIfNeeded(F, G)) {
- mergeEntryCountsInto(F, FEC, GEC);
+ mergeEntryCountsAndImportsInto(F, FEC, GEC, AllImports);
++NumFunctionsMerged;
}
}
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-imports.ll b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-imports.ll
new file mode 100644
index 0000000000000..a3d972f7e984a
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-imports.ll
@@ -0,0 +1,85 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=mergefunc -S | FileCheck %s
+
+define internal i32 @foo(i32 %x) !prof !12 {
+entry:
+ %add = add nsw i32 %x, 1
+ ret i32 %add
+}
+
+define internal i32 @bar(i32 %x) !prof !13 {
+; CHECK-LABEL: define internal i32 @bar(
+; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF12:![0-9]+]] {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[X]], 1
+; CHECK-NEXT: ret i32 [[ADD]]
+;
+entry:
+ %add = add nsw i32 %x, 1
+ ret i32 %add
+}
+
+define internal i32 @f(i32 %x) !prof !14 {
+; CHECK-LABEL: define internal i32 @f(
+; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF13:![0-9]+]] {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[X]], 2
+; CHECK-NEXT: ret i32 [[ADD]]
+;
+entry:
+ %add = add nsw i32 %x, 2
+ ret i32 %add
+}
+
+define internal i32 @g(i32 %x) !prof !15 {
+entry:
+ %add = add nsw i32 %x, 2
+ ret i32 %add
+}
+
+define i32 @main() {
+; CHECK-LABEL: define i32 @main() {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[C1:%.*]] = call i32 @bar(i32 1)
+; CHECK-NEXT: [[C2:%.*]] = call i32 @bar(i32 2)
+; CHECK-NEXT: [[C3:%.*]] = call i32 @f(i32 3)
+; CHECK-NEXT: [[C4:%.*]] = call i32 @f(i32 4)
+; CHECK-NEXT: [[R1:%.*]] = add nsw i32 [[C1]], [[C2]]
+; CHECK-NEXT: [[R2:%.*]] = add nsw i32 [[C3]], [[C4]]
+; CHECK-NEXT: [[R:%.*]] = add nsw i32 [[R1]], [[R2]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+entry:
+ %c1 = call i32 @foo(i32 1)
+ %c2 = call i32 @bar(i32 2)
+ %c3 = call i32 @f(i32 3)
+ %c4 = call i32 @g(i32 4)
+ %r1 = add nsw i32 %c1, %c2
+ %r2 = add nsw i32 %c3, %c4
+ %r = add nsw i32 %r1, %r2
+ ret i32 %r
+}
+
+!llvm.module.flags = !{!0}
+
+!0 = !{i32 1, !"ProfileSummary", !1}
+!1 = !{!2, !3, !4, !5, !6, !7, !8, !9}
+!2 = !{!"ProfileFormat", !"InstrProf"}
+!3 = !{!"TotalCount", i64 300}
+!4 = !{!"MaxCount", i64 200}
+!5 = !{!"MaxInternalCount", i64 200}
+!6 = !{!"MaxFunctionCount", i64 200}
+!7 = !{!"NumCounts", i64 2}
+!8 = !{!"NumFunctions", i64 2}
+!9 = !{!"DetailedSummary", !10}
+!10 = !{!11}
+!11 = !{i32 10000, i64 100, i32 1}
+!12 = !{!"function_entry_count", i64 100, i64 1001, i64 2002}
+!13 = !{!"function_entry_count", i64 200, i64 2002, i64 3003}
+!14 = !{!"function_entry_count", i64 -1, i64 1001, i64 2002}
+!15 = !{!"function_entry_count", i64 -1, i64 2002, i64 3003}
+
+;.
+; CHECK: [[PROF12]] = !{!"function_entry_count", i64 300, i64 1001, i64 2002, i64 3003}
+; CHECK: [[PROF13]] = !{!"function_entry_count", i64 -1, i64 1001, i64 2002, i64 3003}
+;.
>From 0ecf5724a1a515b6cb9f1e5ea2f3cc5a41b278f5 Mon Sep 17 00:00:00 2001
From: Alok Kumar Sharma <AlokKumar.Sharma at amd.com>
Date: Mon, 27 Jul 2026 21:12:30 +0530
Subject: [PATCH 2/3] Review comments.
---
llvm/lib/Transforms/IPO/MergeFunctions.cpp | 103 ++++++++++++---------
1 file changed, 60 insertions(+), 43 deletions(-)
diff --git a/llvm/lib/Transforms/IPO/MergeFunctions.cpp b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
index 3b98dadc68451..9cb950b26288b 100644
--- a/llvm/lib/Transforms/IPO/MergeFunctions.cpp
+++ b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
@@ -288,7 +288,9 @@ class MergeFunctions {
// If needed, replace G with an alias to F if possible, or a thunk to F if
// profitable. Returns false if neither is the case. If \p G is not needed
// (i.e. it is discardable and not used), \p G is removed directly.
- bool writeThunkOrAliasIfNeeded(Function *F, Function *G);
+ // \p MergeProfile must be true when G's profile should be preserved, it is
+ // merged into F before G is erased or rewritten.
+ bool writeThunkOrAliasIfNeeded(Function *F, Function *G, bool MergeProfile);
/// Replace function F with function G in the function tree.
void replaceFunctionInTree(const FunctionNode &FN, Function *G);
@@ -857,23 +859,64 @@ void MergeFunctions::writeAlias(Function *F, Function *G) {
++NumAliasesWritten;
}
+static DenseSet<GlobalValue::GUID> unionImportGUIDs(const Function &F,
+ const Function &G) {
+ DenseSet<GlobalValue::GUID> AllImports = F.getImportGUIDs();
+ DenseSet<GlobalValue::GUID> GImports = G.getImportGUIDs();
+ AllImports.insert(GImports.begin(), GImports.end());
+ return AllImports;
+}
+
+static void mergeEntryCountsAndImportsInto(Function &F, Function &G) {
+ std::optional<uint64_t> FEntryCount = F.getEntryCount();
+ std::optional<uint64_t> GEntryCount = G.getEntryCount();
+ DenseSet<GlobalValue::GUID> AllImports = unionImportGUIDs(F, G);
+ if (!FEntryCount && !GEntryCount && AllImports.empty())
+ return;
+
+ // -1 is a safe placeholder here, getEntryCount() already treats it as
+ // "unknown" (same sentinel SamplePGO uses for no-sample functions), so
+ // it won't look hot to anyone reading the count back.
+ uint64_t Sum = static_cast<uint64_t>(-1);
+ if (FEntryCount || GEntryCount)
+ Sum = SaturatingAdd(FEntryCount ? *FEntryCount : uint64_t{0},
+ GEntryCount ? *GEntryCount : uint64_t{0});
+ F.setEntryCount(Sum, AllImports.empty() ? nullptr : &AllImports);
+}
+
// If needed, replace G with an alias to F if possible, or a thunk to F if
// profitable. Returns false if neither is the case. If \p G is not needed (i.e.
-// it is discardable and unused), \p G is removed directly.
-bool MergeFunctions::writeThunkOrAliasIfNeeded(Function *F, Function *G) {
- if (G->isDiscardableIfUnused() && G->use_empty() && !MergeFunctionsPDI) {
+// it is discardable and unused), \p G is removed directly. If \p MergeProfile
+// is set, G's profile metadata is merged into F.
+bool MergeFunctions::writeThunkOrAliasIfNeeded(Function *F, Function *G,
+ bool MergeProfile) {
+ bool ShouldErase =
+ G->isDiscardableIfUnused() && G->use_empty() && !MergeFunctionsPDI;
+ bool ShouldAlias = canCreateAliasFor(G);
+ bool ShouldThunk = canCreateThunkFor(F);
+
+ if (!ShouldErase && !ShouldAlias && !ShouldThunk)
+ return false;
+
+ if (MergeProfile) {
+ mergeEntryCountsAndImportsInto(*F, *G);
+ }
+
+ if (ShouldErase) {
G->eraseFromParent();
return true;
}
- if (canCreateAliasFor(G)) {
+
+ if (ShouldAlias) {
writeAlias(F, G);
return true;
}
- if (canCreateThunkFor(F)) {
+ if (ShouldThunk) {
writeThunk(F, G);
return true;
}
- return false;
+
+ llvm_unreachable("Erase, alias or thunk must apply");
}
/// Returns true if \p F is either weak_odr or linkonce_odr.
@@ -881,35 +924,10 @@ static bool isODR(const Function *F) {
return F->hasWeakODRLinkage() || F->hasLinkOnceODRLinkage();
}
-static DenseSet<GlobalValue::GUID> unionImportGUIDs(const Function *F,
- const Function *G) {
- DenseSet<GlobalValue::GUID> AllImports = F->getImportGUIDs();
- DenseSet<GlobalValue::GUID> GImports = G->getImportGUIDs();
- AllImports.insert(GImports.begin(), GImports.end());
- return AllImports;
-}
-
-static void
-mergeEntryCountsAndImportsInto(Function *F, std::optional<uint64_t> FC,
- std::optional<uint64_t> GC,
- const DenseSet<GlobalValue::GUID> &Imports) {
- if (!FC && !GC && Imports.empty())
- return;
-
- uint64_t Sum;
- if (!FC && !GC)
- Sum = static_cast<uint64_t>(-1);
- else
- Sum = SaturatingAdd(FC ? *FC : uint64_t{0}, GC ? *GC : uint64_t{0});
- F->setEntryCount(Sum, Imports.empty() ? nullptr : &Imports);
-}
-
// Merge two equivalent functions. Upon completion, Function G is deleted.
void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
- std::optional<uint64_t> FEC = F->getEntryCount();
- std::optional<uint64_t> GEC = G->getEntryCount();
- DenseSet<GlobalValue::GUID> AllImports = unionImportGUIDs(F, G);
+ std::optional<uint64_t> FEntryCount = F->getEntryCount();
// Create a new thunk that both F and G can call, if F cannot call G directly.
// That is the case if F is either interposable or if G is either weak_odr or
@@ -952,19 +970,19 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
const MaybeAlign NewFAlign = NewF->getAlign();
const MaybeAlign GAlign = G->getAlign();
- writeThunkOrAliasIfNeeded(F, G);
- if (FEC)
- NewF->setEntryCount(*FEC);
- writeThunkOrAliasIfNeeded(F, NewF);
+ // Merge !prof, while G still has its body.
+ writeThunkOrAliasIfNeeded(F, G, /*MergeProfile*/ true);
+ if (FEntryCount)
+ NewF->setEntryCount(*FEntryCount);
+ // NewF becomes thunk/alias to the shared body F, it has no profile to be
+ // merged.
+ writeThunkOrAliasIfNeeded(F, NewF, /*MergeProfile*/ false);
if (NewFAlign || GAlign)
F->setAlignment(std::max(NewFAlign.valueOrOne(), GAlign.valueOrOne()));
else
F->setAlignment(std::nullopt);
F->setLinkage(GlobalValue::PrivateLinkage);
- // The private shared implementation accumulates both symbols' entries
- // (FEC + GEC), while each ODR thunk retains its own per-symbol entry count.
- mergeEntryCountsAndImportsInto(F, FEC, GEC, AllImports);
++NumDoubleWeak;
++NumFunctionsMerged;
} else {
@@ -992,14 +1010,13 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
// stop here and delete G. There's no need for a thunk. (See note on
// MergeFunctionsPDI above).
if (G->isDiscardableIfUnused() && G->use_empty() && !MergeFunctionsPDI) {
- mergeEntryCountsAndImportsInto(F, FEC, GEC, AllImports);
+ mergeEntryCountsAndImportsInto(*F, *G);
G->eraseFromParent();
++NumFunctionsMerged;
return;
}
- if (writeThunkOrAliasIfNeeded(F, G)) {
- mergeEntryCountsAndImportsInto(F, FEC, GEC, AllImports);
+ if (writeThunkOrAliasIfNeeded(F, G, /*MergeProfile*/ true)) {
++NumFunctionsMerged;
}
}
>From 659bff722c4e38abd6a1e1c70019ff8392110373 Mon Sep 17 00:00:00 2001
From: Alok Kumar Sharma <AlokKumar.Sharma at amd.com>
Date: Thu, 30 Jul 2026 20:26:40 +0530
Subject: [PATCH 3/3] Review comments.
---
llvm/lib/Transforms/IPO/MergeFunctions.cpp | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Transforms/IPO/MergeFunctions.cpp b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
index 9cb950b26288b..9ea9dbfa3a048 100644
--- a/llvm/lib/Transforms/IPO/MergeFunctions.cpp
+++ b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
@@ -898,9 +898,8 @@ bool MergeFunctions::writeThunkOrAliasIfNeeded(Function *F, Function *G,
if (!ShouldErase && !ShouldAlias && !ShouldThunk)
return false;
- if (MergeProfile) {
+ if (MergeProfile)
mergeEntryCountsAndImportsInto(*F, *G);
- }
if (ShouldErase) {
G->eraseFromParent();
@@ -1016,9 +1015,8 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
return;
}
- if (writeThunkOrAliasIfNeeded(F, G, /*MergeProfile*/ true)) {
+ if (writeThunkOrAliasIfNeeded(F, G, /*MergeProfile*/ true))
++NumFunctionsMerged;
- }
}
}
More information about the llvm-commits
mailing list