[llvm] Reland "[MergeFunctions] Preserve instruction-level profile metadata during merging" (#208009) (PR #210138)

Alok Kumar Sharma via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 5 09:54:18 PDT 2026


https://github.com/alokkrsharma updated https://github.com/llvm/llvm-project/pull/210138

>From 14b8726c6e87f3a328be003e6781bdccfd4092dd Mon Sep 17 00:00:00 2001
From: Alok Kumar Sharma <AlokKumar.Sharma at amd.com>
Date: Thu, 16 Jul 2026 10:07:53 +0530
Subject: [PATCH 1/3] [MergeFunctions] Preserve instruction-level profile
 metadata during merging (#208009)

Merge branch weights and value profile metadata when equivalent
functions are folded. Branch weights are relative, so we scale them
to absolute counts via BlockFrequencyInfo before combining. Value
profile counts are already absolute, so we just sum them directly.
---
 .../llvm/Transforms/IPO/MergeFunctions.h      |   4 +-
 llvm/lib/Transforms/IPO/MergeFunctions.cpp    | 226 +++++++++++++++--
 .../merge-functions-branch-weights.ll         | 239 ++++++++++++++++++
 ...nctions-reordered-blocks-branch-weights.ll | 121 +++++++++
 .../merge-functions-select-weights.ll         |  50 ++++
 .../merge-functions-value-profile.ll          | 142 +++++++++++
 .../Transforms/IPO/MergeFunctionsTest.cpp     |  75 ++++--
 7 files changed, 813 insertions(+), 44 deletions(-)
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-branch-weights.ll
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-reordered-blocks-branch-weights.ll
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-select-weights.ll
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-value-profile.ll

diff --git a/llvm/include/llvm/Transforms/IPO/MergeFunctions.h b/llvm/include/llvm/Transforms/IPO/MergeFunctions.h
index 75399d3297a05..d28d6167e5555 100644
--- a/llvm/include/llvm/Transforms/IPO/MergeFunctions.h
+++ b/llvm/include/llvm/Transforms/IPO/MergeFunctions.h
@@ -28,9 +28,9 @@ class MergeFunctionsPass : public OptionalPassInfoMixin<MergeFunctionsPass> {
 public:
   LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
 
-  LLVM_ABI static bool runOnModule(Module &M);
+  LLVM_ABI static bool runOnModule(Module &M, ModuleAnalysisManager &AM);
   LLVM_ABI static DenseMap<Function *, Function *>
-  runOnFunctions(ArrayRef<Function *> F);
+  runOnFunctions(ArrayRef<Function *> Funcs, ModuleAnalysisManager &AM);
 };
 
 } // end namespace llvm
diff --git a/llvm/lib/Transforms/IPO/MergeFunctions.cpp b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
index 9ea9dbfa3a048..cd35058d099f6 100644
--- a/llvm/lib/Transforms/IPO/MergeFunctions.cpp
+++ b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
@@ -89,10 +89,16 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Transforms/IPO/MergeFunctions.h"
+#include "llvm/ADT/APInt.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/PostOrderIterator.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/Statistic.h"
+#include "llvm/Analysis/BlockFrequencyInfo.h"
+#include "llvm/Analysis/BranchProbabilityInfo.h"
 #include "llvm/IR/Argument.h"
 #include "llvm/IR/BasicBlock.h"
 #include "llvm/IR/DebugInfoMetadata.h"
@@ -105,16 +111,21 @@
 #include "llvm/IR/Instruction.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/Metadata.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/IR/ProfDataUtils.h"
 #include "llvm/IR/StructuralHash.h"
 #include "llvm/IR/Type.h"
 #include "llvm/IR/Use.h"
 #include "llvm/IR/User.h"
 #include "llvm/IR/Value.h"
 #include "llvm/IR/ValueHandle.h"
+#include "llvm/ProfileData/InstrProf.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
+#include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/MathExtras.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Transforms/IPO.h"
@@ -122,7 +133,10 @@
 #include "llvm/Transforms/Utils/ModuleUtils.h"
 #include <algorithm>
 #include <cassert>
+#include <cstddef>
+#include <cstdint>
 #include <iterator>
+#include <memory>
 #include <optional>
 #include <set>
 #include <utility>
@@ -195,11 +209,11 @@ class FunctionNode {
 /// bitcast of the other.
 class MergeFunctions {
 public:
-  MergeFunctions() : FnTree(FunctionNodeCmp(&GlobalNumbers)) {
-  }
+  explicit MergeFunctions(FunctionAnalysisManager &FAM)
+      : FnTree(FunctionNodeCmp(&GlobalNumbers)), FAM(FAM) {}
 
   template <typename FuncContainer> bool run(FuncContainer &Functions);
-  DenseMap<Function *, Function *> runOnFunctions(ArrayRef<Function *> F);
+  DenseMap<Function *, Function *> runOnFunctions(ArrayRef<Function *> Funcs);
 
   SmallPtrSet<GlobalValue *, 4> &getUsed();
 
@@ -258,6 +272,8 @@ class MergeFunctions {
   /// again.
   void mergeTwoFunctions(Function *F, Function *G);
 
+  void mergeInstrProfMetadataInto(Function *Dst, Function *Src);
+
   /// Fill PDIUnrelatedWL with instructions from the entry block that are
   /// unrelated to parameter related debug info.
   /// \param PDVRUnrelatedWL The equivalent non-intrinsic debug records.
@@ -308,20 +324,23 @@ class MergeFunctions {
 
   /// Deleted-New functions mapping
   DenseMap<Function *, Function *> DelToNewMap;
+
+  FunctionAnalysisManager &FAM;
 };
 } // end anonymous namespace
 
 PreservedAnalyses MergeFunctionsPass::run(Module &M,
                                           ModuleAnalysisManager &AM) {
-  if (!MergeFunctionsPass::runOnModule(M))
+  if (!MergeFunctionsPass::runOnModule(M, AM))
     return PreservedAnalyses::all();
   return PreservedAnalyses::none();
 }
 
 SmallPtrSet<GlobalValue *, 4> &MergeFunctions::getUsed() { return Used; }
 
-bool MergeFunctionsPass::runOnModule(Module &M) {
-  MergeFunctions MF;
+bool MergeFunctionsPass::runOnModule(Module &M, ModuleAnalysisManager &AM) {
+  auto &FAM = AM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager();
+  MergeFunctions MF(FAM);
   SmallVector<GlobalValue *, 4> UsedV;
   collectUsedGlobalVariables(M, UsedV, /*CompilerUsed=*/false);
   collectUsedGlobalVariables(M, UsedV, /*CompilerUsed=*/true);
@@ -330,9 +349,15 @@ bool MergeFunctionsPass::runOnModule(Module &M) {
 }
 
 DenseMap<Function *, Function *>
-MergeFunctionsPass::runOnFunctions(ArrayRef<Function *> F) {
-  MergeFunctions MF;
-  return MF.runOnFunctions(F);
+MergeFunctionsPass::runOnFunctions(ArrayRef<Function *> Funcs,
+                                   ModuleAnalysisManager &AM) {
+  if (Funcs.empty())
+    return DenseMap<Function *, Function *>();
+
+  Module &M = *Funcs.front()->getParent();
+  auto &FAM = AM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager();
+  MergeFunctions MF(FAM);
+  return MF.runOnFunctions(Funcs);
 }
 
 #ifndef NDEBUG
@@ -496,8 +521,8 @@ template <typename FuncContainer> bool MergeFunctions::run(FuncContainer &M) {
 }
 
 DenseMap<Function *, Function *>
-MergeFunctions::runOnFunctions(ArrayRef<Function *> F) {
-  [[maybe_unused]] bool MergeResult = this->run(F);
+MergeFunctions::runOnFunctions(ArrayRef<Function *> Funcs) {
+  [[maybe_unused]] bool MergeResult = this->run(Funcs);
   assert(MergeResult == !DelToNewMap.empty());
   return this->DelToNewMap;
 }
@@ -737,7 +762,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) {
-  std::optional<uint64_t> GEC = G->getEntryCount();
+  std::optional<uint64_t> GEntryCount = G->getEntryCount();
   BasicBlock *GEntryBlock = nullptr;
   std::vector<Instruction *> PDIUnrelatedWL;
   std::vector<DbgVariableRecord *> PDVRUnrelatedWL;
@@ -807,8 +832,8 @@ void MergeFunctions::writeThunk(Function *F, Function *G) {
                << G->getName() << "()\n");
   } else {
     NewG->copyAttributesFrom(G);
-    if (GEC)
-      NewG->setEntryCount(*GEC);
+    if (GEntryCount)
+      NewG->setEntryCount(*GEntryCount);
     NewG->takeName(G);
     // Ensure CFI type metadata is propagated to the new function.
     copyMetadataIfPresent(G, NewG, "type");
@@ -898,8 +923,10 @@ bool MergeFunctions::writeThunkOrAliasIfNeeded(Function *F, Function *G,
   if (!ShouldErase && !ShouldAlias && !ShouldThunk)
     return false;
 
-  if (MergeProfile)
+  if (MergeProfile) {
+    mergeInstrProfMetadataInto(F, G);
     mergeEntryCountsAndImportsInto(*F, *G);
+  }
 
   if (ShouldErase) {
     G->eraseFromParent();
@@ -923,6 +950,174 @@ static bool isODR(const Function *F) {
   return F->hasWeakODRLinkage() || F->hasLinkOnceODRLinkage();
 }
 
+static uint64_t getBlockCountForMerging(const BlockFrequencyInfo &BFI,
+                                        const BasicBlock *BB) {
+  if (auto Count = BFI.getBlockProfileCount(BB, /*AllowSynthetic=*/true))
+    return *Count;
+  return 1;
+}
+
+// The branch weights are relative within a function. Before merging we
+// normalize these to absolute counts.
+// (weight * BlockCount / TotalWeight)
+static uint64_t scaleToBlockCount(uint64_t Weight, uint64_t TotalWeight,
+                                  uint64_t BlockCount) {
+  if (Weight == 0 || TotalWeight == 0 || BlockCount == 0)
+    return 0;
+  APInt Num(128, BlockCount);
+  Num *= APInt(128, Weight);
+  APInt Den(128, TotalWeight);
+  Num = (Num + Den.lshr(1)).udiv(Den);
+  assert(Num.getActiveBits() <= 64 &&
+         "scaleToBlockCount: result exceeds uint64_t; Weight > TotalWeight?");
+  return Num.getLimitedValue();
+}
+
+// Combine the scaled branch_weights of corresponding instructions of F and G.
+static void mergeBranchWeightsOnInstructions(Instruction *DstI,
+                                             const Instruction *SrcI,
+                                             const BlockFrequencyInfo &DstBFI,
+                                             const BlockFrequencyInfo &SrcBFI) {
+  SmallVector<uint32_t, 8> DstWeights, SrcWeights;
+  bool HasDst = extractBranchWeights(*DstI, DstWeights);
+  bool HasSrc = extractBranchWeights(*SrcI, SrcWeights);
+  if (!HasDst && !HasSrc)
+    return;
+
+  uint64_t DstBlockCount = getBlockCountForMerging(DstBFI, DstI->getParent());
+  uint64_t SrcBlockCount = getBlockCountForMerging(SrcBFI, SrcI->getParent());
+
+  uint64_t DstTotal = 0, SrcTotal = 0;
+  if (HasDst)
+    extractProfTotalWeight(*DstI, DstTotal);
+  if (HasSrc)
+    extractProfTotalWeight(*SrcI, SrcTotal);
+
+  assert((!HasDst || !HasSrc || DstWeights.size() == SrcWeights.size()) &&
+         "equivalent branch/select instructions must have matching weight "
+         "arity");
+  size_t NumWeights = HasDst ? DstWeights.size() : SrcWeights.size();
+  SmallVector<uint64_t, 8> MergedWeights;
+  MergedWeights.reserve(NumWeights);
+  for (size_t I = 0; I < NumWeights; ++I) {
+    uint64_t DstW = HasDst ? DstWeights[I] : 0;
+    uint64_t SrcW = HasSrc ? SrcWeights[I] : 0;
+    uint64_t DstAbs = scaleToBlockCount(DstW, DstTotal, DstBlockCount);
+    uint64_t SrcAbs = scaleToBlockCount(SrcW, SrcTotal, SrcBlockCount);
+    MergedWeights.push_back(SaturatingAdd(DstAbs, SrcAbs));
+  }
+
+  bool IsExpected =
+      hasBranchWeightOrigin(*DstI) && hasBranchWeightOrigin(*SrcI);
+  setFittedBranchWeights(*DstI, MergedWeights, IsExpected);
+}
+
+// Accumulate value profile counts of Instruction I into Merged. Value profile
+// counts are absolute, not relative branch-style weights.
+static void addValueProfile(const Instruction &I, InstrProfValueKind Kind,
+                            DenseMap<uint64_t, uint64_t> &Merged) {
+  uint64_t Total = 0;
+  SmallVector<InstrProfValueData, 4> VDs =
+      getValueProfDataFromInst(I, Kind, /*MaxNumValueData=*/UINT32_MAX, Total);
+  if (VDs.empty())
+    return;
+  for (const InstrProfValueData &VD : VDs)
+    Merged[VD.Value] = SaturatingAdd(Merged[VD.Value], VD.Count);
+}
+
+// Merge (union) value profiles of Dst and Src.
+static void mergeValueProfileOnInstructions(Instruction *DstI,
+                                            const Instruction *SrcI) {
+  MDNode *DstProf = DstI->getMetadata(LLVMContext::MD_prof);
+  MDNode *SrcProf = SrcI->getMetadata(LLVMContext::MD_prof);
+  bool HasDst = DstProf && isValueProfileMD(DstProf);
+  bool HasSrc = SrcProf && isValueProfileMD(SrcProf);
+  if (!HasDst && !HasSrc)
+    return;
+
+  auto *DstKind =
+      HasDst ? mdconst::dyn_extract<ConstantInt>(DstProf->getOperand(1))
+             : nullptr;
+  auto *SrcKind =
+      HasSrc ? mdconst::dyn_extract<ConstantInt>(SrcProf->getOperand(1))
+             : nullptr;
+  if (HasDst && HasSrc && DstKind && SrcKind &&
+      DstKind->getZExtValue() != SrcKind->getZExtValue()) {
+    DstI->setMetadata(LLVMContext::MD_prof, nullptr);
+    return;
+  }
+
+  const ConstantInt *KindCI = DstKind ? DstKind : SrcKind;
+  if (!KindCI) {
+    DstI->setMetadata(LLVMContext::MD_prof, nullptr);
+    return;
+  }
+
+  InstrProfValueKind Kind =
+      static_cast<InstrProfValueKind>(KindCI->getZExtValue());
+
+  DenseMap<uint64_t, uint64_t> Merged;
+  if (HasDst)
+    addValueProfile(*DstI, Kind, Merged);
+  if (HasSrc)
+    addValueProfile(*SrcI, Kind, Merged);
+
+  if (Merged.empty())
+    return;
+
+  SmallVector<InstrProfValueData, 8> VDs;
+  VDs.reserve(Merged.size());
+  uint64_t Sum = 0;
+  for (auto &[Value, Count] : Merged) {
+    VDs.push_back({Value, Count});
+    Sum = SaturatingAdd(Sum, Count);
+  }
+  llvm::sort(VDs, [](const InstrProfValueData &A, const InstrProfValueData &B) {
+    return A.Count > B.Count;
+  });
+  annotateValueSite(*DstI->getFunction()->getParent(), *DstI, VDs, Sum, Kind,
+                    VDs.size());
+}
+
+/// Merge \p Src's instruction-level branch weights and value profile
+/// metadata into the corresponding instructions of \p Dst. \p Dst is the
+/// surviving function; \p Src will be erased or rewritten after this call.
+/// Both functions must be structurally identical.
+void MergeFunctions::mergeInstrProfMetadataInto(Function *Dst, Function *Src) {
+  const BlockFrequencyInfo &DstBFI =
+      FAM.getResult<BlockFrequencyAnalysis>(*Dst);
+  const BlockFrequencyInfo &SrcBFI =
+      FAM.getResult<BlockFrequencyAnalysis>(*Src);
+
+  // FunctionComparator guarantees identical CFG topology and instruction
+  // ordering. Walk the CFGs in RPO rather than function block-list order, as
+  // equivalent functions need not store their basic blocks in the same order.
+  ReversePostOrderTraversal<Function *> DstRPOT(Dst);
+  ReversePostOrderTraversal<Function *> SrcRPOT(Src);
+  for (auto [DstBB, SrcBB] : llvm::zip_equal(DstRPOT, SrcRPOT)) {
+    for (auto [DstI, SrcI] : llvm::zip_equal(*DstBB, *SrcBB)) {
+      MDNode *DstProf = DstI.getMetadata(LLVMContext::MD_prof);
+      MDNode *SrcProf = SrcI.getMetadata(LLVMContext::MD_prof);
+      if ((DstProf && isValueProfileMD(DstProf)) ||
+          (SrcProf && isValueProfileMD(SrcProf)))
+        mergeValueProfileOnInstructions(&DstI, &SrcI);
+
+      // Handle branch weights on SelectInsts here. Terminators are handled
+      // separately below, outside the instruction loop.
+      if (isa<SelectInst>(DstI))
+        mergeBranchWeightsOnInstructions(&DstI, &SrcI, DstBFI, SrcBFI);
+    }
+    Instruction *DstTerm = DstBB->getTerminator();
+    const Instruction *SrcTerm = SrcBB->getTerminator();
+    mergeBranchWeightsOnInstructions(DstTerm, SrcTerm, DstBFI, SrcBFI);
+  }
+
+  PreservedAnalyses PA = PreservedAnalyses::all();
+  PA.abandon<BranchProbabilityAnalysis>();
+  PA.abandon<BlockFrequencyAnalysis>();
+  FAM.invalidate(*Dst, PA);
+}
+
 // Merge two equivalent functions. Upon completion, Function G is deleted.
 void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
 
@@ -1009,6 +1204,7 @@ 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) {
+      mergeInstrProfMetadataInto(F, G);
       mergeEntryCountsAndImportsInto(*F, *G);
       G->eraseFromParent();
       ++NumFunctionsMerged;
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-branch-weights.ll b/llvm/test/Transforms/MergeFunc/merge-functions-branch-weights.ll
new file mode 100644
index 0000000000000..46a80c2916766
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-branch-weights.ll
@@ -0,0 +1,239 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --include-generated-funcs --version 6
+; RUN: opt -S -passes=mergefunc < %s | FileCheck %s
+
+; Verify that MergeFunctions correctly combines branch_weights on
+; conditional branches when merging structurally identical functions.
+; Because FunctionComparator only matches functions whose control-flow
+; structure and instruction sequence are identical, MergeFunctions can
+; process both functions side by side and merge any instruction-level
+; metadata, including branch weight information.
+; To compute the merged weights, the pass converts each function's
+; relative branch weights into absolute execution counts using the
+; function's entry count, then adds the corresponding counts together.
+
+; --------------------------------------------------------------------------
+; Both functions provide branch weight metadata.
+; br_both_a: entry count = 100, weights = 10:90
+;            absolute counts: true = 10, false = 90
+; br_both_b: entry count = 200, weights = 30:70
+;            absolute counts: true = 60, false = 140
+; After merging:
+;            true  = 10 + 60  = 70
+;            false = 90 + 140 = 230
+; --------------------------------------------------------------------------
+define internal i32 @br_both_a(i32 %x) !prof !1 {
+entry:
+  %cmp = icmp slt i32 %x, 0
+  br i1 %cmp, label %then, label %else, !prof !2
+
+then:
+  ret i32 1
+
+else:
+  ret i32 0
+}
+
+define internal i32 @br_both_b(i32 %x) !prof !3 {
+entry:
+  %cmp = icmp slt i32 %x, 0
+  br i1 %cmp, label %then, label %else, !prof !4
+
+then:
+  ret i32 1
+
+else:
+  ret i32 0
+}
+
+; --------------------------------------------------------------------------
+; Only one of the functions provides branch weight metadata.
+; br_one_a has an entry count of 40 but does not specify any
+; branch_weights.
+; br_one_b has an entry count of 60 with branch weights of 40:80,
+; which correspond to absolute counts of true = 20 and false = 40.
+; Since only br_one_b contributes branch weight information, the
+; merged branch retains those counts:
+;            true  = 20
+;            false = 40
+; --------------------------------------------------------------------------
+define internal i32 @br_one_a(i32 %x) !prof !5 {
+entry:
+  %cmp = icmp sgt i32 %x, 0
+  br i1 %cmp, label %then, label %else
+
+then:
+  ret i32 7
+
+else:
+  ret i32 8
+}
+
+define internal i32 @br_one_b(i32 %x) !prof !6 {
+entry:
+  %cmp = icmp sgt i32 %x, 0
+  br i1 %cmp, label %then, label %else, !prof !7
+
+then:
+  ret i32 7
+
+else:
+  ret i32 8
+}
+
+; --------------------------------------------------------------------------
+; Thunk case: two dso_local functions are merged through a thunk, and
+; their branch weight metadata is combined as part of the merge.
+; thunk_a has an entry count of 10 with branch weights of 1:9,
+; resulting in absolute counts of true = 1 and false = 9.
+; thunk_b has an entry count of 20 with branch weights of 3:17,
+; resulting in absolute counts of true = 3 and false = 17.
+; After merging the two functions, the corresponding counts are added:
+;            true  = 1 + 3  = 4
+;            false = 9 + 17 = 26
+; --------------------------------------------------------------------------
+define dso_local i32 @thunk_a(i32 %x) unnamed_addr !prof !8 {
+entry:
+  %cmp = icmp ult i32 %x, 3
+  br i1 %cmp, label %then, label %else, !prof !9
+
+then:
+  ret i32 9
+
+else:
+  ret i32 10
+}
+
+define dso_local i32 @thunk_b(i32 %x) unnamed_addr !prof !10 {
+entry:
+  %cmp = icmp ult i32 %x, 3
+  br i1 %cmp, label %then, label %else, !prof !11
+
+then:
+  ret i32 9
+
+else:
+  ret i32 10
+}
+
+; --------------------------------------------------------------------------
+; ODR case: two weak_odr functions are merged into a single shared
+; implementation, and their branch weight metadata is merged as well.
+; odr_a has an entry count of 100 with branch weights of 1:9,
+; corresponding to absolute counts of true = 10 and false = 90.
+; odr_b has an entry count of 200 with branch weights of 3:17,
+; corresponding to absolute counts of true = 30 and false = 170.
+; The merged function combines the counts from both inputs:
+;            true  = 10 + 30  = 40
+;            false = 90 + 170 = 260
+; --------------------------------------------------------------------------
+define weak_odr i32 @odr_a(i32 %x) !prof !12 {
+entry:
+  %cmp = icmp ugt i32 %x, 4
+  br i1 %cmp, label %then, label %else, !prof !13
+
+then:
+  ret i32 11
+
+else:
+  ret i32 12
+}
+
+define weak_odr i32 @odr_b(i32 %x) !prof !14 {
+entry:
+  %cmp = icmp ugt i32 %x, 4
+  br i1 %cmp, label %then, label %else, !prof !15
+
+then:
+  ret i32 11
+
+else:
+  ret i32 12
+}
+
+!1 = !{!"function_entry_count", i64 100}
+!2 = !{!"branch_weights", i32 10, i32 90}
+!3 = !{!"function_entry_count", i64 200}
+!4 = !{!"branch_weights", i32 30, i32 70}
+!5 = !{!"function_entry_count", i64 40}
+!6 = !{!"function_entry_count", i64 60}
+!7 = !{!"branch_weights", i32 40, i32 80}
+!8 = !{!"function_entry_count", i64 10}
+!9 = !{!"branch_weights", i32 1, i32 9}
+!10 = !{!"function_entry_count", i64 20}
+!11 = !{!"branch_weights", i32 3, i32 17}
+!12 = !{!"function_entry_count", i64 100}
+!13 = !{!"branch_weights", i32 1, i32 9}
+!14 = !{!"function_entry_count", i64 200}
+!15 = !{!"branch_weights", i32 3, i32 17}
+; CHECK-LABEL: define internal i32 @br_both_a(
+; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF0:![0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF1:![0-9]+]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret i32 1
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    ret i32 0
+;
+;
+; CHECK-LABEL: define internal i32 @br_one_a(
+; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF2:![0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[X]], 0
+; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF3:![0-9]+]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret i32 7
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    ret i32 8
+;
+;
+; CHECK-LABEL: define dso_local i32 @thunk_a(
+; CHECK-SAME: i32 [[X:%.*]]) unnamed_addr !prof [[PROF4:![0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X]], 3
+; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF5:![0-9]+]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret i32 9
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    ret i32 10
+;
+;
+; CHECK-LABEL: define private i32 @0(
+; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i32 [[X]], 4
+; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF6:![0-9]+]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret i32 11
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    ret i32 12
+;
+;
+; CHECK-LABEL: define dso_local i32 @thunk_b(
+; CHECK-SAME: i32 [[TMP0:%.*]]) unnamed_addr !prof [[PROF7:![0-9]+]] {
+; CHECK-NEXT:    [[TMP2:%.*]] = tail call i32 @thunk_a(i32 [[TMP0]])
+; CHECK-NEXT:    ret i32 [[TMP2]]
+;
+;
+; CHECK-LABEL: define weak_odr i32 @odr_b(
+; CHECK-SAME: i32 [[TMP0:%.*]]) !prof [[PROF8:![0-9]+]] {
+; CHECK-NEXT:    [[TMP2:%.*]] = tail call i32 @[[GLOB0:[0-9]+]](i32 [[TMP0]])
+; CHECK-NEXT:    ret i32 [[TMP2]]
+;
+;
+; CHECK-LABEL: define weak_odr i32 @odr_a(
+; CHECK-SAME: i32 [[TMP0:%.*]]) !prof [[PROF2]] {
+; CHECK-NEXT:    [[TMP2:%.*]] = tail call i32 @[[GLOB0]](i32 [[TMP0]])
+; CHECK-NEXT:    ret i32 [[TMP2]]
+;
+;.
+; CHECK: [[PROF0]] = !{!"function_entry_count", i64 300}
+; CHECK: [[PROF1]] = !{!"branch_weights", i32 70, i32 230}
+; CHECK: [[PROF2]] = !{!"function_entry_count", i64 100}
+; CHECK: [[PROF3]] = !{!"branch_weights", i32 20, i32 40}
+; CHECK: [[PROF4]] = !{!"function_entry_count", i64 30}
+; CHECK: [[PROF5]] = !{!"branch_weights", i32 4, i32 26}
+; CHECK: [[PROF6]] = !{!"branch_weights", i32 40, i32 260}
+; CHECK: [[PROF7]] = !{!"function_entry_count", i64 20}
+; CHECK: [[PROF8]] = !{!"function_entry_count", i64 200}
+;.
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-reordered-blocks-branch-weights.ll b/llvm/test/Transforms/MergeFunc/merge-functions-reordered-blocks-branch-weights.ll
new file mode 100644
index 0000000000000..6853813849243
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-reordered-blocks-branch-weights.ll
@@ -0,0 +1,121 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=mergefunc < %s | FileCheck %s
+
+; Verify that MergeFunctions correctly combines branch_weights when equivalent
+; functions store corresponding basic blocks in a different textual order.
+; mergeInstrProfMetadataInto must match blocks by CFG traversal, not function
+; block-list order, so the left and right branch metadata is merged with the
+; corresponding block even when @br_reordered_b lists right before left.
+
+; --------------------------------------------------------------------------
+; Both functions have the same CFG and successor order:
+;            entry -> left/right
+;            left  -> ret1/ret2
+;            right -> ret3/ret4
+;
+; br_reordered_a block order: entry, left, right, ret1, ret2, ret3, ret4
+; br_reordered_b block order: entry, right, left, ret1, ret2, ret3, ret4
+;
+; entry is reached with counts 100 and 200, and both functions split it evenly:
+;            true  = 50 + 100 = 150
+;            false = 50 + 100 = 150
+;
+; left is reached with counts 50 and 100.
+; br_reordered_a left weights = 1:4, giving counts 10:40.
+; br_reordered_b left weights = 3:7, giving counts 30:70.
+; After merging left:
+;            true  = 10 + 30 = 40
+;            false = 40 + 70 = 110
+;
+; right is reached with counts 50 and 100.
+; br_reordered_a right weights = 2:3, giving counts 20:30.
+; br_reordered_b right weights = 4:6, giving counts 40:60.
+; After merging right:
+;            true  = 20 + 40 = 60
+;            false = 30 + 60 = 90
+; --------------------------------------------------------------------------
+define internal i32 @br_reordered_a(i32 %x, i32 %y) !prof !0 {
+; CHECK-LABEL: define internal i32 @br_reordered_a(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) !prof [[PROF0:![0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[ENTRY_CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT:    br i1 [[ENTRY_CMP]], label %[[LEFT:.*]], label %[[RIGHT:.*]], !prof [[PROF1:![0-9]+]]
+; CHECK:       [[LEFT]]:
+; CHECK-NEXT:    [[LEFT_CMP:%.*]] = icmp slt i32 [[Y]], 10
+; CHECK-NEXT:    br i1 [[LEFT_CMP]], label %[[RET1:.*]], label %[[RET2:.*]], !prof [[PROF2:![0-9]+]]
+; CHECK:       [[RIGHT]]:
+; CHECK-NEXT:    [[RIGHT_CMP:%.*]] = icmp slt i32 [[Y]], 20
+; CHECK-NEXT:    br i1 [[RIGHT_CMP]], label %[[RET3:.*]], label %[[RET4:.*]], !prof [[PROF3:![0-9]+]]
+; CHECK:       [[RET1]]:
+; CHECK-NEXT:    ret i32 1
+; CHECK:       [[RET2]]:
+; CHECK-NEXT:    ret i32 2
+; CHECK:       [[RET3]]:
+; CHECK-NEXT:    ret i32 3
+; CHECK:       [[RET4]]:
+; CHECK-NEXT:    ret i32 4
+;
+entry:
+  %entry.cmp = icmp slt i32 %x, 0
+  br i1 %entry.cmp, label %left, label %right, !prof !1
+
+left:                                             ; preds = %entry
+  %left.cmp = icmp slt i32 %y, 10
+  br i1 %left.cmp, label %ret1, label %ret2, !prof !2
+
+right:                                            ; preds = %entry
+  %right.cmp = icmp slt i32 %y, 20
+  br i1 %right.cmp, label %ret3, label %ret4, !prof !3
+
+ret1:                                             ; preds = %left
+  ret i32 1
+
+ret2:                                             ; preds = %left
+  ret i32 2
+
+ret3:                                             ; preds = %right
+  ret i32 3
+
+ret4:                                             ; preds = %right
+  ret i32 4
+}
+
+define internal i32 @br_reordered_b(i32 %x, i32 %y) !prof !4 {
+entry:
+  %entry.cmp = icmp slt i32 %x, 0
+  br i1 %entry.cmp, label %left, label %right, !prof !1
+
+right:                                            ; preds = %entry
+  %right.cmp = icmp slt i32 %y, 20
+  br i1 %right.cmp, label %ret3, label %ret4, !prof !5
+
+left:                                             ; preds = %entry
+  %left.cmp = icmp slt i32 %y, 10
+  br i1 %left.cmp, label %ret1, label %ret2, !prof !6
+
+ret1:                                             ; preds = %left
+  ret i32 1
+
+ret2:                                             ; preds = %left
+  ret i32 2
+
+ret3:                                             ; preds = %right
+  ret i32 3
+
+ret4:                                             ; preds = %right
+  ret i32 4
+}
+
+!0 = !{!"function_entry_count", i64 100}
+!1 = !{!"branch_weights", i32 1, i32 1}
+!2 = !{!"branch_weights", i32 1, i32 4}
+!3 = !{!"branch_weights", i32 2, i32 3}
+!4 = !{!"function_entry_count", i64 200}
+!5 = !{!"branch_weights", i32 4, i32 6}
+!6 = !{!"branch_weights", i32 3, i32 7}
+;.
+; CHECK: [[PROF0]] = !{!"function_entry_count", i64 300}
+; CHECK: [[PROF1]] = !{!"branch_weights", i32 150, i32 150}
+; CHECK: [[PROF2]] = !{!"branch_weights", i32 40, i32 110}
+; CHECK: [[PROF3]] = !{!"branch_weights", i32 60, i32 90}
+;.
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-select-weights.ll b/llvm/test/Transforms/MergeFunc/merge-functions-select-weights.ll
new file mode 100644
index 0000000000000..c052cabba6288
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-select-weights.ll
@@ -0,0 +1,50 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=mergefunc < %s | FileCheck %s
+;
+
+; Verify that MergeFunctions correctly combines select-instruction
+; branch_weights when merging two structurally identical functions.
+; Since FunctionComparator only considers functions equal when their
+; control-flow graphs and instruction order match exactly, MergeFunctions
+; can safely walk both functions in parallel and merge any instruction-level
+; metadata it encounters.
+;
+; The functions sel_a and sel_b differ only in their entry counts and
+; select branch_weights. When merged, the pass first converts each
+; function's relative branch weights into absolute execution counts using
+; its entry count, and then adds the counts together to produce the
+; final merged weights.
+;
+; sel_a: entry 50,  weights 10:90  -> true=5,   false=45
+; sel_b: entry 150, weights 30:70  -> true=45,  false=105
+; Merged:                         true=5+45=50,  false=45+105=150
+
+define internal i32 @sel_a(i32 %x) !prof !1 {
+; CHECK-LABEL: define internal i32 @sel_a(
+; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF0:![0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i32 1, i32 0, !prof [[PROF1:![0-9]+]]
+; CHECK-NEXT:    ret i32 [[SEL]]
+;
+entry:
+  %cmp = icmp slt i32 %x, 0
+  %sel = select i1 %cmp, i32 1, i32 0, !prof !2
+  ret i32 %sel
+}
+
+define internal i32 @sel_b(i32 %x) !prof !3 {
+entry:
+  %cmp = icmp slt i32 %x, 0
+  %sel = select i1 %cmp, i32 1, i32 0, !prof !4
+  ret i32 %sel
+}
+
+!1 = !{!"function_entry_count", i64 50}
+!2 = !{!"branch_weights", i32 10, i32 90}
+!3 = !{!"function_entry_count", i64 150}
+!4 = !{!"branch_weights", i32 30, i32 70}
+;.
+; CHECK: [[PROF0]] = !{!"function_entry_count", i64 200}
+; CHECK: [[PROF1]] = !{!"branch_weights", i32 50, i32 150}
+;.
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-value-profile.ll b/llvm/test/Transforms/MergeFunc/merge-functions-value-profile.ll
new file mode 100644
index 0000000000000..c5e9488b90480
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-value-profile.ll
@@ -0,0 +1,142 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=mergefunc < %s | FileCheck %s
+;
+
+; Verify that MergeFunctions correctly merges value profile (VP) metadata
+; on indirect calls when combining structurally identical functions.
+; Because FunctionComparator only considers functions equivalent when
+; their control-flow structure and instruction ordering match exactly,
+; MergeFunctions can walk the two functions in parallel and safely merge
+; instruction-level metadata.
+;
+; Value profile counts are already absolute. The pass combines profiles by
+; adding counts for targets that appear in both functions and preserving
+; targets that appear in only one.
+
+; --------------------------------------------------------------------------
+; Both functions provide value profile metadata for the indirect call.
+;
+; vp_both_a has an entry count of 100 and a profile of:
+;            {111:60, 222:40}
+;
+; vp_both_b has an entry count of 200 and a profile of:
+;            {111:120, 333:80}
+;
+; After merging, counts for shared targets are summed while unique
+; targets are carried over:
+;
+;            entry count = 300
+;            VP {111:180, 333:80, 222:40}
+; --------------------------------------------------------------------------
+define internal i32 @vp_both_a(ptr %fn) !prof !1 {
+; CHECK-LABEL: define internal i32 @vp_both_a(
+; CHECK-SAME: ptr [[FN:%.*]]) !prof [[PROF0:![0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[FN_VAL:%.*]] = load ptr, ptr [[FN]], align 8
+; CHECK-NEXT:    [[R:%.*]] = call i32 [[FN_VAL]](), !prof [[PROF1:![0-9]+]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val(), !prof !2
+  ret i32 %r
+}
+
+define internal i32 @vp_both_b(ptr %fn) !prof !3 {
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val(), !prof !4
+  ret i32 %r
+}
+
+; --------------------------------------------------------------------------
+; Only one of the functions provides value profile metadata for the
+; indirect call.
+;
+; vp_one_a has an entry count of 30 but does not contain any VP data
+; on the call site.
+;
+; vp_one_b has an entry count of 70 and records the target profile:
+;            {888:50}
+;
+; Since only vp_one_b contributes value profile information, the merged
+; function carries that profile forward unchanged while combining the
+; overall entry counts.
+;
+;            entry count = 100
+;            VP {888:50}
+; --------------------------------------------------------------------------
+define internal i32 @vp_one_a(ptr %fn) !prof !5 {
+; CHECK-LABEL: define internal i32 @vp_one_a(
+; CHECK-SAME: ptr [[FN:%.*]]) !prof [[PROF2:![0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[FN_VAL:%.*]] = load ptr, ptr [[FN]], align 8
+; CHECK-NEXT:    [[R:%.*]] = call i32 [[FN_VAL]](), !prof [[PROF3:![0-9]+]]
+; CHECK-NEXT:    [[ADJ:%.*]] = add i32 [[R]], 3
+; CHECK-NEXT:    ret i32 [[ADJ]]
+;
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val()
+  %adj = add i32 %r, 3
+  ret i32 %adj
+}
+
+define internal i32 @vp_one_b(ptr %fn) !prof !6 {
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val(), !prof !7
+  %adj = add i32 %r, 3
+  ret i32 %adj
+}
+
+; --------------------------------------------------------------------------
+; Both functions attach value profile metadata to the indirect call, but
+; they use different VP kinds.
+;
+; Because the profiles are not compatible, MergeFunctions cannot safely
+; merge them into a single representation. Rather than producing
+; potentially misleading profile data, the pass drops the call-site
+; !prof metadata during the merge.
+; --------------------------------------------------------------------------
+define internal i32 @vp_kind_a(ptr %fn) !prof !8 {
+; CHECK-LABEL: define internal i32 @vp_kind_a(
+; CHECK-SAME: ptr [[FN:%.*]]) !prof [[PROF2]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[FN_VAL:%.*]] = load ptr, ptr [[FN]], align 8
+; CHECK-NEXT:    [[R:%.*]] = call i32 [[FN_VAL]]()
+; CHECK-NEXT:    [[ADJ:%.*]] = add i32 [[R]], 2
+; CHECK-NEXT:    ret i32 [[ADJ]]
+;
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val(), !prof !9
+  %adj = add i32 %r, 2
+  ret i32 %adj
+}
+
+define internal i32 @vp_kind_b(ptr %fn) !prof !10 {
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val(), !prof !11
+  %adj = add i32 %r, 2
+  ret i32 %adj
+}
+
+!1 = !{!"function_entry_count", i64 100}
+!2 = !{!"VP", i32 0, i64 100, i64 111, i64 60, i64 222, i64 40}
+!3 = !{!"function_entry_count", i64 200}
+!4 = !{!"VP", i32 0, i64 200, i64 111, i64 120, i64 333, i64 80}
+!5 = !{!"function_entry_count", i64 30}
+!6 = !{!"function_entry_count", i64 70}
+!7 = !{!"VP", i32 0, i64 70, i64 888, i64 50}
+!8 = !{!"function_entry_count", i64 30}
+!9 = !{!"VP", i32 0, i64 30, i64 666, i64 20}
+!10 = !{!"function_entry_count", i64 70}
+!11 = !{!"VP", i32 1, i64 70, i64 777, i64 50}
+;.
+; CHECK: [[PROF0]] = !{!"function_entry_count", i64 300}
+; CHECK: [[PROF1]] = !{!"VP", i32 0, i64 300, i64 111, i64 180, i64 333, i64 80, i64 222, i64 40}
+; CHECK: [[PROF2]] = !{!"function_entry_count", i64 100}
+; CHECK: [[PROF3]] = !{!"VP", i32 0, i64 50, i64 888, i64 50}
+;.
diff --git a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
index 5be92b5ae3188..879b48e6457a3 100644
--- a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
+++ b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
@@ -9,9 +9,15 @@
 #include "llvm/Transforms/IPO/MergeFunctions.h"
 
 #include "llvm/ADT/SetVector.h"
+#include "llvm/Analysis/BlockFrequencyInfo.h"
+#include "llvm/Analysis/BranchProbabilityInfo.h"
+#include "llvm/Analysis/LoopInfo.h"
+#include "llvm/Analysis/PostDominators.h"
+#include "llvm/Analysis/TargetLibraryInfo.h"
 #include "llvm/AsmParser/Parser.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/PassInstrumentation.h"
 #include "llvm/Support/SourceMgr.h"
 #include "gtest/gtest.h"
 #include <memory>
@@ -20,10 +26,35 @@ using namespace llvm;
 
 namespace {
 
-TEST(MergeFunctions, TrueOutputModuleTest) {
+class MergeFunctionsTest : public testing::Test {
+protected:
   LLVMContext Ctx;
-  SMDiagnostic Err;
-  std::unique_ptr<Module> M(parseAssemblyString(R"invalid(
+  ModuleAnalysisManager MAM;
+  FunctionAnalysisManager FAM;
+
+  MergeFunctionsTest() {
+    FAM.registerPass([&] { return TargetLibraryAnalysis(); });
+    FAM.registerPass([&] { return DominatorTreeAnalysis(); });
+    FAM.registerPass([&] { return PostDominatorTreeAnalysis(); });
+    FAM.registerPass([&] { return LoopAnalysis(); });
+    FAM.registerPass([&] { return BranchProbabilityAnalysis(); });
+    FAM.registerPass([&] { return BlockFrequencyAnalysis(); });
+    FAM.registerPass([&] { return PassInstrumentationAnalysis(); });
+    FAM.registerPass([&] { return ModuleAnalysisManagerFunctionProxy(MAM); });
+    MAM.registerPass([&] { return PassInstrumentationAnalysis(); });
+    MAM.registerPass([&] { return FunctionAnalysisManagerModuleProxy(FAM); });
+  }
+
+  std::unique_ptr<Module> parseModule(StringRef IR) {
+    SMDiagnostic Err;
+    std::unique_ptr<Module> M = parseAssemblyString(IR, Err, Ctx);
+    EXPECT_TRUE(M);
+    return M;
+  }
+};
+
+TEST_F(MergeFunctionsTest, TrueOutputModuleTest) {
+  std::unique_ptr<Module> M = parseModule(R"invalid(
         @.str = private unnamed_addr constant [10 x i8] c"On f: %d\0A\00", align 1
         @.str.1 = private unnamed_addr constant [13 x i8] c"On main: %d\0A\00", align 1
 
@@ -64,17 +95,14 @@ TEST(MergeFunctions, TrueOutputModuleTest) {
                 %4 = add nsw i32 %3, 2
                 ret i32 %4
         }
-    )invalid",
-                                                Err, Ctx));
+    )invalid");
 
   // Expects true after merging _slice_add10 and _slice_add10_alt
-  EXPECT_TRUE(MergeFunctionsPass::runOnModule(*M));
+  EXPECT_TRUE(MergeFunctionsPass::runOnModule(*M, MAM));
 }
 
-TEST(MergeFunctions, TrueOutputFunctionsTest) {
-  LLVMContext Ctx;
-  SMDiagnostic Err;
-  std::unique_ptr<Module> M(parseAssemblyString(R"invalid(
+TEST_F(MergeFunctionsTest, TrueOutputFunctionsTest) {
+  std::unique_ptr<Module> M = parseModule(R"invalid(
         @.str = private unnamed_addr constant [10 x i8] c"On f: %d\0A\00", align 1
         @.str.1 = private unnamed_addr constant [13 x i8] c"On main: %d\0A\00", align 1
 
@@ -115,15 +143,14 @@ TEST(MergeFunctions, TrueOutputFunctionsTest) {
                 %4 = add nsw i32 %3, 2
                 ret i32 %4
         }
-    )invalid",
-                                                Err, Ctx));
+    )invalid");
 
   SetVector<Function *> FunctionsSet;
   for (Function &F : *M)
     FunctionsSet.insert(&F);
 
   DenseMap<Function *, Function *> MergeResult =
-      MergeFunctionsPass::runOnFunctions(FunctionsSet.getArrayRef());
+      MergeFunctionsPass::runOnFunctions(FunctionsSet.getArrayRef(), MAM);
 
   // Expects that both functions (_slice_add10 and _slice_add10_alt)
   // be mapped to the same new function
@@ -134,10 +161,8 @@ TEST(MergeFunctions, TrueOutputFunctionsTest) {
       EXPECT_EQ(P.second, NewFunction);
 }
 
-TEST(MergeFunctions, FalseOutputModuleTest) {
-  LLVMContext Ctx;
-  SMDiagnostic Err;
-  std::unique_ptr<Module> M(parseAssemblyString(R"invalid(
+TEST_F(MergeFunctionsTest, FalseOutputModuleTest) {
+  std::unique_ptr<Module> M = parseModule(R"invalid(
         @.str = private unnamed_addr constant [10 x i8] c"On f: %d\0A\00", align 1
         @.str.1 = private unnamed_addr constant [13 x i8] c"On main: %d\0A\00", align 1
 
@@ -178,17 +203,14 @@ TEST(MergeFunctions, FalseOutputModuleTest) {
                 %4 = add nsw i32 %3, 2
                 ret i32 %0
         }
-    )invalid",
-                                                Err, Ctx));
+    )invalid");
 
   // Expects false after trying to merge _slice_add10 and _slice_add10_alt
-  EXPECT_FALSE(MergeFunctionsPass::runOnModule(*M));
+  EXPECT_FALSE(MergeFunctionsPass::runOnModule(*M, MAM));
 }
 
-TEST(MergeFunctions, FalseOutputFunctionsTest) {
-  LLVMContext Ctx;
-  SMDiagnostic Err;
-  std::unique_ptr<Module> M(parseAssemblyString(R"invalid(
+TEST_F(MergeFunctionsTest, FalseOutputFunctionsTest) {
+  std::unique_ptr<Module> M = parseModule(R"invalid(
         @.str = private unnamed_addr constant [10 x i8] c"On f: %d\0A\00", align 1
         @.str.1 = private unnamed_addr constant [13 x i8] c"On main: %d\0A\00", align 1
 
@@ -229,15 +251,14 @@ TEST(MergeFunctions, FalseOutputFunctionsTest) {
                 %4 = add nsw i32 %3, 2
                 ret i32 %0
         }
-    )invalid",
-                                                Err, Ctx));
+    )invalid");
 
   SetVector<Function *> FunctionsSet;
   for (Function &F : *M)
     FunctionsSet.insert(&F);
 
   DenseMap<Function *, Function *> MergeResult =
-      MergeFunctionsPass::runOnFunctions(FunctionsSet.getArrayRef());
+      MergeFunctionsPass::runOnFunctions(FunctionsSet.getArrayRef(), MAM);
 
   // Expects empty map
   EXPECT_EQ(MergeResult.size(), 0u);

>From bc7be3709c2041674caef5f0437ae21dc9ecab78 Mon Sep 17 00:00:00 2001
From: Alok Kumar Sharma <AlokKumar.Sharma at amd.com>
Date: Thu, 16 Jul 2026 23:33:12 +0530
Subject: [PATCH 2/3] [MergeFunctions] Fix ASan use-after-free in
 MergeFunctionsTest

MergeFunctionsTest declared ModuleAnalysisManager before
FunctionAnalysisManager. MAM caches a proxy result that calls
FAM.clear() on destruction, so MAM must be destroyed before FAM.
But C++ destroys members in reverse declaration order, so FAM was
freed first, causing an ASan use-after-free when MAM was torn down.
This caused the revert of #208009.
Reorder the members so FAM is declared before MAM, matching other
LLVM test fixtures that pair these two managers.
---
 llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
index 879b48e6457a3..77e7f9459097b 100644
--- a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
+++ b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
@@ -29,8 +29,8 @@ namespace {
 class MergeFunctionsTest : public testing::Test {
 protected:
   LLVMContext Ctx;
-  ModuleAnalysisManager MAM;
   FunctionAnalysisManager FAM;
+  ModuleAnalysisManager MAM;
 
   MergeFunctionsTest() {
     FAM.registerPass([&] { return TargetLibraryAnalysis(); });

>From 1c26df1de30aa521e9774678bd03f9ad4004a0b1 Mon Sep 17 00:00:00 2001
From: Alok Kumar Sharma <AlokKumar.Sharma at amd.com>
Date: Fri, 31 Jul 2026 18:18:05 +0530
Subject: [PATCH 3/3] [MergeFunctions] Fix missing CycleAnalysis registration
 in test fixture

MergeFunctionsTest never registered CycleAnalysis on its FAM, which
used to be harmless since nothing in the test actually exercised
BranchProbabilityAnalysis. That changed once mergeInstrProfMetadataInto
started pulling in BlockFrequencyAnalysis, which needs it under the
hood - so the test started hitting an assertion.

Also, BranchProbabilityAnalysis itself switched from computing its own
internal cycle info to using the shared CycleAnalysis a while back
(#210301), which is why this was never a problem before.
---
 llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
index 77e7f9459097b..e812e988ecd86 100644
--- a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
+++ b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
@@ -11,6 +11,7 @@
 #include "llvm/ADT/SetVector.h"
 #include "llvm/Analysis/BlockFrequencyInfo.h"
 #include "llvm/Analysis/BranchProbabilityInfo.h"
+#include "llvm/Analysis/CycleAnalysis.h"
 #include "llvm/Analysis/LoopInfo.h"
 #include "llvm/Analysis/PostDominators.h"
 #include "llvm/Analysis/TargetLibraryInfo.h"
@@ -37,6 +38,7 @@ class MergeFunctionsTest : public testing::Test {
     FAM.registerPass([&] { return DominatorTreeAnalysis(); });
     FAM.registerPass([&] { return PostDominatorTreeAnalysis(); });
     FAM.registerPass([&] { return LoopAnalysis(); });
+    FAM.registerPass([&] { return CycleAnalysis(); });
     FAM.registerPass([&] { return BranchProbabilityAnalysis(); });
     FAM.registerPass([&] { return BlockFrequencyAnalysis(); });
     FAM.registerPass([&] { return PassInstrumentationAnalysis(); });



More information about the llvm-commits mailing list