[llvm] [MergeFunctions] Preserve entry counts/import GUIDs on folds (PR #202218)

Alok Kumar Sharma via llvm-commits llvm-commits at lists.llvm.org
Sun Jun 7 11:13:03 PDT 2026


https://github.com/alokkrsharma created https://github.com/llvm/llvm-project/pull/202218

```markdown
## Summary

`MergeFunctions` can fold two equivalent functions into one surviving body, a
thunk, or an alias-backed merge. Before this patch, the transform preserved the
IR body but did not preserve or combine profile entry-count metadata correctly.

That meant the surviving function could keep only one original function's
`function_entry_count`, lose the merged-away function's count, or drop
SamplePGO/ThinLTO import GUID operands attached to entry-count metadata.

This patch preserves profile metadata when a merge is committed.

## Simple example

Before `mergefunc`:

define internal i32 @add1(i32 %x) !prof !1 {
  %add = add i32 %x, 1
  ret i32 %add
}

define internal i32 @plus1(i32 %x) !prof !2 {
  %add = add i32 %x, 1
  ret i32 %add
}

!1 = !{!"function_entry_count", i64 2000}
!2 = !{!"function_entry_count", i64 1000}
```

Both functions are equivalent, so `mergefunc` keeps one body and removes the
other.

Old result:

```llvm
define internal i32 @add1(i32 %x) !prof !1 {
  %add = add i32 %x, 1
  ret i32 %add
}

!1 = !{!"function_entry_count", i64 2000}
```

The surviving body only kept `@add1`'s count. Calls that previously reached
`@plus1` now also reach `@add1`, but `@plus1`'s count was not reflected.

New result:

```llvm
define internal i32 @add1(i32 %x) !prof !3 {
  %add = add i32 %x, 1
  ret i32 %add
}

!3 = !{!"function_entry_count", i64 3000}
```

The surviving body receives the combined count:

```text
2000 + 1000 = 3000
```

## What is preserved

The patch captures entry counts before either function can be detached,
rewritten, or erased, then updates the retained body after the merge is known to
succeed.

Counts are combined with saturating add:

```text
merged_count = saturating_add(F_count, G_count)
```

If either input count is synthetic, the merged count is synthetic.

Import GUID operands from SamplePGO/ThinLTO metadata are also unioned:

```text
F: !{!"function_entry_count", i64 100, i64 1001, i64 2002}
G: !{!"function_entry_count", i64 200, i64 2002, i64 3003}

Merged:
!{!"function_entry_count", i64 300, i64 1001, i64 2002, i64 3003}
```

## Thunks and aliases

For ODR/double-thunk merges, the private backing body gets the combined count and
the union of import GUIDs, while each thunk keeps its own entry count.

For alias-backed merges, the alias itself does not carry entry-count metadata.
The combined count is kept on the backing function:

```llvm
@b = unnamed_addr alias void (), ptr @a

define void @a() unnamed_addr !prof !0 {
  ret void
}

!0 = !{!"function_entry_count", i64 12}
```

## Tests

Added coverage for:

- symmetric entry-count merge:
  merge-functions-entry-count.ll
- asymmetric counts:
  `merge-functions-entry-count-asymmetric.ll`
- only merged-away function has a count:
  `merge-functions-entry-count-one-sided.ll`
- SamplePGO/ThinLTO import GUID union:
  `merge-functions-entry-count-imports.ll`
- ODR/double-thunk path:
  `merge-functions-entry-count-odr.ll`
- real-to-synthetic promotion:
  `merge-functions-entry-count-real-to-synthetic.ll`
- alias-backed merge:
  `merge-functions-entry-count-alias.ll`

Validation:

```text
ninja -C build opt
llvm-lit -sv llvm/test/Transforms/MergeFunc/merge-functions-entry-count*.ll
llvm-lit -sv llvm/test/Transforms/MergeFunc/
```

Result:

```text
Full MergeFunc suite: 88/88 passed
```


>From d5cd78324b8ab87cc73e101661661cbf975103c0 Mon Sep 17 00:00:00 2001
From: Alok Kumar Sharma <AlokKumar.Sharma at amd.com>
Date: Sun, 7 Jun 2026 22:01:43 +0530
Subject: [PATCH] [MergeFunctions] Preserve entry counts/import GUIDs on folds

When folding two equivalent functions, preserve and update profile
entry counts on the surviving body.

This change:
- Captures and merges entry counts, using saturating add, from both
  functions before any transform that may detach or erase a function.
- Computes the union of SamplePGO/ThinLTO import GUIDs from both
  inputs and attaches it to the surviving function so import-driven
  inlining decisions remain valid.
- Ensures thunks preserve their entry counts and alias-backed merges
  preserve the combined count on the backing function, paralleling the
  GlobalMergeFunctions precedent.

Also add lit tests covering symmetric, asymmetric, one-sided, ODR,
real-to-synthetic promotion, and import-GUID union scenarios.

No behavior changes beyond preserving and correctly combining profile
metadata when a merge is committed locally.
---
 llvm/lib/Transforms/IPO/MergeFunctions.cpp    | 68 ++++++++++++++++++
 .../merge-functions-entry-count-alias.ll      | 31 +++++++++
 .../merge-functions-entry-count-asymmetric.ll | 47 +++++++++++++
 .../merge-functions-entry-count-imports.ll    | 48 +++++++++++++
 .../merge-functions-entry-count-odr.ll        | 69 +++++++++++++++++++
 .../merge-functions-entry-count-one-sided.ll  | 44 ++++++++++++
 ...functions-entry-count-real-to-synthetic.ll | 48 +++++++++++++
 .../MergeFunc/merge-functions-entry-count.ll  | 48 +++++++++++++
 8 files changed, 403 insertions(+)
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-entry-count-alias.ll
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-entry-count-asymmetric.ll
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-entry-count-imports.ll
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-entry-count-odr.ll
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-entry-count-one-sided.ll
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-entry-count-real-to-synthetic.ll
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-entry-count.ll

diff --git a/llvm/lib/Transforms/IPO/MergeFunctions.cpp b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
index ac8a21ea24320..4d2db145d620b 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"
@@ -114,6 +115,7 @@
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
+#include "llvm/Support/MathExtras.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Transforms/IPO.h"
 #include "llvm/Transforms/Utils/FunctionComparator.h"
@@ -121,6 +123,7 @@
 #include <algorithm>
 #include <cassert>
 #include <iterator>
+#include <optional>
 #include <set>
 #include <utility>
 #include <vector>
@@ -730,6 +733,7 @@ static void copyMetadataIfPresent(Function *From, Function *To,
 // For better debugability, under MergeFunctionsPDI, we do not modify G's
 // call sites to point to F even when within the same translation unit.
 void MergeFunctions::writeThunk(Function *F, Function *G) {
+  auto GEC = G->getEntryCount(/*AllowSynthetic=*/true);
   BasicBlock *GEntryBlock = nullptr;
   std::vector<Instruction *> PDIUnrelatedWL;
   std::vector<DbgVariableRecord *> PDVRUnrelatedWL;
@@ -799,6 +803,8 @@ void MergeFunctions::writeThunk(Function *F, Function *G) {
                << G->getName() << "()\n");
   } else {
     NewG->copyAttributesFrom(G);
+    if (GEC)
+      NewG->setEntryCount(*GEC);
     NewG->takeName(G);
     // Ensure CFI type metadata is propagated to the new function.
     copyMetadataIfPresent(G, NewG, "type");
@@ -872,6 +878,54 @@ static bool isODR(const Function *F) {
   return F->hasWeakODRLinkage() || F->hasLinkOnceODRLinkage();
 }
 
+/// Compute the union of SamplePGO/ThinLTO import GUIDs attached to F and G.
+/// This follows Function::getImportGUIDs(), which reads import GUID operands
+/// from function_entry_count metadata.
+static DenseSet<GlobalValue::GUID> unionImportGUIDs(const Function *F,
+                                                   const Function *G) {
+  DenseSet<GlobalValue::GUID> All = F->getImportGUIDs();
+  DenseSet<GlobalValue::GUID> GImports = G->getImportGUIDs();
+  All.insert(GImports.begin(), GImports.end());
+  return All;
+}
+
+/// When merging two equivalent functions, the function whose body is retained
+/// will, after merging, be entered from the callers of both originals (either
+/// directly or via an alias/thunk). Update its entry count to reflect that.
+/// If either function carries a synthetic count, the result is synthetic.
+///
+/// If `Imports` is provided it should point to the union of import GUIDs from
+/// both source functions; it will be passed through to `Function::setEntryCount`
+/// so that SamplePGO/ThinLTO import information is preserved.
+static void mergeEntryCountsInto(
+    Function *F, std::optional<Function::ProfileCount> FC,
+    std::optional<Function::ProfileCount> GC,
+    const DenseSet<GlobalValue::GUID> *Imports = nullptr) {
+  if (!FC && !GC)
+    return;
+  // Match the saturating-add convention used for profile count accumulation
+  // elsewhere in LLVM.
+  uint64_t Sum = SaturatingAdd(FC ? FC->getCount() : uint64_t{0},
+                               GC ? GC->getCount() : uint64_t{0});
+  Function::ProfileCountType PCT =
+      ((FC && FC->isSynthetic()) || (GC && GC->isSynthetic()))
+          ? Function::PCT_Synthetic
+          : Function::PCT_Real;
+  // Function::setEntryCount() requires the existing ProfileCountType to match.
+  // If the merge promotes real to synthetic, clear the stale metadata first.
+  if (FC && FC->getType() != PCT) {
+    DenseSet<GlobalValue::GUID> OldImports;
+    if (!Imports)
+      OldImports = F->getImportGUIDs();
+    const DenseSet<GlobalValue::GUID> *Use = Imports ? Imports : &OldImports;
+    F->setMetadata(LLVMContext::MD_prof, nullptr);
+    F->setEntryCount(Function::ProfileCount(Sum, PCT),
+                     Use->empty() ? nullptr : Use);
+    return;
+  }
+  F->setEntryCount(Sum, PCT, Imports);
+}
+
 // Merge two equivalent functions. Upon completion, Function G is deleted.
 void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
 
@@ -900,6 +954,9 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
     // Ensure CFI type metadata is propagated to the new function.
     copyMetadataIfPresent(F, NewF, "type");
     copyMetadataIfPresent(F, NewF, "kcfi_type");
+    auto FEC = F->getEntryCount(/*AllowSynthetic=*/true);
+    auto GEC = G->getEntryCount(/*AllowSynthetic=*/true);
+    DenseSet<GlobalValue::GUID> AllImports = unionImportGUIDs(F, G);
     removeUsers(F);
     F->replaceAllUsesWith(NewF);
 
@@ -916,6 +973,8 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
     const MaybeAlign GAlign = G->getAlign();
 
     writeThunkOrAliasIfNeeded(F, G);
+    if (FEC)
+      NewF->setEntryCount(*FEC);
     writeThunkOrAliasIfNeeded(F, NewF);
 
     if (NewFAlign || GAlign)
@@ -923,9 +982,16 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
     else
       F->setAlignment(std::nullopt);
     F->setLinkage(GlobalValue::PrivateLinkage);
+    // Keep the merged import GUID union on the retained private body; the ODR
+    // thunks remain entry points and preserve only their own entry counts.
+    mergeEntryCountsInto(F, FEC, GEC, &AllImports);
     ++NumDoubleWeak;
     ++NumFunctionsMerged;
   } else {
+    auto FEC = F->getEntryCount(/*AllowSynthetic=*/true);
+    auto GEC = G->getEntryCount(/*AllowSynthetic=*/true);
+    DenseSet<GlobalValue::GUID> AllImports = unionImportGUIDs(F, G);
+
     // For better debugability, under MergeFunctionsPDI, we do not modify G's
     // call sites to point to F even when within the same translation unit.
     if (!G->isInterposable() && !MergeFunctionsPDI) {
@@ -950,12 +1016,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, &AllImports);
       G->eraseFromParent();
       ++NumFunctionsMerged;
       return;
     }
 
     if (writeThunkOrAliasIfNeeded(F, G)) {
+      mergeEntryCountsInto(F, FEC, GEC, &AllImports);
       ++NumFunctionsMerged;
     }
   }
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-alias.ll b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-alias.ll
new file mode 100644
index 0000000000000..bf64821e49c5f
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-alias.ll
@@ -0,0 +1,31 @@
+; RUN: opt -S -passes=mergefunc -mergefunc-use-aliases < %s | FileCheck %s
+
+; Alias-backed merges keep the combined entry count on the backing function.
+
+; CHECK: @b = unnamed_addr alias void (), ptr @a
+; CHECK: define void @a() unnamed_addr !prof ![[PROF:[0-9]+]] {
+; CHECK: ![[PROF]] = !{!"function_entry_count", i64 12}
+
+define void @a() unnamed_addr !prof !0 {
+  ret void
+}
+
+define void @b() unnamed_addr !prof !1 {
+  ret void
+}
+
+!llvm.module.flags = !{!2}
+
+!0 = !{!"function_entry_count", i64 7}
+!1 = !{!"function_entry_count", i64 5}
+!2 = !{i32 1, !"ProfileSummary", !3}
+!3 = !{!4, !5, !6, !7, !8, !9, !10}
+!4 = !{!"ProfileFormat", !"InstrProf"}
+!5 = !{!"TotalCount", i64 12}
+!6 = !{!"MaxCount", i64 7}
+!7 = !{!"MaxFunctionCount", i64 7}
+!8 = !{!"NumCounts", i64 2}
+!9 = !{!"NumFunctions", i64 2}
+!10 = !{!"DetailedSummary", !11}
+!11 = !{!12}
+!12 = !{i32 10000, i64 5, i32 1}
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-asymmetric.ll b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-asymmetric.ll
new file mode 100644
index 0000000000000..c9eb35516b1e9
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-asymmetric.ll
@@ -0,0 +1,47 @@
+; RUN: opt < %s -passes=mergefunc -S | FileCheck %s
+
+; Non-ODR merge with asymmetric entry counts: the surviving body absorbs the
+; sum of both originals' counts (2000 + 1000 = 3000).
+
+; CHECK: define internal i32 @add1(i32 %x) !prof ![[COMBINED:[0-9]+]]
+; CHECK-NOT: define {{.*}}@plus1
+; CHECK: ![[COMBINED]] = !{!"function_entry_count", i64 3000}
+
+define internal i32 @add1(i32 %x) !prof !1 {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+define internal i32 @plus1(i32 %x) !prof !2 {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+define i32 @main() !prof !2 {
+entry:
+  %call = call i32 @add1(i32 5)
+  %call2 = call i32 @add1(i32 6)
+  %call3 = call i32 @plus1(i32 7)
+  %add = add nsw i32 %call, %call2
+  %sum = add nsw i32 %add, %call3
+  ret i32 %sum
+}
+
+!llvm.module.flags = !{!0}
+
+!0 = !{i32 1, !"ProfileSummary", !3}
+!1 = !{!"function_entry_count", i64 2000}
+!2 = !{!"function_entry_count", i64 1000}
+!3 = !{!4, !5, !6, !7, !8, !9, !10, !11}
+!4 = !{!"ProfileFormat", !"InstrProf"}
+!5 = !{!"TotalCount", i64 4000}
+!6 = !{!"MaxCount", i64 2000}
+!7 = !{!"MaxInternalCount", i64 2000}
+!8 = !{!"MaxFunctionCount", i64 2000}
+!9 = !{!"NumCounts", i64 3}
+!10 = !{!"NumFunctions", i64 3}
+!11 = !{!"DetailedSummary", !12}
+!12 = !{!13}
+!13 = !{i32 10000, i64 1000, i32 2}
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..da894efa0e031
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-imports.ll
@@ -0,0 +1,48 @@
+; RUN: opt < %s -passes=mergefunc -S | FileCheck %s
+
+; Ensure that when two functions with differing import GUIDs are merged,
+; the surviving function gets the union of GUIDs and the summed entry count.
+
+; CHECK: define internal i32 @a(i32 %x) !prof ![[P:[0-9]+]]
+; CHECK-NOT: define {{.*}}@b
+; CHECK: ![[P]] = !{!"function_entry_count", i64 300
+; CHECK-DAG: i64 1001
+; CHECK-DAG: i64 2002
+; CHECK-DAG: i64 3003
+
+define internal i32 @a(i32 %x) !prof !2 {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+define internal i32 @b(i32 %x) !prof !3 {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+define i32 @main() {
+entry:
+  %c1 = call i32 @a(i32 1)
+  %c2 = call i32 @b(i32 2)
+  %r = add nsw i32 %c1, %c2
+  ret i32 %r
+}
+
+!llvm.module.flags = !{!0}
+
+!0 = !{i32 1, !"ProfileSummary", !4}
+!2 = !{!"function_entry_count", i64 100, i64 1001, i64 2002}
+!3 = !{!"function_entry_count", i64 200, i64 2002, i64 3003}
+!4 = !{!5, !6, !7, !8, !9, !10, !11, !12}
+!5 = !{!"ProfileFormat", !"InstrProf"}
+!6 = !{!"TotalCount", i64 300}
+!7 = !{!"MaxCount", i64 200}
+!8 = !{!"MaxInternalCount", i64 200}
+!9 = !{!"MaxFunctionCount", i64 200}
+!10 = !{!"NumCounts", i64 2}
+!11 = !{!"NumFunctions", i64 2}
+!12 = !{!"DetailedSummary", !13}
+!13 = !{!14}
+!14 = !{i32 10000, i64 100, i32 1}
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-odr.ll b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-odr.ll
new file mode 100644
index 0000000000000..e35fd9461bf57
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-odr.ll
@@ -0,0 +1,69 @@
+; RUN: opt -S -passes=mergefunc < %s | FileCheck %s
+
+; After merging on the ODR/double-thunk path, the private merged body absorbs
+; the combined entry count and the union of import GUIDs, while both
+; linkonce_odr thunks (kept alive by llvm.used) keep their original count.
+
+; CHECK: define private i32 @0(i32 %x) !prof ![[COMBINED:[0-9]+]] {
+; CHECK-DAG: define linkonce_odr i32 @add_odr(i32 {{.*}}) !prof ![[ORIG:[0-9]+]] {
+; CHECK-DAG: define linkonce_odr i32 @plus_odr(i32 {{.*}}) !prof ![[ORIG]] {
+
+define linkonce_odr i32 @add_odr(i32 %x) !prof !1 {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+define linkonce_odr i32 @plus_odr(i32 %x) !prof !2 {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+define i32 @use_odr(i32 %a, i32 %b) {
+  %res = call i32 @add_odr(i32 %a)
+  %res2 = call i32 @plus_odr(i32 %b)
+  %res3 = add i32 %res, %res2
+  ret i32 %res3
+}
+
+ at llvm.used = appending global [2 x ptr] [ptr @add_odr, ptr @plus_odr], section "llvm.metadata"
+
+; CHECK-DAG: define internal i32 @add_synthetic(i32 %x) !prof ![[SYNTH_COMBINED:[0-9]+]] {
+
+define internal i32 @add_synthetic(i32 %x) !prof !4 {
+entry:
+  %add = add nsw i32 %x, 2
+  ret i32 %add
+}
+
+define internal i32 @plus_synthetic(i32 %x) !prof !15 {
+entry:
+  %add = add nsw i32 %x, 2
+  ret i32 %add
+}
+
+!llvm.module.flags = !{!0}
+
+!0 = !{i32 1, !"ProfileSummary", !3}
+!1 = !{!"function_entry_count", i64 1, i64 1001}
+!2 = !{!"function_entry_count", i64 1, i64 2002}
+!3 = !{!4, !5, !6, !7, !8, !9, !10, !11}
+!4 = !{!"synthetic_function_entry_count", i64 1}
+!5 = !{!"ProfileFormat", !"InstrProf"}
+!6 = !{!"TotalCount", i64 2}
+!7 = !{!"MaxCount", i64 1}
+!8 = !{!"MaxInternalCount", i64 0}
+!9 = !{!"MaxFunctionCount", i64 1}
+!10 = !{!"NumCounts", i64 2}
+!11 = !{!"NumFunctions", i64 2}
+!12 = !{!"DetailedSummary", !13}
+!13 = !{!14}
+!14 = !{i32 10000, i64 1, i32 2}
+
+; CHECK: ![[COMBINED]] = !{!"function_entry_count", i64 2
+; CHECK-DAG: i64 1001
+; CHECK-DAG: i64 2002
+; CHECK-DAG: ![[ORIG]] = !{!"function_entry_count", i64 1}
+; CHECK-DAG: ![[SYNTH_COMBINED]] = !{!"synthetic_function_entry_count", i64 2}
+!15 = !{!"function_entry_count", i64 1}
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-one-sided.ll b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-one-sided.ll
new file mode 100644
index 0000000000000..73b6bfdc13222
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-one-sided.ll
@@ -0,0 +1,44 @@
+; RUN: opt < %s -passes=mergefunc -S | FileCheck %s
+
+; Only the merged-away function @plus1 carries an entry count; verify that
+; the surviving body @add1 still ends up with the (one-sided) combined count.
+
+; CHECK: define internal i32 @add1(i32 %x) !prof ![[INHERITED:[0-9]+]]
+; CHECK-NOT: define {{.*}}@plus1
+; CHECK: ![[INHERITED]] = !{!"function_entry_count", i64 500}
+
+define internal i32 @add1(i32 %x) {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+define internal i32 @plus1(i32 %x) !prof !2 {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+define i32 @main() {
+entry:
+  %call = call i32 @add1(i32 5)
+  %call4 = call i32 @plus1(i32 7)
+  %add = add nsw i32 %call, %call4
+  ret i32 %add
+}
+
+!llvm.module.flags = !{!0}
+
+!0 = !{i32 1, !"ProfileSummary", !3}
+!2 = !{!"function_entry_count", i64 500}
+!3 = !{!4, !5, !6, !7, !8, !9, !10, !11}
+!4 = !{!"ProfileFormat", !"InstrProf"}
+!5 = !{!"TotalCount", i64 500}
+!6 = !{!"MaxCount", i64 500}
+!7 = !{!"MaxInternalCount", i64 500}
+!8 = !{!"MaxFunctionCount", i64 500}
+!9 = !{!"NumCounts", i64 1}
+!10 = !{!"NumFunctions", i64 1}
+!11 = !{!"DetailedSummary", !12}
+!12 = !{!13}
+!13 = !{i32 10000, i64 500, i32 1}
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-real-to-synthetic.ll b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-real-to-synthetic.ll
new file mode 100644
index 0000000000000..8be1a1effffa4
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count-real-to-synthetic.ll
@@ -0,0 +1,48 @@
+; RUN: opt < %s -passes=mergefunc -S | FileCheck %s
+
+; The retained function (lexicographically first internal function, @aaa)
+; carries a real entry count, while the merged-away @zzz carries a synthetic
+; entry count. The merged result must be promoted to synthetic without tripping
+; Function::setEntryCount()'s debug assert that the previous count type must
+; match the new one.
+
+; CHECK: define internal i32 @aaa(i32 %x) !prof ![[COMBINED:[0-9]+]]
+; CHECK-NOT: define {{.*}}@zzz
+; CHECK: ![[COMBINED]] = !{!"synthetic_function_entry_count", i64 15}
+
+define internal i32 @aaa(i32 %x) !prof !1 {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+define internal i32 @zzz(i32 %x) !prof !2 {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+define i32 @main() {
+entry:
+  %call = call i32 @aaa(i32 5)
+  %call4 = call i32 @zzz(i32 7)
+  %add = add nsw i32 %call, %call4
+  ret i32 %add
+}
+
+!llvm.module.flags = !{!0}
+
+!0 = !{i32 1, !"ProfileSummary", !3}
+!1 = !{!"function_entry_count", i64 10}
+!2 = !{!"synthetic_function_entry_count", i64 5}
+!3 = !{!4, !5, !6, !7, !8, !9, !10, !11}
+!4 = !{!"ProfileFormat", !"InstrProf"}
+!5 = !{!"TotalCount", i64 15}
+!6 = !{!"MaxCount", i64 10}
+!7 = !{!"MaxInternalCount", i64 10}
+!8 = !{!"MaxFunctionCount", i64 10}
+!9 = !{!"NumCounts", i64 2}
+!10 = !{!"NumFunctions", i64 2}
+!11 = !{!"DetailedSummary", !12}
+!12 = !{!13}
+!13 = !{i32 10000, i64 10, i32 1}
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-entry-count.ll b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count.ll
new file mode 100644
index 0000000000000..5d8b58be7683c
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-entry-count.ll
@@ -0,0 +1,48 @@
+; RUN: opt < %s -passes=mergefunc -S 2>&1 | FileCheck %s --check-prefix=IR
+
+; IR: define internal {{.*}}i32 @add1(i32 %x) !prof ![[ADD1PROF:[0-9]+]]
+; IR-NOT: define {{.*}}@plus1
+; IR: ![[ADD1PROF]] = !{!"function_entry_count", i64 2}
+
+source_filename = "mymergefun.c"
+
+define internal range(i32 -2147483647, -2147483648) i32 @add1(i32 %x) !prof !14 {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+define internal range(i32 -2147483647, -2147483648) i32 @plus1(i32 %x) !prof !14 {
+entry:
+  %add = add nsw i32 %x, 1
+  ret i32 %add
+}
+
+; Function Attrs: inlinehint
+define i32 @main() #0 !prof !14 {
+entry:
+  %call = call i32 @add1(i32 5)
+  %call4 = call i32 @plus1(i32 7)
+  %add = add nsw i32 %call, %call4
+  ret i32 %add
+}
+
+attributes #0 = { inlinehint }
+
+!llvm.module.flags = !{!0}
+
+!0 = !{i32 1, !"ProfileSummary", !1}
+!1 = !{!2, !3, !4, !5, !6, !7, !8, !9, !10, !11}
+!2 = !{!"ProfileFormat", !"InstrProf"}
+!3 = !{!"TotalCount", i64 3}
+!4 = !{!"MaxCount", i64 1}
+!5 = !{!"MaxInternalCount", i64 0}
+!6 = !{!"MaxFunctionCount", i64 1}
+!7 = !{!"NumCounts", i64 3}
+!8 = !{!"NumFunctions", i64 3}
+!9 = !{!"IsPartialProfile", i64 0}
+!10 = !{!"PartialProfileRatio", double 0.000000e+00}
+!11 = !{!"DetailedSummary", !12}
+!12 = !{!13}
+!13 = !{i32 10000, i64 0, i32 0}
+!14 = !{!"function_entry_count", i64 1}



More information about the llvm-commits mailing list