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

Alok Kumar Sharma via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 13 06:26:46 PDT 2026


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

>From 1b064ff0394b536662b6ea107f285677ef945d52 Mon Sep 17 00:00:00 2001
From: Alok Kumar Sharma <AlokKumar.Sharma at amd.com>
Date: Tue, 7 Jul 2026 18:39:52 +0530
Subject: [PATCH 1/4] [MergeFunctions] Preserve instruction-level profile
 metadata during merging

Merge branch weights and value profile metadata when equivalent
functions are folded. Instruction-level branch weights and value
profiles are scaled to absolute counts using BlockFrequencyInfo
before being aggregated.
---
 .../llvm/Transforms/IPO/MergeFunctions.h      |   6 +-
 llvm/lib/Transforms/IPO/MergeFunctions.cpp    | 288 ++++++++++-
 .../merge-functions-prof-metadata.ll          | 460 ++++++++++++++++++
 3 files changed, 736 insertions(+), 18 deletions(-)
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-prof-metadata.ll

diff --git a/llvm/include/llvm/Transforms/IPO/MergeFunctions.h b/llvm/include/llvm/Transforms/IPO/MergeFunctions.h
index 75399d3297a05..218bd0702e1d2 100644
--- a/llvm/include/llvm/Transforms/IPO/MergeFunctions.h
+++ b/llvm/include/llvm/Transforms/IPO/MergeFunctions.h
@@ -28,9 +28,11 @@ 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,
+                                   FunctionAnalysisManager *FAM = nullptr);
   LLVM_ABI static DenseMap<Function *, Function *>
-  runOnFunctions(ArrayRef<Function *> F);
+  runOnFunctions(ArrayRef<Function *> F,
+                 FunctionAnalysisManager *FAM = nullptr);
 };
 
 } // end namespace llvm
diff --git a/llvm/lib/Transforms/IPO/MergeFunctions.cpp b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
index c4e56855ea2fb..c78bb2b0f357f 100644
--- a/llvm/lib/Transforms/IPO/MergeFunctions.cpp
+++ b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
@@ -89,14 +89,21 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Transforms/IPO/MergeFunctions.h"
+#include "llvm/ADT/APInt.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.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/Analysis/LoopInfo.h"
 #include "llvm/IR/Argument.h"
 #include "llvm/IR/BasicBlock.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/DebugLoc.h"
 #include "llvm/IR/DerivedTypes.h"
+#include "llvm/IR/Dominators.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/GlobalValue.h"
 #include "llvm/IR/IRBuilder.h"
@@ -104,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"
@@ -121,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>
@@ -198,10 +213,13 @@ class MergeFunctions {
   }
 
   template <typename FuncContainer> bool run(FuncContainer &Functions);
-  DenseMap<Function *, Function *> runOnFunctions(ArrayRef<Function *> F);
+  DenseMap<Function *, Function *>
+  runOnFunctions(ArrayRef<Function *> F, FunctionAnalysisManager *AM = nullptr);
 
   SmallPtrSet<GlobalValue *, 4> &getUsed();
 
+  void setFunctionAnalysisManager(FunctionAnalysisManager *AM) { FAM = AM; }
+
 private:
   // The function comparison operator is provided here so that FunctionNodes do
   // not need to become larger with another pointer.
@@ -257,6 +275,10 @@ class MergeFunctions {
   /// again.
   void mergeTwoFunctions(Function *F, Function *G);
 
+  const BlockFrequencyInfo &getBFI(Function &F);
+
+  void mergeProfMetadataInto(Function *F, Function *G);
+
   /// Fill PDIUnrelatedWL with instructions from the entry block that are
   /// unrelated to parameter related debug info.
   /// \param PDVRUnrelatedWL The equivalent non-intrinsic debug records.
@@ -287,7 +309,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);
@@ -305,20 +329,36 @@ class MergeFunctions {
 
   /// Deleted-New functions mapping
   DenseMap<Function *, Function *> DelToNewMap;
+
+  FunctionAnalysisManager *FAM = nullptr;
+
+  struct CachedProfileAnalyses {
+    DominatorTree DT;
+    LoopInfo LI;
+    BranchProbabilityInfo BPI;
+    BlockFrequencyInfo BFI;
+
+    CachedProfileAnalyses(Function &F)
+        : DT(F), LI(DT), BPI(F, LI, nullptr, &DT), BFI(F, BPI, LI) {}
+  };
+  DenseMap<const Function *, std::unique_ptr<CachedProfileAnalyses>> CachedBFI;
 };
 } // end anonymous namespace
 
 PreservedAnalyses MergeFunctionsPass::run(Module &M,
                                           ModuleAnalysisManager &AM) {
-  if (!MergeFunctionsPass::runOnModule(M))
+  auto &FAM = AM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager();
+  if (!MergeFunctionsPass::runOnModule(M, &FAM))
     return PreservedAnalyses::all();
   return PreservedAnalyses::none();
 }
 
 SmallPtrSet<GlobalValue *, 4> &MergeFunctions::getUsed() { return Used; }
 
-bool MergeFunctionsPass::runOnModule(Module &M) {
+bool MergeFunctionsPass::runOnModule(Module &M, FunctionAnalysisManager *FAM) {
   MergeFunctions MF;
+  if (FAM)
+    MF.setFunctionAnalysisManager(FAM);
   SmallVector<GlobalValue *, 4> UsedV;
   collectUsedGlobalVariables(M, UsedV, /*CompilerUsed=*/false);
   collectUsedGlobalVariables(M, UsedV, /*CompilerUsed=*/true);
@@ -327,9 +367,10 @@ bool MergeFunctionsPass::runOnModule(Module &M) {
 }
 
 DenseMap<Function *, Function *>
-MergeFunctionsPass::runOnFunctions(ArrayRef<Function *> F) {
+MergeFunctionsPass::runOnFunctions(ArrayRef<Function *> F,
+                                   FunctionAnalysisManager *FAM) {
   MergeFunctions MF;
-  return MF.runOnFunctions(F);
+  return MF.runOnFunctions(F, FAM);
 }
 
 #ifndef NDEBUG
@@ -493,7 +534,10 @@ template <typename FuncContainer> bool MergeFunctions::run(FuncContainer &M) {
 }
 
 DenseMap<Function *, Function *>
-MergeFunctions::runOnFunctions(ArrayRef<Function *> F) {
+MergeFunctions::runOnFunctions(ArrayRef<Function *> F,
+                               FunctionAnalysisManager *AM) {
+  if (AM)
+    setFunctionAnalysisManager(AM);
   [[maybe_unused]] bool MergeResult = this->run(F);
   assert(MergeResult == !DelToNewMap.empty());
   return this->DelToNewMap;
@@ -858,21 +902,36 @@ void MergeFunctions::writeAlias(Function *F, Function *G) {
 
 // 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)
+    mergeProfMetadataInto(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.
@@ -880,6 +939,199 @@ static bool isODR(const Function *F) {
   return F->hasWeakODRLinkage() || F->hasLinkOnceODRLinkage();
 }
 
+static uint64_t getBlockCountForMerging(const BlockFrequencyInfo *BFI,
+                                        const BasicBlock *BB,
+                                        std::optional<uint64_t> EC) {
+  if (BFI) {
+    if (auto Count = BFI->getBlockProfileCount(BB, /*AllowSynthetic=*/true))
+      return *Count;
+    // BFI present but no count for this block, fall through to EC.
+  }
+  if (EC)
+    return *EC;
+  return 1;
+}
+
+// The branch/value profile weights are relative within a function. Before
+// merge we need to normalize weights 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);
+  return Num.getLimitedValue();
+}
+
+// Combine the scaled branch_weights of corresponding instructions of F and G.
+static void mergeBranchWeightsOnInstructions(Instruction *FI,
+                                             const Instruction *GI,
+                                             const BlockFrequencyInfo *BfiF,
+                                             const BlockFrequencyInfo *BfiG,
+                                             std::optional<uint64_t> FEC,
+                                             std::optional<uint64_t> GEC) {
+  SmallVector<uint32_t, 8> FWeights, GWeights;
+  bool HasF = extractBranchWeights(*FI, FWeights);
+  bool HasG = extractBranchWeights(*GI, GWeights);
+  if (!HasF && !HasG)
+    return;
+
+  uint64_t BlockCountF = getBlockCountForMerging(BfiF, FI->getParent(), FEC);
+  uint64_t BlockCountG = getBlockCountForMerging(BfiG, GI->getParent(), GEC);
+
+  uint64_t TotalF = 0, TotalG = 0;
+  if (HasF)
+    extractProfTotalWeight(*FI, TotalF);
+  if (HasG)
+    extractProfTotalWeight(*GI, TotalG);
+
+  size_t NumWeights = std::max(HasF ? FWeights.size() : size_t{0},
+                               HasG ? GWeights.size() : size_t{0});
+  SmallVector<uint64_t, 8> MergedWeights;
+  MergedWeights.reserve(NumWeights);
+  for (size_t I = 0; I < NumWeights; ++I) {
+    uint64_t FW = (HasF && I < FWeights.size()) ? FWeights[I] : 0;
+    uint64_t GW = (HasG && I < GWeights.size()) ? GWeights[I] : 0;
+    uint64_t AbsF = HasF ? scaleToBlockCount(FW, TotalF, BlockCountF) : 0;
+    uint64_t AbsG = HasG ? scaleToBlockCount(GW, TotalG, BlockCountG) : 0;
+    MergedWeights.push_back(SaturatingAdd(AbsF, AbsG));
+  }
+
+  bool IsExpected = hasBranchWeightOrigin(*FI) || hasBranchWeightOrigin(*GI);
+  setFittedBranchWeights(*FI, MergedWeights, IsExpected);
+}
+
+// Accumulate scaled value profile counts of Instruction I into Merged.
+static void addScaledValueProfile(const Instruction &I, InstrProfValueKind Kind,
+                                  uint64_t BlockCount,
+                                  DenseMap<uint64_t, uint64_t> &Merged) {
+  uint64_t Total = 0;
+  SmallVector<InstrProfValueData, 4> VDs =
+      getValueProfDataFromInst(I, Kind, /*MaxNumValueData=*/UINT32_MAX, Total);
+  if (VDs.empty() || Total == 0)
+    return;
+  for (const InstrProfValueData &VD : VDs) {
+    uint64_t Abs = scaleToBlockCount(VD.Count, Total, BlockCount);
+    Merged[VD.Value] = SaturatingAdd(Merged[VD.Value], Abs);
+  }
+}
+
+// Merge (union) scaled value profiles of F and G.
+static void mergeValueProfileOnInstructions(Instruction *FI,
+                                            const Instruction *GI,
+                                            const BlockFrequencyInfo *BfiF,
+                                            const BlockFrequencyInfo *BfiG,
+                                            std::optional<uint64_t> FEC,
+                                            std::optional<uint64_t> GEC) {
+  MDNode *ProfF = FI->getMetadata(LLVMContext::MD_prof);
+  MDNode *ProfG = GI->getMetadata(LLVMContext::MD_prof);
+  bool HasF = ProfF && isValueProfileMD(ProfF);
+  bool HasG = ProfG && isValueProfileMD(ProfG);
+  if (!HasF && !HasG)
+    return;
+
+  auto *KindF =
+      HasF ? mdconst::dyn_extract<ConstantInt>(ProfF->getOperand(1)) : nullptr;
+  auto *KindG =
+      HasG ? mdconst::dyn_extract<ConstantInt>(ProfG->getOperand(1)) : nullptr;
+  if (HasF && HasG && KindF && KindG &&
+      KindF->getZExtValue() != KindG->getZExtValue()) {
+    FI->setMetadata(LLVMContext::MD_prof, nullptr);
+    return;
+  }
+
+  const ConstantInt *KindCI = KindF ? KindF : KindG;
+  if (!KindCI) {
+    FI->setMetadata(LLVMContext::MD_prof, nullptr);
+    return;
+  }
+
+  InstrProfValueKind Kind =
+      static_cast<InstrProfValueKind>(KindCI->getZExtValue());
+
+  DenseMap<uint64_t, uint64_t> Merged;
+  uint64_t BCF = getBlockCountForMerging(BfiF, FI->getParent(), FEC);
+  uint64_t BCG = getBlockCountForMerging(BfiG, GI->getParent(), GEC);
+  if (HasF)
+    addScaledValueProfile(*FI, Kind, BCF, Merged);
+  if (HasG)
+    addScaledValueProfile(*GI, Kind, BCG, 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(*FI->getFunction()->getParent(), *FI, VDs, Sum, Kind,
+                    VDs.size());
+}
+
+const BlockFrequencyInfo &MergeFunctions::getBFI(Function &F) {
+  if (FAM)
+    return FAM->getResult<BlockFrequencyAnalysis>(F);
+  auto &Entry = CachedBFI[&F];
+  if (!Entry)
+    Entry = std::make_unique<CachedProfileAnalyses>(F);
+  return Entry->BFI;
+}
+
+void MergeFunctions::mergeProfMetadataInto(Function *F, Function *G) {
+  std::optional<uint64_t> FEC = F->getEntryCount();
+  std::optional<uint64_t> GEC = G->getEntryCount();
+  const BlockFrequencyInfo *BfiF = &getBFI(*F);
+  const BlockFrequencyInfo *BfiG = &getBFI(*G);
+
+  SmallVector<std::pair<BasicBlock *, const BasicBlock *>, 8> BBWorklist;
+  SmallPtrSet<const BasicBlock *, 32> VisitedBBs;
+
+  BBWorklist.emplace_back(&F->getEntryBlock(), &G->getEntryBlock());
+  VisitedBBs.insert(BBWorklist.back().first);
+
+  while (!BBWorklist.empty()) {
+    auto [BBF, BBG] = BBWorklist.pop_back_val();
+
+    for (auto [FI, GI] : llvm::zip(*BBF, *BBG)) {
+      MDNode *ProfF = FI.getMetadata(LLVMContext::MD_prof);
+      MDNode *ProfG = GI.getMetadata(LLVMContext::MD_prof);
+      if ((ProfF && isValueProfileMD(ProfF)) ||
+          (ProfG && isValueProfileMD(ProfG)))
+        mergeValueProfileOnInstructions(&FI, &GI, BfiF, BfiG, FEC, GEC);
+      if (isa<SelectInst>(FI))
+        mergeBranchWeightsOnInstructions(&FI, &GI, BfiF, BfiG, FEC, GEC);
+    }
+
+    Instruction *TermF = BBF->getTerminator();
+    const Instruction *TermG = BBG->getTerminator();
+    mergeBranchWeightsOnInstructions(TermF, TermG, BfiF, BfiG, FEC, GEC);
+    for (unsigned I = 0, E = TermF->getNumSuccessors(); I != E; ++I) {
+      if (!VisitedBBs.insert(TermF->getSuccessor(I)).second)
+        continue;
+      BBWorklist.emplace_back(TermF->getSuccessor(I), TermG->getSuccessor(I));
+    }
+  }
+
+  if (FAM) {
+    PreservedAnalyses PA = PreservedAnalyses::all();
+    PA.abandon<BranchProbabilityAnalysis>();
+    PA.abandon<BlockFrequencyAnalysis>();
+    FAM->invalidate(*F, PA);
+  } else {
+    CachedBFI.erase(F);
+    CachedBFI.erase(G);
+  }
+}
+
 static void mergeEntryCountsInto(Function *F, std::optional<uint64_t> FC,
                                  std::optional<uint64_t> GC) {
   if (!FC && !GC)
@@ -935,10 +1187,13 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
     const MaybeAlign NewFAlign = NewF->getAlign();
     const MaybeAlign GAlign = G->getAlign();
 
-    writeThunkOrAliasIfNeeded(F, G);
+    // Merge !prof, while G still has its body.
+    writeThunkOrAliasIfNeeded(F, G, /*MergeProfile*/ true);
     if (FEC)
       NewF->setEntryCount(*FEC);
-    writeThunkOrAliasIfNeeded(F, NewF);
+    // 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()));
@@ -975,13 +1230,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) {
+      mergeProfMetadataInto(F, G);
       mergeEntryCountsInto(F, FEC, GEC);
       G->eraseFromParent();
       ++NumFunctionsMerged;
       return;
     }
 
-    if (writeThunkOrAliasIfNeeded(F, G)) {
+    if (writeThunkOrAliasIfNeeded(F, G, /*MergeProfile*/ true)) {
       mergeEntryCountsInto(F, FEC, GEC);
       ++NumFunctionsMerged;
     }
diff --git a/llvm/test/Transforms/MergeFunc/merge-functions-prof-metadata.ll b/llvm/test/Transforms/MergeFunc/merge-functions-prof-metadata.ll
new file mode 100644
index 0000000000000..278ded8693662
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-prof-metadata.ll
@@ -0,0 +1,460 @@
+; 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
+
+; Check mergefunc combines branch/value profile data.
+
+define internal i32 @bw_asym_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 @bw_asym_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
+}
+
+define internal i32 @bw_sym_a(i32 %x) !prof !5 {
+entry:
+  %cmp = icmp ne i32 %x, 0
+  br i1 %cmp, label %then, label %else, !prof !6
+
+then:
+  ret i32 4
+
+else:
+  ret i32 5
+}
+
+define internal i32 @bw_sym_b(i32 %x) !prof !7 {
+entry:
+  %cmp = icmp ne i32 %x, 0
+  br i1 %cmp, label %then, label %else, !prof !8
+
+then:
+  ret i32 4
+
+else:
+  ret i32 5
+}
+
+define internal i32 @bw_g_only_a(i32 %x) !prof !24 {
+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 @bw_g_only_b(i32 %x) !prof !25 {
+entry:
+  %cmp = icmp sgt i32 %x, 0
+  br i1 %cmp, label %then, label %else, !prof !26
+
+then:
+  ret i32 7
+
+else:
+  ret i32 8
+}
+
+define internal i32 @select_bw_a(i32 %x) !prof !9 {
+entry:
+  %cmp = icmp slt i32 %x, 0
+  %sel = select i1 %cmp, i32 1, i32 0, !prof !10
+  ret i32 %sel
+}
+
+define internal i32 @select_bw_b(i32 %x) !prof !11 {
+entry:
+  %cmp = icmp slt i32 %x, 0
+  %sel = select i1 %cmp, i32 1, i32 0, !prof !12
+  ret i32 %sel
+}
+
+define internal i32 @vp_sym_a(ptr %fn) !prof !13 {
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val(), !prof !14
+  ret i32 %r
+}
+
+define internal i32 @vp_sym_b(ptr %fn) !prof !15 {
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val(), !prof !16
+  ret i32 %r
+}
+
+; add i32 1 keeps @vp_asym_* distinct from @vp_sym_*.
+define internal i32 @vp_asym_a(ptr %fn) !prof !17 {
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val(), !prof !18
+  %adj = add i32 %r, 1
+  ret i32 %adj
+}
+
+define internal i32 @vp_asym_b(ptr %fn) !prof !19 {
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val()
+  %adj = add i32 %r, 1
+  ret i32 %adj
+}
+
+define internal i32 @vp_g_only_a(ptr %fn) !prof !27 {
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val()
+  %adj = add i32 %r, 3
+  ret i32 %adj
+}
+
+define internal i32 @vp_g_only_b(ptr %fn) !prof !28 {
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val(), !prof !29
+  %adj = add i32 %r, 3
+  ret i32 %adj
+}
+
+define internal i32 @vp_kind_mismatch_a(ptr %fn) !prof !20 {
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val(), !prof !21
+  %adj = add i32 %r, 2
+  ret i32 %adj
+}
+
+define internal i32 @vp_kind_mismatch_b(ptr %fn) !prof !22 {
+entry:
+  %fn_val = load ptr, ptr %fn
+  %r = call i32 %fn_val(), !prof !23
+  %adj = add i32 %r, 2
+  ret i32 %adj
+}
+
+define dso_local i32 @thunk_path_a(i32 %x) unnamed_addr !prof !30 {
+entry:
+  %cmp = icmp ult i32 %x, 3
+  br i1 %cmp, label %then, label %else, !prof !31
+
+then:
+  ret i32 9
+
+else:
+  ret i32 10
+}
+
+define dso_local i32 @thunk_path_b(i32 %x) unnamed_addr !prof !32 {
+entry:
+  %cmp = icmp ult i32 %x, 3
+  br i1 %cmp, label %then, label %else, !prof !33
+
+then:
+  ret i32 9
+
+else:
+  ret i32 10
+}
+
+define weak_odr i32 @odr_path_a(i32 %x) !prof !34 {
+entry:
+  %cmp = icmp ugt i32 %x, 4
+  br i1 %cmp, label %then, label %else, !prof !35
+
+then:
+  ret i32 11
+
+else:
+  ret i32 12
+}
+
+define weak_odr i32 @odr_path_b(i32 %x) !prof !36 {
+entry:
+  %cmp = icmp ugt i32 %x, 4
+  br i1 %cmp, label %then, label %else, !prof !37
+
+then:
+  ret i32 11
+
+else:
+  ret i32 12
+}
+
+define i32 @use(i32 %x, ptr %fn) {
+entry:
+  %a = call i32 @bw_asym_a(i32 %x)
+  %b = call i32 @bw_asym_b(i32 %x)
+  %c = call i32 @bw_sym_a(i32 %x)
+  %d = call i32 @bw_sym_b(i32 %x)
+  %e = call i32 @bw_g_only_a(i32 %x)
+  %f = call i32 @bw_g_only_b(i32 %x)
+  %g = call i32 @select_bw_a(i32 %x)
+  %h = call i32 @select_bw_b(i32 %x)
+  %i = call i32 @vp_sym_a(ptr %fn)
+  %j = call i32 @vp_sym_b(ptr %fn)
+  %k = call i32 @vp_asym_a(ptr %fn)
+  %l = call i32 @vp_asym_b(ptr %fn)
+  %m = call i32 @vp_g_only_a(ptr %fn)
+  %n = call i32 @vp_g_only_b(ptr %fn)
+  %o = call i32 @vp_kind_mismatch_a(ptr %fn)
+  %p = call i32 @vp_kind_mismatch_b(ptr %fn)
+  %q = call i32 @thunk_path_a(i32 %x)
+  %r = call i32 @thunk_path_b(i32 %x)
+  %s = call i32 @odr_path_a(i32 %x)
+  %t = call i32 @odr_path_b(i32 %x)
+  %sum0 = add i32 %a, %b
+  %sum1 = add i32 %sum0, %c
+  %sum2 = add i32 %sum1, %d
+  %sum3 = add i32 %sum2, %e
+  %sum4 = add i32 %sum3, %f
+  %sum5 = add i32 %sum4, %g
+  %sum6 = add i32 %sum5, %h
+  %sum7 = add i32 %sum6, %i
+  %sum8 = add i32 %sum7, %j
+  %sum9 = add i32 %sum8, %k
+  %sum10 = add i32 %sum9, %l
+  %sum11 = add i32 %sum10, %m
+  %sum12 = add i32 %sum11, %n
+  %sum13 = add i32 %sum12, %o
+  %sum14 = add i32 %sum13, %p
+  %sum15 = add i32 %sum14, %q
+  %sum16 = add i32 %sum15, %r
+  %sum17 = add i32 %sum16, %s
+  %sum18 = add i32 %sum17, %t
+  ret i32 %sum18
+}
+
+!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 = !{!"branch_weights", i32 60, i32 40}
+!7 = !{!"function_entry_count", i64 60}
+!8 = !{!"branch_weights", i32 20, i32 80}
+!9 = !{!"function_entry_count", i64 50}
+!10 = !{!"branch_weights", i32 10, i32 90}
+!11 = !{!"function_entry_count", i64 150}
+!12 = !{!"branch_weights", i32 30, i32 70}
+!13 = !{!"function_entry_count", i64 100}
+!14 = !{!"VP", i32 0, i64 100, i64 111, i64 60, i64 222, i64 40}
+!15 = !{!"function_entry_count", i64 200}
+!16 = !{!"VP", i32 0, i64 200, i64 111, i64 120, i64 333, i64 80}
+!17 = !{!"function_entry_count", i64 80}
+!18 = !{!"VP", i32 0, i64 80, i64 444, i64 50, i64 555, i64 30}
+!19 = !{!"function_entry_count", i64 120}
+!20 = !{!"function_entry_count", i64 30}
+!21 = !{!"VP", i32 0, i64 30, i64 666, i64 20}
+!22 = !{!"function_entry_count", i64 70}
+!23 = !{!"VP", i32 1, i64 70, i64 777, i64 50}
+!24 = !{!"function_entry_count", i64 40}
+!25 = !{!"function_entry_count", i64 60}
+!26 = !{!"branch_weights", i32 20, i32 50}
+!27 = !{!"function_entry_count", i64 30}
+!28 = !{!"function_entry_count", i64 70}
+!29 = !{!"VP", i32 0, i64 70, i64 888, i64 50}
+!30 = !{!"function_entry_count", i64 10}
+!31 = !{!"branch_weights", i32 1, i32 9}
+!32 = !{!"function_entry_count", i64 20}
+!33 = !{!"branch_weights", i32 3, i32 17}
+!34 = !{!"function_entry_count", i64 10}
+!35 = !{!"branch_weights", i32 1, i32 9}
+!36 = !{!"function_entry_count", i64 20}
+!37 = !{!"branch_weights", i32 3, i32 17}
+
+; CHECK-LABEL: define internal i32 @bw_asym_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 @bw_sym_a(
+; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF2:![0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[X]], 0
+; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF3:![0-9]+]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret i32 4
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    ret i32 5
+;
+;
+; CHECK-LABEL: define internal i32 @bw_g_only_a(
+; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF2]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[X]], 0
+; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF4:![0-9]+]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret i32 7
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    ret i32 8
+;
+;
+; CHECK-LABEL: define internal i32 @select_bw_a(
+; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF5:![0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[X]], 0
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i32 1, i32 0, !prof [[PROF6:![0-9]+]]
+; CHECK-NEXT:    ret i32 [[SEL]]
+;
+;
+; CHECK-LABEL: define internal i32 @vp_sym_a(
+; CHECK-SAME: ptr [[FN:%.*]]) !prof [[PROF0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[FN_VAL:%.*]] = load ptr, ptr [[FN]], align 8
+; CHECK-NEXT:    [[R:%.*]] = call i32 [[FN_VAL]](), !prof [[PROF7:![0-9]+]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+;
+; CHECK-LABEL: define internal i32 @vp_asym_a(
+; CHECK-SAME: ptr [[FN:%.*]]) !prof [[PROF5]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[FN_VAL:%.*]] = load ptr, ptr [[FN]], align 8
+; CHECK-NEXT:    [[R:%.*]] = call i32 [[FN_VAL]](), !prof [[PROF8:![0-9]+]]
+; CHECK-NEXT:    [[ADJ:%.*]] = add i32 [[R]], 1
+; CHECK-NEXT:    ret i32 [[ADJ]]
+;
+;
+; CHECK-LABEL: define internal i32 @vp_g_only_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]](), !prof [[PROF9:![0-9]+]]
+; CHECK-NEXT:    [[ADJ:%.*]] = add i32 [[R]], 3
+; CHECK-NEXT:    ret i32 [[ADJ]]
+;
+;
+; CHECK-LABEL: define internal i32 @vp_kind_mismatch_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]]
+;
+;
+; CHECK-LABEL: define dso_local i32 @thunk_path_a(
+; CHECK-SAME: i32 [[X:%.*]]) unnamed_addr !prof [[PROF10:![0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X]], 3
+; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF11:![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 [[PROF10]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i32 [[X]], 4
+; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF11]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret i32 11
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    ret i32 12
+;
+;
+; CHECK-LABEL: define i32 @use(
+; CHECK-SAME: i32 [[X:%.*]], ptr [[FN:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[A:%.*]] = call i32 @bw_asym_a(i32 [[X]])
+; CHECK-NEXT:    [[B:%.*]] = call i32 @bw_asym_a(i32 [[X]])
+; CHECK-NEXT:    [[C:%.*]] = call i32 @bw_sym_a(i32 [[X]])
+; CHECK-NEXT:    [[D:%.*]] = call i32 @bw_sym_a(i32 [[X]])
+; CHECK-NEXT:    [[E:%.*]] = call i32 @bw_g_only_a(i32 [[X]])
+; CHECK-NEXT:    [[F:%.*]] = call i32 @bw_g_only_a(i32 [[X]])
+; CHECK-NEXT:    [[G:%.*]] = call i32 @select_bw_a(i32 [[X]])
+; CHECK-NEXT:    [[H:%.*]] = call i32 @select_bw_a(i32 [[X]])
+; CHECK-NEXT:    [[I:%.*]] = call i32 @vp_sym_a(ptr [[FN]])
+; CHECK-NEXT:    [[J:%.*]] = call i32 @vp_sym_a(ptr [[FN]])
+; CHECK-NEXT:    [[K:%.*]] = call i32 @vp_asym_a(ptr [[FN]])
+; CHECK-NEXT:    [[L:%.*]] = call i32 @vp_asym_a(ptr [[FN]])
+; CHECK-NEXT:    [[M:%.*]] = call i32 @vp_g_only_a(ptr [[FN]])
+; CHECK-NEXT:    [[N:%.*]] = call i32 @vp_g_only_a(ptr [[FN]])
+; CHECK-NEXT:    [[O:%.*]] = call i32 @vp_kind_mismatch_a(ptr [[FN]])
+; CHECK-NEXT:    [[P:%.*]] = call i32 @vp_kind_mismatch_a(ptr [[FN]])
+; CHECK-NEXT:    [[Q:%.*]] = call i32 @thunk_path_a(i32 [[X]])
+; CHECK-NEXT:    [[R:%.*]] = call i32 @thunk_path_a(i32 [[X]])
+; CHECK-NEXT:    [[S:%.*]] = call i32 @[[GLOB0:[0-9]+]](i32 [[X]])
+; CHECK-NEXT:    [[T:%.*]] = call i32 @[[GLOB0]](i32 [[X]])
+; CHECK-NEXT:    [[SUM0:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT:    [[SUM1:%.*]] = add i32 [[SUM0]], [[C]]
+; CHECK-NEXT:    [[SUM2:%.*]] = add i32 [[SUM1]], [[D]]
+; CHECK-NEXT:    [[SUM3:%.*]] = add i32 [[SUM2]], [[E]]
+; CHECK-NEXT:    [[SUM4:%.*]] = add i32 [[SUM3]], [[F]]
+; CHECK-NEXT:    [[SUM5:%.*]] = add i32 [[SUM4]], [[G]]
+; CHECK-NEXT:    [[SUM6:%.*]] = add i32 [[SUM5]], [[H]]
+; CHECK-NEXT:    [[SUM7:%.*]] = add i32 [[SUM6]], [[I]]
+; CHECK-NEXT:    [[SUM8:%.*]] = add i32 [[SUM7]], [[J]]
+; CHECK-NEXT:    [[SUM9:%.*]] = add i32 [[SUM8]], [[K]]
+; CHECK-NEXT:    [[SUM10:%.*]] = add i32 [[SUM9]], [[L]]
+; CHECK-NEXT:    [[SUM11:%.*]] = add i32 [[SUM10]], [[M]]
+; CHECK-NEXT:    [[SUM12:%.*]] = add i32 [[SUM11]], [[N]]
+; CHECK-NEXT:    [[SUM13:%.*]] = add i32 [[SUM12]], [[O]]
+; CHECK-NEXT:    [[SUM14:%.*]] = add i32 [[SUM13]], [[P]]
+; CHECK-NEXT:    [[SUM15:%.*]] = add i32 [[SUM14]], [[Q]]
+; CHECK-NEXT:    [[SUM16:%.*]] = add i32 [[SUM15]], [[R]]
+; CHECK-NEXT:    [[SUM17:%.*]] = add i32 [[SUM16]], [[S]]
+; CHECK-NEXT:    [[SUM18:%.*]] = add i32 [[SUM17]], [[T]]
+; CHECK-NEXT:    ret i32 [[SUM18]]
+;
+;
+; CHECK-LABEL: define dso_local i32 @thunk_path_b(
+; CHECK-SAME: i32 [[TMP0:%.*]]) unnamed_addr !prof [[PROF12:![0-9]+]] {
+; CHECK-NEXT:    [[TMP2:%.*]] = tail call i32 @thunk_path_a(i32 [[TMP0]])
+; CHECK-NEXT:    ret i32 [[TMP2]]
+;
+;
+; CHECK-LABEL: define weak_odr i32 @odr_path_b(
+; CHECK-SAME: i32 [[TMP0:%.*]]) !prof [[PROF12]] {
+; CHECK-NEXT:    [[TMP2:%.*]] = tail call i32 @[[GLOB0]](i32 [[TMP0]])
+; CHECK-NEXT:    ret i32 [[TMP2]]
+;
+;
+; CHECK-LABEL: define weak_odr i32 @odr_path_a(
+; CHECK-SAME: i32 [[TMP0:%.*]]) !prof [[PROF13:![0-9]+]] {
+; 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 36, i32 64}
+; CHECK: [[PROF4]] = !{!"branch_weights", i32 17, i32 43}
+; CHECK: [[PROF5]] = !{!"function_entry_count", i64 200}
+; CHECK: [[PROF6]] = !{!"branch_weights", i32 50, i32 150}
+; CHECK: [[PROF7]] = !{!"VP", i32 0, i64 300, i64 111, i64 180, i64 333, i64 80, i64 222, i64 40}
+; CHECK: [[PROF8]] = !{!"VP", i32 0, i64 80, i64 444, i64 50, i64 555, i64 30}
+; CHECK: [[PROF9]] = !{!"VP", i32 0, i64 50, i64 888, i64 50}
+; CHECK: [[PROF10]] = !{!"function_entry_count", i64 30}
+; CHECK: [[PROF11]] = !{!"branch_weights", i32 4, i32 26}
+; CHECK: [[PROF12]] = !{!"function_entry_count", i64 20}
+; CHECK: [[PROF13]] = !{!"function_entry_count", i64 10}
+;.

>From cd303e1b3353fd7a44354ee7efc94035fedc7b7d Mon Sep 17 00:00:00 2001
From: Alok Kumar Sharma <AlokKumar.Sharma at amd.com>
Date: Fri, 10 Jul 2026 12:32:02 +0530
Subject: [PATCH 2/4] Review comments.

---
 .../llvm/Transforms/IPO/MergeFunctions.h      |   2 +
 llvm/lib/Transforms/IPO/MergeFunctions.cpp    | 264 +++++-----
 .../merge-functions-branch-weights.ll         | 239 +++++++++
 .../merge-functions-prof-metadata.ll          | 460 ------------------
 .../merge-functions-select-weights.ll         |  50 ++
 .../merge-functions-value-profile.ll          | 143 ++++++
 6 files changed, 558 insertions(+), 600 deletions(-)
 create mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-branch-weights.ll
 delete mode 100644 llvm/test/Transforms/MergeFunc/merge-functions-prof-metadata.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 218bd0702e1d2..e5fbf0ee6c77a 100644
--- a/llvm/include/llvm/Transforms/IPO/MergeFunctions.h
+++ b/llvm/include/llvm/Transforms/IPO/MergeFunctions.h
@@ -28,6 +28,8 @@ class MergeFunctionsPass : public OptionalPassInfoMixin<MergeFunctionsPass> {
 public:
   LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
 
+  /// When null, instruction-level profile merging scales using function entry
+  /// counts only. The NewPM path always provides FAM.
   LLVM_ABI static bool runOnModule(Module &M,
                                    FunctionAnalysisManager *FAM = nullptr);
   LLVM_ABI static DenseMap<Function *, Function *>
diff --git a/llvm/lib/Transforms/IPO/MergeFunctions.cpp b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
index c78bb2b0f357f..a3dd33cefe6ec 100644
--- a/llvm/lib/Transforms/IPO/MergeFunctions.cpp
+++ b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
@@ -97,13 +97,11 @@
 #include "llvm/ADT/Statistic.h"
 #include "llvm/Analysis/BlockFrequencyInfo.h"
 #include "llvm/Analysis/BranchProbabilityInfo.h"
-#include "llvm/Analysis/LoopInfo.h"
 #include "llvm/IR/Argument.h"
 #include "llvm/IR/BasicBlock.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/DebugLoc.h"
 #include "llvm/IR/DerivedTypes.h"
-#include "llvm/IR/Dominators.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/GlobalValue.h"
 #include "llvm/IR/IRBuilder.h"
@@ -209,17 +207,14 @@ class FunctionNode {
 /// bitcast of the other.
 class MergeFunctions {
 public:
-  MergeFunctions() : FnTree(FunctionNodeCmp(&GlobalNumbers)) {
-  }
+  explicit MergeFunctions(FunctionAnalysisManager *FAM = nullptr)
+      : FnTree(FunctionNodeCmp(&GlobalNumbers)), FAM(FAM) {}
 
   template <typename FuncContainer> bool run(FuncContainer &Functions);
-  DenseMap<Function *, Function *>
-  runOnFunctions(ArrayRef<Function *> F, FunctionAnalysisManager *AM = nullptr);
+  DenseMap<Function *, Function *> runOnFunctions(ArrayRef<Function *> F);
 
   SmallPtrSet<GlobalValue *, 4> &getUsed();
 
-  void setFunctionAnalysisManager(FunctionAnalysisManager *AM) { FAM = AM; }
-
 private:
   // The function comparison operator is provided here so that FunctionNodes do
   // not need to become larger with another pointer.
@@ -275,9 +270,9 @@ class MergeFunctions {
   /// again.
   void mergeTwoFunctions(Function *F, Function *G);
 
-  const BlockFrequencyInfo &getBFI(Function &F);
+  const BlockFrequencyInfo* getBFI(Function &F);
 
-  void mergeProfMetadataInto(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.
@@ -331,17 +326,6 @@ class MergeFunctions {
   DenseMap<Function *, Function *> DelToNewMap;
 
   FunctionAnalysisManager *FAM = nullptr;
-
-  struct CachedProfileAnalyses {
-    DominatorTree DT;
-    LoopInfo LI;
-    BranchProbabilityInfo BPI;
-    BlockFrequencyInfo BFI;
-
-    CachedProfileAnalyses(Function &F)
-        : DT(F), LI(DT), BPI(F, LI, nullptr, &DT), BFI(F, BPI, LI) {}
-  };
-  DenseMap<const Function *, std::unique_ptr<CachedProfileAnalyses>> CachedBFI;
 };
 } // end anonymous namespace
 
@@ -356,9 +340,7 @@ PreservedAnalyses MergeFunctionsPass::run(Module &M,
 SmallPtrSet<GlobalValue *, 4> &MergeFunctions::getUsed() { return Used; }
 
 bool MergeFunctionsPass::runOnModule(Module &M, FunctionAnalysisManager *FAM) {
-  MergeFunctions MF;
-  if (FAM)
-    MF.setFunctionAnalysisManager(FAM);
+  MergeFunctions MF(FAM);
   SmallVector<GlobalValue *, 4> UsedV;
   collectUsedGlobalVariables(M, UsedV, /*CompilerUsed=*/false);
   collectUsedGlobalVariables(M, UsedV, /*CompilerUsed=*/true);
@@ -369,8 +351,8 @@ bool MergeFunctionsPass::runOnModule(Module &M, FunctionAnalysisManager *FAM) {
 DenseMap<Function *, Function *>
 MergeFunctionsPass::runOnFunctions(ArrayRef<Function *> F,
                                    FunctionAnalysisManager *FAM) {
-  MergeFunctions MF;
-  return MF.runOnFunctions(F, FAM);
+  MergeFunctions MF(FAM);
+  return MF.runOnFunctions(F);
 }
 
 #ifndef NDEBUG
@@ -534,10 +516,7 @@ template <typename FuncContainer> bool MergeFunctions::run(FuncContainer &M) {
 }
 
 DenseMap<Function *, Function *>
-MergeFunctions::runOnFunctions(ArrayRef<Function *> F,
-                               FunctionAnalysisManager *AM) {
-  if (AM)
-    setFunctionAnalysisManager(AM);
+MergeFunctions::runOnFunctions(ArrayRef<Function *> F) {
   [[maybe_unused]] bool MergeResult = this->run(F);
   assert(MergeResult == !DelToNewMap.empty());
   return this->DelToNewMap;
@@ -778,7 +757,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;
@@ -848,8 +827,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");
@@ -915,7 +894,7 @@ bool MergeFunctions::writeThunkOrAliasIfNeeded(Function *F, Function *G,
     return false;
 
   if (MergeProfile)
-    mergeProfMetadataInto(F, G);
+    mergeInstrProfMetadataInto(F, G);
 
   if (ShouldErase) {
     G->eraseFromParent();
@@ -963,45 +942,52 @@ static uint64_t scaleToBlockCount(uint64_t Weight, uint64_t TotalWeight,
   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 *FI,
-                                             const Instruction *GI,
-                                             const BlockFrequencyInfo *BfiF,
-                                             const BlockFrequencyInfo *BfiG,
-                                             std::optional<uint64_t> FEC,
-                                             std::optional<uint64_t> GEC) {
-  SmallVector<uint32_t, 8> FWeights, GWeights;
-  bool HasF = extractBranchWeights(*FI, FWeights);
-  bool HasG = extractBranchWeights(*GI, GWeights);
-  if (!HasF && !HasG)
+static void
+mergeBranchWeightsOnInstructions(Instruction *DstI, const Instruction *SrcI,
+                                 const BlockFrequencyInfo *DstBFI,
+                                 const BlockFrequencyInfo *SrcBFI,
+                                 std::optional<uint64_t> DstEntryCount,
+                                 std::optional<uint64_t> SrcEntryCount) {
+  SmallVector<uint32_t, 8> DstWeights, SrcWeights;
+  bool HasDst = extractBranchWeights(*DstI, DstWeights);
+  bool HasSrc = extractBranchWeights(*SrcI, SrcWeights);
+  if (!HasDst && !HasSrc)
     return;
 
-  uint64_t BlockCountF = getBlockCountForMerging(BfiF, FI->getParent(), FEC);
-  uint64_t BlockCountG = getBlockCountForMerging(BfiG, GI->getParent(), GEC);
+  uint64_t DstBlockCount =
+      getBlockCountForMerging(DstBFI, DstI->getParent(), DstEntryCount);
+  uint64_t SrcBlockCount =
+      getBlockCountForMerging(SrcBFI, SrcI->getParent(), SrcEntryCount);
 
-  uint64_t TotalF = 0, TotalG = 0;
-  if (HasF)
-    extractProfTotalWeight(*FI, TotalF);
-  if (HasG)
-    extractProfTotalWeight(*GI, TotalG);
+  uint64_t DstTotal = 0, SrcTotal = 0;
+  if (HasDst)
+    extractProfTotalWeight(*DstI, DstTotal);
+  if (HasSrc)
+    extractProfTotalWeight(*SrcI, SrcTotal);
 
-  size_t NumWeights = std::max(HasF ? FWeights.size() : size_t{0},
-                               HasG ? GWeights.size() : size_t{0});
+  size_t NumWeights = std::max(HasDst ? DstWeights.size() : size_t{0},
+                               HasSrc ? SrcWeights.size() : size_t{0});
   SmallVector<uint64_t, 8> MergedWeights;
   MergedWeights.reserve(NumWeights);
   for (size_t I = 0; I < NumWeights; ++I) {
-    uint64_t FW = (HasF && I < FWeights.size()) ? FWeights[I] : 0;
-    uint64_t GW = (HasG && I < GWeights.size()) ? GWeights[I] : 0;
-    uint64_t AbsF = HasF ? scaleToBlockCount(FW, TotalF, BlockCountF) : 0;
-    uint64_t AbsG = HasG ? scaleToBlockCount(GW, TotalG, BlockCountG) : 0;
-    MergedWeights.push_back(SaturatingAdd(AbsF, AbsG));
+    uint64_t DstW = (HasDst && I < DstWeights.size()) ? DstWeights[I] : 0;
+    uint64_t SrcW = (HasSrc && I < SrcWeights.size()) ? SrcWeights[I] : 0;
+    uint64_t DstAbs =
+        HasDst ? scaleToBlockCount(DstW, DstTotal, DstBlockCount) : 0;
+    uint64_t SrcAbs =
+        HasSrc ? scaleToBlockCount(SrcW, SrcTotal, SrcBlockCount) : 0;
+    MergedWeights.push_back(SaturatingAdd(DstAbs, SrcAbs));
   }
 
-  bool IsExpected = hasBranchWeightOrigin(*FI) || hasBranchWeightOrigin(*GI);
-  setFittedBranchWeights(*FI, MergedWeights, IsExpected);
+  bool IsExpected =
+      hasBranchWeightOrigin(*DstI) && hasBranchWeightOrigin(*SrcI);
+  setFittedBranchWeights(*DstI, MergedWeights, IsExpected);
 }
 
 // Accumulate scaled value profile counts of Instruction I into Merged.
@@ -1019,33 +1005,35 @@ static void addScaledValueProfile(const Instruction &I, InstrProfValueKind Kind,
   }
 }
 
-// Merge (union) scaled value profiles of F and G.
-static void mergeValueProfileOnInstructions(Instruction *FI,
-                                            const Instruction *GI,
-                                            const BlockFrequencyInfo *BfiF,
-                                            const BlockFrequencyInfo *BfiG,
-                                            std::optional<uint64_t> FEC,
-                                            std::optional<uint64_t> GEC) {
-  MDNode *ProfF = FI->getMetadata(LLVMContext::MD_prof);
-  MDNode *ProfG = GI->getMetadata(LLVMContext::MD_prof);
-  bool HasF = ProfF && isValueProfileMD(ProfF);
-  bool HasG = ProfG && isValueProfileMD(ProfG);
-  if (!HasF && !HasG)
+// Merge (union) scaled value profiles of Dst and Src.
+static void
+mergeValueProfileOnInstructions(Instruction *DstI, const Instruction *SrcI,
+                                const BlockFrequencyInfo *DstBFI,
+                                const BlockFrequencyInfo *SrcBFI,
+                                std::optional<uint64_t> DstEntryCount,
+                                std::optional<uint64_t> SrcEntryCount) {
+  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 *KindF =
-      HasF ? mdconst::dyn_extract<ConstantInt>(ProfF->getOperand(1)) : nullptr;
-  auto *KindG =
-      HasG ? mdconst::dyn_extract<ConstantInt>(ProfG->getOperand(1)) : nullptr;
-  if (HasF && HasG && KindF && KindG &&
-      KindF->getZExtValue() != KindG->getZExtValue()) {
-    FI->setMetadata(LLVMContext::MD_prof, nullptr);
+  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 = KindF ? KindF : KindG;
+  const ConstantInt *KindCI = DstKind ? DstKind : SrcKind;
   if (!KindCI) {
-    FI->setMetadata(LLVMContext::MD_prof, nullptr);
+    DstI->setMetadata(LLVMContext::MD_prof, nullptr);
     return;
   }
 
@@ -1053,12 +1041,14 @@ static void mergeValueProfileOnInstructions(Instruction *FI,
       static_cast<InstrProfValueKind>(KindCI->getZExtValue());
 
   DenseMap<uint64_t, uint64_t> Merged;
-  uint64_t BCF = getBlockCountForMerging(BfiF, FI->getParent(), FEC);
-  uint64_t BCG = getBlockCountForMerging(BfiG, GI->getParent(), GEC);
-  if (HasF)
-    addScaledValueProfile(*FI, Kind, BCF, Merged);
-  if (HasG)
-    addScaledValueProfile(*GI, Kind, BCG, Merged);
+  uint64_t DstBlockCount =
+      getBlockCountForMerging(DstBFI, DstI->getParent(), DstEntryCount);
+  uint64_t SrcBlockCount =
+      getBlockCountForMerging(SrcBFI, SrcI->getParent(), SrcEntryCount);
+  if (HasDst)
+    addScaledValueProfile(*DstI, Kind, DstBlockCount, Merged);
+  if (HasSrc)
+    addScaledValueProfile(*SrcI, Kind, SrcBlockCount, Merged);
 
   if (Merged.empty())
     return;
@@ -1073,62 +1063,55 @@ static void mergeValueProfileOnInstructions(Instruction *FI,
   llvm::sort(VDs, [](const InstrProfValueData &A, const InstrProfValueData &B) {
     return A.Count > B.Count;
   });
-  annotateValueSite(*FI->getFunction()->getParent(), *FI, VDs, Sum, Kind,
+  annotateValueSite(*DstI->getFunction()->getParent(), *DstI, VDs, Sum, Kind,
                     VDs.size());
 }
 
-const BlockFrequencyInfo &MergeFunctions::getBFI(Function &F) {
-  if (FAM)
-    return FAM->getResult<BlockFrequencyAnalysis>(F);
-  auto &Entry = CachedBFI[&F];
-  if (!Entry)
-    Entry = std::make_unique<CachedProfileAnalyses>(F);
-  return Entry->BFI;
+const BlockFrequencyInfo *MergeFunctions::getBFI(Function &F) {
+  if (!FAM)
+    return nullptr;
+
+  return &FAM->getResult<BlockFrequencyAnalysis>(F);
 }
 
-void MergeFunctions::mergeProfMetadataInto(Function *F, Function *G) {
-  std::optional<uint64_t> FEC = F->getEntryCount();
-  std::optional<uint64_t> GEC = G->getEntryCount();
-  const BlockFrequencyInfo *BfiF = &getBFI(*F);
-  const BlockFrequencyInfo *BfiG = &getBFI(*G);
-
-  SmallVector<std::pair<BasicBlock *, const BasicBlock *>, 8> BBWorklist;
-  SmallPtrSet<const BasicBlock *, 32> VisitedBBs;
-
-  BBWorklist.emplace_back(&F->getEntryBlock(), &G->getEntryBlock());
-  VisitedBBs.insert(BBWorklist.back().first);
-
-  while (!BBWorklist.empty()) {
-    auto [BBF, BBG] = BBWorklist.pop_back_val();
-
-    for (auto [FI, GI] : llvm::zip(*BBF, *BBG)) {
-      MDNode *ProfF = FI.getMetadata(LLVMContext::MD_prof);
-      MDNode *ProfG = GI.getMetadata(LLVMContext::MD_prof);
-      if ((ProfF && isValueProfileMD(ProfF)) ||
-          (ProfG && isValueProfileMD(ProfG)))
-        mergeValueProfileOnInstructions(&FI, &GI, BfiF, BfiG, FEC, GEC);
-      if (isa<SelectInst>(FI))
-        mergeBranchWeightsOnInstructions(&FI, &GI, BfiF, BfiG, FEC, GEC);
-    }
-
-    Instruction *TermF = BBF->getTerminator();
-    const Instruction *TermG = BBG->getTerminator();
-    mergeBranchWeightsOnInstructions(TermF, TermG, BfiF, BfiG, FEC, GEC);
-    for (unsigned I = 0, E = TermF->getNumSuccessors(); I != E; ++I) {
-      if (!VisitedBBs.insert(TermF->getSuccessor(I)).second)
-        continue;
-      BBWorklist.emplace_back(TermF->getSuccessor(I), TermG->getSuccessor(I));
+/// 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) {
+  std::optional<uint64_t> DstEntryCount = Dst->getEntryCount();
+  std::optional<uint64_t> SrcEntryCount = Src->getEntryCount();
+  const BlockFrequencyInfo *DstBFI = getBFI(*Dst);
+  const BlockFrequencyInfo *SrcBFI = getBFI(*Src);
+
+  // FunctionComparator guarantees identical CFG topology and instruction
+  // ordering, allowing us to walk both functions in lockstep.
+  for (auto [DstBB, SrcBB] : llvm::zip_equal(*Dst, *Src)) {
+    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, DstBFI, SrcBFI,
+                                        DstEntryCount, SrcEntryCount);
+
+      // Handle branch weights on SelectInsts here. Terminators are handled
+      // separately below, outside the instruction loop.
+      if (isa<SelectInst>(DstI))
+        mergeBranchWeightsOnInstructions(&DstI, &SrcI, DstBFI, SrcBFI,
+                                         DstEntryCount, SrcEntryCount);
     }
+    Instruction *DstTerm = DstBB.getTerminator();
+    const Instruction *SrcTerm = SrcBB.getTerminator();
+    mergeBranchWeightsOnInstructions(DstTerm, SrcTerm, DstBFI, SrcBFI,
+                                     DstEntryCount, SrcEntryCount);
   }
 
   if (FAM) {
     PreservedAnalyses PA = PreservedAnalyses::all();
     PA.abandon<BranchProbabilityAnalysis>();
     PA.abandon<BlockFrequencyAnalysis>();
-    FAM->invalidate(*F, PA);
-  } else {
-    CachedBFI.erase(F);
-    CachedBFI.erase(G);
+    FAM->invalidate(*Dst, PA);
   }
 }
 
@@ -1143,8 +1126,8 @@ static void mergeEntryCountsInto(Function *F, std::optional<uint64_t> FC,
 // 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();
+  std::optional<uint64_t> FEntryCount = F->getEntryCount();
+  std::optional<uint64_t> GEntryCount = G->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
@@ -1189,8 +1172,8 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
 
     // Merge !prof, while G still has its body.
     writeThunkOrAliasIfNeeded(F, G, /*MergeProfile*/ true);
-    if (FEC)
-      NewF->setEntryCount(*FEC);
+    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);
@@ -1201,8 +1184,9 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
       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.
-    mergeEntryCountsInto(F, FEC, GEC);
+    // (FEntryCount + GEntryCount), while each ODR thunk retains its own
+    // per-symbol entry count.
+    mergeEntryCountsInto(F, FEntryCount, GEntryCount);
     ++NumDoubleWeak;
     ++NumFunctionsMerged;
   } else {
@@ -1230,15 +1214,15 @@ 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) {
-      mergeProfMetadataInto(F, G);
-      mergeEntryCountsInto(F, FEC, GEC);
+      mergeInstrProfMetadataInto(F, G);
+      mergeEntryCountsInto(F, FEntryCount, GEntryCount);
       G->eraseFromParent();
       ++NumFunctionsMerged;
       return;
     }
 
     if (writeThunkOrAliasIfNeeded(F, G, /*MergeProfile*/ true)) {
-      mergeEntryCountsInto(F, FEC, GEC);
+      mergeEntryCountsInto(F, FEntryCount, GEntryCount);
       ++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-prof-metadata.ll b/llvm/test/Transforms/MergeFunc/merge-functions-prof-metadata.ll
deleted file mode 100644
index 278ded8693662..0000000000000
--- a/llvm/test/Transforms/MergeFunc/merge-functions-prof-metadata.ll
+++ /dev/null
@@ -1,460 +0,0 @@
-; 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
-
-; Check mergefunc combines branch/value profile data.
-
-define internal i32 @bw_asym_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 @bw_asym_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
-}
-
-define internal i32 @bw_sym_a(i32 %x) !prof !5 {
-entry:
-  %cmp = icmp ne i32 %x, 0
-  br i1 %cmp, label %then, label %else, !prof !6
-
-then:
-  ret i32 4
-
-else:
-  ret i32 5
-}
-
-define internal i32 @bw_sym_b(i32 %x) !prof !7 {
-entry:
-  %cmp = icmp ne i32 %x, 0
-  br i1 %cmp, label %then, label %else, !prof !8
-
-then:
-  ret i32 4
-
-else:
-  ret i32 5
-}
-
-define internal i32 @bw_g_only_a(i32 %x) !prof !24 {
-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 @bw_g_only_b(i32 %x) !prof !25 {
-entry:
-  %cmp = icmp sgt i32 %x, 0
-  br i1 %cmp, label %then, label %else, !prof !26
-
-then:
-  ret i32 7
-
-else:
-  ret i32 8
-}
-
-define internal i32 @select_bw_a(i32 %x) !prof !9 {
-entry:
-  %cmp = icmp slt i32 %x, 0
-  %sel = select i1 %cmp, i32 1, i32 0, !prof !10
-  ret i32 %sel
-}
-
-define internal i32 @select_bw_b(i32 %x) !prof !11 {
-entry:
-  %cmp = icmp slt i32 %x, 0
-  %sel = select i1 %cmp, i32 1, i32 0, !prof !12
-  ret i32 %sel
-}
-
-define internal i32 @vp_sym_a(ptr %fn) !prof !13 {
-entry:
-  %fn_val = load ptr, ptr %fn
-  %r = call i32 %fn_val(), !prof !14
-  ret i32 %r
-}
-
-define internal i32 @vp_sym_b(ptr %fn) !prof !15 {
-entry:
-  %fn_val = load ptr, ptr %fn
-  %r = call i32 %fn_val(), !prof !16
-  ret i32 %r
-}
-
-; add i32 1 keeps @vp_asym_* distinct from @vp_sym_*.
-define internal i32 @vp_asym_a(ptr %fn) !prof !17 {
-entry:
-  %fn_val = load ptr, ptr %fn
-  %r = call i32 %fn_val(), !prof !18
-  %adj = add i32 %r, 1
-  ret i32 %adj
-}
-
-define internal i32 @vp_asym_b(ptr %fn) !prof !19 {
-entry:
-  %fn_val = load ptr, ptr %fn
-  %r = call i32 %fn_val()
-  %adj = add i32 %r, 1
-  ret i32 %adj
-}
-
-define internal i32 @vp_g_only_a(ptr %fn) !prof !27 {
-entry:
-  %fn_val = load ptr, ptr %fn
-  %r = call i32 %fn_val()
-  %adj = add i32 %r, 3
-  ret i32 %adj
-}
-
-define internal i32 @vp_g_only_b(ptr %fn) !prof !28 {
-entry:
-  %fn_val = load ptr, ptr %fn
-  %r = call i32 %fn_val(), !prof !29
-  %adj = add i32 %r, 3
-  ret i32 %adj
-}
-
-define internal i32 @vp_kind_mismatch_a(ptr %fn) !prof !20 {
-entry:
-  %fn_val = load ptr, ptr %fn
-  %r = call i32 %fn_val(), !prof !21
-  %adj = add i32 %r, 2
-  ret i32 %adj
-}
-
-define internal i32 @vp_kind_mismatch_b(ptr %fn) !prof !22 {
-entry:
-  %fn_val = load ptr, ptr %fn
-  %r = call i32 %fn_val(), !prof !23
-  %adj = add i32 %r, 2
-  ret i32 %adj
-}
-
-define dso_local i32 @thunk_path_a(i32 %x) unnamed_addr !prof !30 {
-entry:
-  %cmp = icmp ult i32 %x, 3
-  br i1 %cmp, label %then, label %else, !prof !31
-
-then:
-  ret i32 9
-
-else:
-  ret i32 10
-}
-
-define dso_local i32 @thunk_path_b(i32 %x) unnamed_addr !prof !32 {
-entry:
-  %cmp = icmp ult i32 %x, 3
-  br i1 %cmp, label %then, label %else, !prof !33
-
-then:
-  ret i32 9
-
-else:
-  ret i32 10
-}
-
-define weak_odr i32 @odr_path_a(i32 %x) !prof !34 {
-entry:
-  %cmp = icmp ugt i32 %x, 4
-  br i1 %cmp, label %then, label %else, !prof !35
-
-then:
-  ret i32 11
-
-else:
-  ret i32 12
-}
-
-define weak_odr i32 @odr_path_b(i32 %x) !prof !36 {
-entry:
-  %cmp = icmp ugt i32 %x, 4
-  br i1 %cmp, label %then, label %else, !prof !37
-
-then:
-  ret i32 11
-
-else:
-  ret i32 12
-}
-
-define i32 @use(i32 %x, ptr %fn) {
-entry:
-  %a = call i32 @bw_asym_a(i32 %x)
-  %b = call i32 @bw_asym_b(i32 %x)
-  %c = call i32 @bw_sym_a(i32 %x)
-  %d = call i32 @bw_sym_b(i32 %x)
-  %e = call i32 @bw_g_only_a(i32 %x)
-  %f = call i32 @bw_g_only_b(i32 %x)
-  %g = call i32 @select_bw_a(i32 %x)
-  %h = call i32 @select_bw_b(i32 %x)
-  %i = call i32 @vp_sym_a(ptr %fn)
-  %j = call i32 @vp_sym_b(ptr %fn)
-  %k = call i32 @vp_asym_a(ptr %fn)
-  %l = call i32 @vp_asym_b(ptr %fn)
-  %m = call i32 @vp_g_only_a(ptr %fn)
-  %n = call i32 @vp_g_only_b(ptr %fn)
-  %o = call i32 @vp_kind_mismatch_a(ptr %fn)
-  %p = call i32 @vp_kind_mismatch_b(ptr %fn)
-  %q = call i32 @thunk_path_a(i32 %x)
-  %r = call i32 @thunk_path_b(i32 %x)
-  %s = call i32 @odr_path_a(i32 %x)
-  %t = call i32 @odr_path_b(i32 %x)
-  %sum0 = add i32 %a, %b
-  %sum1 = add i32 %sum0, %c
-  %sum2 = add i32 %sum1, %d
-  %sum3 = add i32 %sum2, %e
-  %sum4 = add i32 %sum3, %f
-  %sum5 = add i32 %sum4, %g
-  %sum6 = add i32 %sum5, %h
-  %sum7 = add i32 %sum6, %i
-  %sum8 = add i32 %sum7, %j
-  %sum9 = add i32 %sum8, %k
-  %sum10 = add i32 %sum9, %l
-  %sum11 = add i32 %sum10, %m
-  %sum12 = add i32 %sum11, %n
-  %sum13 = add i32 %sum12, %o
-  %sum14 = add i32 %sum13, %p
-  %sum15 = add i32 %sum14, %q
-  %sum16 = add i32 %sum15, %r
-  %sum17 = add i32 %sum16, %s
-  %sum18 = add i32 %sum17, %t
-  ret i32 %sum18
-}
-
-!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 = !{!"branch_weights", i32 60, i32 40}
-!7 = !{!"function_entry_count", i64 60}
-!8 = !{!"branch_weights", i32 20, i32 80}
-!9 = !{!"function_entry_count", i64 50}
-!10 = !{!"branch_weights", i32 10, i32 90}
-!11 = !{!"function_entry_count", i64 150}
-!12 = !{!"branch_weights", i32 30, i32 70}
-!13 = !{!"function_entry_count", i64 100}
-!14 = !{!"VP", i32 0, i64 100, i64 111, i64 60, i64 222, i64 40}
-!15 = !{!"function_entry_count", i64 200}
-!16 = !{!"VP", i32 0, i64 200, i64 111, i64 120, i64 333, i64 80}
-!17 = !{!"function_entry_count", i64 80}
-!18 = !{!"VP", i32 0, i64 80, i64 444, i64 50, i64 555, i64 30}
-!19 = !{!"function_entry_count", i64 120}
-!20 = !{!"function_entry_count", i64 30}
-!21 = !{!"VP", i32 0, i64 30, i64 666, i64 20}
-!22 = !{!"function_entry_count", i64 70}
-!23 = !{!"VP", i32 1, i64 70, i64 777, i64 50}
-!24 = !{!"function_entry_count", i64 40}
-!25 = !{!"function_entry_count", i64 60}
-!26 = !{!"branch_weights", i32 20, i32 50}
-!27 = !{!"function_entry_count", i64 30}
-!28 = !{!"function_entry_count", i64 70}
-!29 = !{!"VP", i32 0, i64 70, i64 888, i64 50}
-!30 = !{!"function_entry_count", i64 10}
-!31 = !{!"branch_weights", i32 1, i32 9}
-!32 = !{!"function_entry_count", i64 20}
-!33 = !{!"branch_weights", i32 3, i32 17}
-!34 = !{!"function_entry_count", i64 10}
-!35 = !{!"branch_weights", i32 1, i32 9}
-!36 = !{!"function_entry_count", i64 20}
-!37 = !{!"branch_weights", i32 3, i32 17}
-
-; CHECK-LABEL: define internal i32 @bw_asym_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 @bw_sym_a(
-; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF2:![0-9]+]] {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[X]], 0
-; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF3:![0-9]+]]
-; CHECK:       [[THEN]]:
-; CHECK-NEXT:    ret i32 4
-; CHECK:       [[ELSE]]:
-; CHECK-NEXT:    ret i32 5
-;
-;
-; CHECK-LABEL: define internal i32 @bw_g_only_a(
-; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF2]] {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[X]], 0
-; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF4:![0-9]+]]
-; CHECK:       [[THEN]]:
-; CHECK-NEXT:    ret i32 7
-; CHECK:       [[ELSE]]:
-; CHECK-NEXT:    ret i32 8
-;
-;
-; CHECK-LABEL: define internal i32 @select_bw_a(
-; CHECK-SAME: i32 [[X:%.*]]) !prof [[PROF5:![0-9]+]] {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[X]], 0
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], i32 1, i32 0, !prof [[PROF6:![0-9]+]]
-; CHECK-NEXT:    ret i32 [[SEL]]
-;
-;
-; CHECK-LABEL: define internal i32 @vp_sym_a(
-; CHECK-SAME: ptr [[FN:%.*]]) !prof [[PROF0]] {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[FN_VAL:%.*]] = load ptr, ptr [[FN]], align 8
-; CHECK-NEXT:    [[R:%.*]] = call i32 [[FN_VAL]](), !prof [[PROF7:![0-9]+]]
-; CHECK-NEXT:    ret i32 [[R]]
-;
-;
-; CHECK-LABEL: define internal i32 @vp_asym_a(
-; CHECK-SAME: ptr [[FN:%.*]]) !prof [[PROF5]] {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[FN_VAL:%.*]] = load ptr, ptr [[FN]], align 8
-; CHECK-NEXT:    [[R:%.*]] = call i32 [[FN_VAL]](), !prof [[PROF8:![0-9]+]]
-; CHECK-NEXT:    [[ADJ:%.*]] = add i32 [[R]], 1
-; CHECK-NEXT:    ret i32 [[ADJ]]
-;
-;
-; CHECK-LABEL: define internal i32 @vp_g_only_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]](), !prof [[PROF9:![0-9]+]]
-; CHECK-NEXT:    [[ADJ:%.*]] = add i32 [[R]], 3
-; CHECK-NEXT:    ret i32 [[ADJ]]
-;
-;
-; CHECK-LABEL: define internal i32 @vp_kind_mismatch_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]]
-;
-;
-; CHECK-LABEL: define dso_local i32 @thunk_path_a(
-; CHECK-SAME: i32 [[X:%.*]]) unnamed_addr !prof [[PROF10:![0-9]+]] {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X]], 3
-; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF11:![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 [[PROF10]] {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i32 [[X]], 4
-; CHECK-NEXT:    br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]], !prof [[PROF11]]
-; CHECK:       [[THEN]]:
-; CHECK-NEXT:    ret i32 11
-; CHECK:       [[ELSE]]:
-; CHECK-NEXT:    ret i32 12
-;
-;
-; CHECK-LABEL: define i32 @use(
-; CHECK-SAME: i32 [[X:%.*]], ptr [[FN:%.*]]) {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[A:%.*]] = call i32 @bw_asym_a(i32 [[X]])
-; CHECK-NEXT:    [[B:%.*]] = call i32 @bw_asym_a(i32 [[X]])
-; CHECK-NEXT:    [[C:%.*]] = call i32 @bw_sym_a(i32 [[X]])
-; CHECK-NEXT:    [[D:%.*]] = call i32 @bw_sym_a(i32 [[X]])
-; CHECK-NEXT:    [[E:%.*]] = call i32 @bw_g_only_a(i32 [[X]])
-; CHECK-NEXT:    [[F:%.*]] = call i32 @bw_g_only_a(i32 [[X]])
-; CHECK-NEXT:    [[G:%.*]] = call i32 @select_bw_a(i32 [[X]])
-; CHECK-NEXT:    [[H:%.*]] = call i32 @select_bw_a(i32 [[X]])
-; CHECK-NEXT:    [[I:%.*]] = call i32 @vp_sym_a(ptr [[FN]])
-; CHECK-NEXT:    [[J:%.*]] = call i32 @vp_sym_a(ptr [[FN]])
-; CHECK-NEXT:    [[K:%.*]] = call i32 @vp_asym_a(ptr [[FN]])
-; CHECK-NEXT:    [[L:%.*]] = call i32 @vp_asym_a(ptr [[FN]])
-; CHECK-NEXT:    [[M:%.*]] = call i32 @vp_g_only_a(ptr [[FN]])
-; CHECK-NEXT:    [[N:%.*]] = call i32 @vp_g_only_a(ptr [[FN]])
-; CHECK-NEXT:    [[O:%.*]] = call i32 @vp_kind_mismatch_a(ptr [[FN]])
-; CHECK-NEXT:    [[P:%.*]] = call i32 @vp_kind_mismatch_a(ptr [[FN]])
-; CHECK-NEXT:    [[Q:%.*]] = call i32 @thunk_path_a(i32 [[X]])
-; CHECK-NEXT:    [[R:%.*]] = call i32 @thunk_path_a(i32 [[X]])
-; CHECK-NEXT:    [[S:%.*]] = call i32 @[[GLOB0:[0-9]+]](i32 [[X]])
-; CHECK-NEXT:    [[T:%.*]] = call i32 @[[GLOB0]](i32 [[X]])
-; CHECK-NEXT:    [[SUM0:%.*]] = add i32 [[A]], [[B]]
-; CHECK-NEXT:    [[SUM1:%.*]] = add i32 [[SUM0]], [[C]]
-; CHECK-NEXT:    [[SUM2:%.*]] = add i32 [[SUM1]], [[D]]
-; CHECK-NEXT:    [[SUM3:%.*]] = add i32 [[SUM2]], [[E]]
-; CHECK-NEXT:    [[SUM4:%.*]] = add i32 [[SUM3]], [[F]]
-; CHECK-NEXT:    [[SUM5:%.*]] = add i32 [[SUM4]], [[G]]
-; CHECK-NEXT:    [[SUM6:%.*]] = add i32 [[SUM5]], [[H]]
-; CHECK-NEXT:    [[SUM7:%.*]] = add i32 [[SUM6]], [[I]]
-; CHECK-NEXT:    [[SUM8:%.*]] = add i32 [[SUM7]], [[J]]
-; CHECK-NEXT:    [[SUM9:%.*]] = add i32 [[SUM8]], [[K]]
-; CHECK-NEXT:    [[SUM10:%.*]] = add i32 [[SUM9]], [[L]]
-; CHECK-NEXT:    [[SUM11:%.*]] = add i32 [[SUM10]], [[M]]
-; CHECK-NEXT:    [[SUM12:%.*]] = add i32 [[SUM11]], [[N]]
-; CHECK-NEXT:    [[SUM13:%.*]] = add i32 [[SUM12]], [[O]]
-; CHECK-NEXT:    [[SUM14:%.*]] = add i32 [[SUM13]], [[P]]
-; CHECK-NEXT:    [[SUM15:%.*]] = add i32 [[SUM14]], [[Q]]
-; CHECK-NEXT:    [[SUM16:%.*]] = add i32 [[SUM15]], [[R]]
-; CHECK-NEXT:    [[SUM17:%.*]] = add i32 [[SUM16]], [[S]]
-; CHECK-NEXT:    [[SUM18:%.*]] = add i32 [[SUM17]], [[T]]
-; CHECK-NEXT:    ret i32 [[SUM18]]
-;
-;
-; CHECK-LABEL: define dso_local i32 @thunk_path_b(
-; CHECK-SAME: i32 [[TMP0:%.*]]) unnamed_addr !prof [[PROF12:![0-9]+]] {
-; CHECK-NEXT:    [[TMP2:%.*]] = tail call i32 @thunk_path_a(i32 [[TMP0]])
-; CHECK-NEXT:    ret i32 [[TMP2]]
-;
-;
-; CHECK-LABEL: define weak_odr i32 @odr_path_b(
-; CHECK-SAME: i32 [[TMP0:%.*]]) !prof [[PROF12]] {
-; CHECK-NEXT:    [[TMP2:%.*]] = tail call i32 @[[GLOB0]](i32 [[TMP0]])
-; CHECK-NEXT:    ret i32 [[TMP2]]
-;
-;
-; CHECK-LABEL: define weak_odr i32 @odr_path_a(
-; CHECK-SAME: i32 [[TMP0:%.*]]) !prof [[PROF13:![0-9]+]] {
-; 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 36, i32 64}
-; CHECK: [[PROF4]] = !{!"branch_weights", i32 17, i32 43}
-; CHECK: [[PROF5]] = !{!"function_entry_count", i64 200}
-; CHECK: [[PROF6]] = !{!"branch_weights", i32 50, i32 150}
-; CHECK: [[PROF7]] = !{!"VP", i32 0, i64 300, i64 111, i64 180, i64 333, i64 80, i64 222, i64 40}
-; CHECK: [[PROF8]] = !{!"VP", i32 0, i64 80, i64 444, i64 50, i64 555, i64 30}
-; CHECK: [[PROF9]] = !{!"VP", i32 0, i64 50, i64 888, i64 50}
-; CHECK: [[PROF10]] = !{!"function_entry_count", i64 30}
-; CHECK: [[PROF11]] = !{!"branch_weights", i32 4, i32 26}
-; CHECK: [[PROF12]] = !{!"function_entry_count", i64 20}
-; CHECK: [[PROF13]] = !{!"function_entry_count", i64 10}
-;.
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..c12a1e244f818
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/merge-functions-value-profile.ll
@@ -0,0 +1,143 @@
+; 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.
+;
+; To produce the merged profile, the pass first scales each function's
+; VP counts based on its entry count to obtain absolute execution counts.
+; It then combines the profiles, adding together 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}
+;.

>From f8c01470ae507c3c3cc9d2f048eb88c376f3785e Mon Sep 17 00:00:00 2001
From: Alok Kumar Sharma <AlokKumar.Sharma at amd.com>
Date: Fri, 10 Jul 2026 13:10:46 +0530
Subject: [PATCH 3/4] fixed format.

---
 llvm/lib/Transforms/IPO/MergeFunctions.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/IPO/MergeFunctions.cpp b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
index a3dd33cefe6ec..4a29c49e493d5 100644
--- a/llvm/lib/Transforms/IPO/MergeFunctions.cpp
+++ b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
@@ -270,7 +270,7 @@ class MergeFunctions {
   /// again.
   void mergeTwoFunctions(Function *F, Function *G);
 
-  const BlockFrequencyInfo* getBFI(Function &F);
+  const BlockFrequencyInfo *getBFI(Function &F);
 
   void mergeInstrProfMetadataInto(Function *Dst, Function *Src);
 

>From bb964c3beaf5a8d3e3f576fe17bd6f381b689b1e Mon Sep 17 00:00:00 2001
From: Alok Kumar Sharma <AlokKumar.Sharma at amd.com>
Date: Mon, 13 Jul 2026 15:53:55 +0530
Subject: [PATCH 4/4] Review comments.

---
 .../llvm/Transforms/IPO/MergeFunctions.h      |  8 +-
 llvm/lib/Transforms/IPO/MergeFunctions.cpp    | 35 +++++----
 .../Transforms/IPO/MergeFunctionsTest.cpp     | 75 ++++++++++++-------
 3 files changed, 69 insertions(+), 49 deletions(-)

diff --git a/llvm/include/llvm/Transforms/IPO/MergeFunctions.h b/llvm/include/llvm/Transforms/IPO/MergeFunctions.h
index e5fbf0ee6c77a..5d73e0d90c8c6 100644
--- a/llvm/include/llvm/Transforms/IPO/MergeFunctions.h
+++ b/llvm/include/llvm/Transforms/IPO/MergeFunctions.h
@@ -28,13 +28,9 @@ class MergeFunctionsPass : public OptionalPassInfoMixin<MergeFunctionsPass> {
 public:
   LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
 
-  /// When null, instruction-level profile merging scales using function entry
-  /// counts only. The NewPM path always provides FAM.
-  LLVM_ABI static bool runOnModule(Module &M,
-                                   FunctionAnalysisManager *FAM = nullptr);
+  LLVM_ABI static bool runOnModule(Module &M, ModuleAnalysisManager &AM);
   LLVM_ABI static DenseMap<Function *, Function *>
-  runOnFunctions(ArrayRef<Function *> F,
-                 FunctionAnalysisManager *FAM = nullptr);
+  runOnFunctions(ArrayRef<Function *> F, ModuleAnalysisManager &AM);
 };
 
 } // end namespace llvm
diff --git a/llvm/lib/Transforms/IPO/MergeFunctions.cpp b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
index 4a29c49e493d5..04d56ebdfde27 100644
--- a/llvm/lib/Transforms/IPO/MergeFunctions.cpp
+++ b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
@@ -207,7 +207,7 @@ class FunctionNode {
 /// bitcast of the other.
 class MergeFunctions {
 public:
-  explicit MergeFunctions(FunctionAnalysisManager *FAM = nullptr)
+  explicit MergeFunctions(FunctionAnalysisManager &FAM)
       : FnTree(FunctionNodeCmp(&GlobalNumbers)), FAM(FAM) {}
 
   template <typename FuncContainer> bool run(FuncContainer &Functions);
@@ -325,21 +325,21 @@ class MergeFunctions {
   /// Deleted-New functions mapping
   DenseMap<Function *, Function *> DelToNewMap;
 
-  FunctionAnalysisManager *FAM = nullptr;
+  FunctionAnalysisManager &FAM;
 };
 } // end anonymous namespace
 
 PreservedAnalyses MergeFunctionsPass::run(Module &M,
                                           ModuleAnalysisManager &AM) {
-  auto &FAM = AM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager();
-  if (!MergeFunctionsPass::runOnModule(M, &FAM))
+  if (!MergeFunctionsPass::runOnModule(M, AM))
     return PreservedAnalyses::all();
   return PreservedAnalyses::none();
 }
 
 SmallPtrSet<GlobalValue *, 4> &MergeFunctions::getUsed() { return Used; }
 
-bool MergeFunctionsPass::runOnModule(Module &M, FunctionAnalysisManager *FAM) {
+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);
@@ -350,7 +350,15 @@ bool MergeFunctionsPass::runOnModule(Module &M, FunctionAnalysisManager *FAM) {
 
 DenseMap<Function *, Function *>
 MergeFunctionsPass::runOnFunctions(ArrayRef<Function *> F,
-                                   FunctionAnalysisManager *FAM) {
+                                   ModuleAnalysisManager &AM) {
+  if (F.empty())
+    return DenseMap<Function *, Function *>();
+
+  Module &M = *F.front()->getParent();
+  assert(
+      llvm::all_of(F, [&M](Function *Fn) { return Fn->getParent() == &M; }) &&
+      "all functions must belong to the same module");
+  auto &FAM = AM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager();
   MergeFunctions MF(FAM);
   return MF.runOnFunctions(F);
 }
@@ -1068,10 +1076,7 @@ mergeValueProfileOnInstructions(Instruction *DstI, const Instruction *SrcI,
 }
 
 const BlockFrequencyInfo *MergeFunctions::getBFI(Function &F) {
-  if (!FAM)
-    return nullptr;
-
-  return &FAM->getResult<BlockFrequencyAnalysis>(F);
+  return &FAM.getResult<BlockFrequencyAnalysis>(F);
 }
 
 /// Merge \p Src's instruction-level branch weights and value profile
@@ -1107,12 +1112,10 @@ void MergeFunctions::mergeInstrProfMetadataInto(Function *Dst, Function *Src) {
                                      DstEntryCount, SrcEntryCount);
   }
 
-  if (FAM) {
-    PreservedAnalyses PA = PreservedAnalyses::all();
-    PA.abandon<BranchProbabilityAnalysis>();
-    PA.abandon<BlockFrequencyAnalysis>();
-    FAM->invalidate(*Dst, PA);
-  }
+  PreservedAnalyses PA = PreservedAnalyses::all();
+  PA.abandon<BranchProbabilityAnalysis>();
+  PA.abandon<BlockFrequencyAnalysis>();
+  FAM.invalidate(*Dst, PA);
 }
 
 static void mergeEntryCountsInto(Function *F, std::optional<uint64_t> FC,
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);



More information about the llvm-commits mailing list