[llvm] 2f51e5b - [MergeFunc] Preserve observable function pointer identity (#213604)

via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 24 02:21:17 PDT 2026


Author: Gauarv Chaudhary
Date: 2026-08-24T09:21:12Z
New Revision: 2f51e5b46ac4bcaea1ff7b504d484709f908ac0b

URL: https://github.com/llvm/llvm-project/commit/2f51e5b46ac4bcaea1ff7b504d484709f908ac0b
DIFF: https://github.com/llvm/llvm-project/commit/2f51e5b46ac4bcaea1ff7b504d484709f908ac0b.diff

LOG: [MergeFunc] Preserve observable function pointer identity (#213604)

Fixes #213206.

MergeFunc can replace one function with a forwarding thunk to another.
FunctionComparator treated self-references as equal in every context, so
functions that observe their own address (e.g. icmp %p, @g) could be
merged incorrectly.
Compare ordinary function-pointer uses as normal global values. Treat
self-references as equal only for corresponding call targets and
blockaddress.

AI assistance was used while preparing this change.

Added: 
    llvm/test/Transforms/MergeFunc/recursive-self-reference.ll
    llvm/test/Transforms/MergeFunc/self-reference.ll

Modified: 
    llvm/include/llvm/Transforms/Utils/FunctionComparator.h
    llvm/lib/Transforms/Utils/FunctionComparator.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Transforms/Utils/FunctionComparator.h b/llvm/include/llvm/Transforms/Utils/FunctionComparator.h
index d765875864a89..abe22bc2cade8 100644
--- a/llvm/include/llvm/Transforms/Utils/FunctionComparator.h
+++ b/llvm/include/llvm/Transforms/Utils/FunctionComparator.h
@@ -226,9 +226,6 @@ class FunctionComparator {
   /// return whether the numbers are equal. Numbers are assigned in the order
   /// visited.
   /// Comparison order:
-  /// Stage 0: Value that is function itself is always greater then others.
-  ///          If left and right values are references to their functions, then
-  ///          they are equal.
   /// Stage 1: Constants are greater than non-constants.
   ///          If both left and right are constants, then the result of
   ///          cmpConstants is used as cmpValues result.
@@ -242,6 +239,11 @@ class FunctionComparator {
   ///          see comments for sn_mapL and sn_mapR.
   LLVM_ABI int cmpValues(const Value *L, const Value *R) const;
 
+  /// References to the functions being compared are equal. Used for
+  /// corresponding call targets and blockaddress functions, which stay
+  /// valid if MergeFunc replaces one body with a forwarding thunk.
+  int cmpValuesAllowingSelfRef(const Value *L, const Value *R) const;
+
   /// Compare two Instructions for equivalence, similar to
   /// Instruction::isSameOperationAs.
   ///

diff  --git a/llvm/lib/Transforms/Utils/FunctionComparator.cpp b/llvm/lib/Transforms/Utils/FunctionComparator.cpp
index 05c24af6efb39..679d4dbfeff54 100644
--- a/llvm/lib/Transforms/Utils/FunctionComparator.cpp
+++ b/llvm/lib/Transforms/Utils/FunctionComparator.cpp
@@ -478,7 +478,8 @@ int FunctionComparator::cmpConstants(const Constant *L,
   case Value::BlockAddressVal: {
     const BlockAddress *LBA = cast<BlockAddress>(L);
     const BlockAddress *RBA = cast<BlockAddress>(R);
-    if (int Res = cmpValues(LBA->getFunction(), RBA->getFunction()))
+    if (int Res =
+            cmpValuesAllowingSelfRef(LBA->getFunction(), RBA->getFunction()))
       return Res;
     if (LBA->getFunction() == RBA->getFunction()) {
       // They are BBs in the same function. Order by which comes first in the
@@ -891,23 +892,23 @@ int FunctionComparator::cmpInlineAsm(const InlineAsm *L,
   return 0;
 }
 
-/// Compare two values used by the two functions under pair-wise comparison. If
-/// this is the first time the values are seen, they're added to the mapping so
-/// that we will detect mismatches on next use.
-/// See comments in declaration for more details.
-int FunctionComparator::cmpValues(const Value *L, const Value *R) const {
-  // Catch self-reference case.
+int FunctionComparator::cmpValuesAllowingSelfRef(const Value *L,
+                                                 const Value *R) const {
   if (L == FnL) {
     if (R == FnR)
       return 0;
     return -1;
   }
-  if (R == FnR) {
-    if (L == FnL)
-      return 0;
+  if (R == FnR)
     return 1;
-  }
+  return cmpValues(L, R);
+}
 
+/// Compare two values used by the two functions under pair-wise comparison. If
+/// this is the first time the values are seen, they're added to the mapping so
+/// that we will detect mismatches on next use.
+/// See comments in declaration for more details.
+int FunctionComparator::cmpValues(const Value *L, const Value *R) const {
   const Constant *ConstL = dyn_cast<Constant>(L);
   const Constant *ConstR = dyn_cast<Constant>(R);
   if (ConstL && ConstR) {
@@ -964,11 +965,19 @@ int FunctionComparator::cmpBasicBlocks(const BasicBlock *BBL,
       return Res;
     if (needToCmpOperands) {
       assert(InstL->getNumOperands() == InstR->getNumOperands());
+      const auto *CBL = dyn_cast<CallBase>(&*InstL);
 
       for (unsigned i = 0, e = InstL->getNumOperands(); i != e; ++i) {
         Value *OpL = InstL->getOperand(i);
         Value *OpR = InstR->getOperand(i);
-        if (int Res = cmpValues(OpL, OpR))
+        int Res;
+        if (CBL && CBL->isCallee(&InstL->getOperandUse(i))) {
+          assert(cast<CallBase>(&*InstR)->isCallee(&InstR->getOperandUse(i)));
+          Res = cmpValuesAllowingSelfRef(OpL, OpR);
+        } else {
+          Res = cmpValues(OpL, OpR);
+        }
+        if (Res)
           return Res;
         // cmpValues should ensure this is true.
         assert(cmpTypes(OpL->getType(), OpR->getType()) == 0);

diff  --git a/llvm/test/Transforms/MergeFunc/recursive-self-reference.ll b/llvm/test/Transforms/MergeFunc/recursive-self-reference.ll
new file mode 100644
index 0000000000000..7478696b43828
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/recursive-self-reference.ll
@@ -0,0 +1,29 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=mergefunc < %s | FileCheck %s
+
+; Recursive call targets may still be considered equivalent.
+
+define internal void @recursive_f() {
+; CHECK-LABEL: define internal void @recursive_f() {
+; CHECK-NEXT:    call void @recursive_f()
+; CHECK-NEXT:    ret void
+;
+  call void @recursive_f()
+  ret void
+}
+
+define internal void @recursive_g() {
+  call void @recursive_g()
+  ret void
+}
+
+define i32 @main() {
+; CHECK-LABEL: define i32 @main() {
+; CHECK-NEXT:    call void @recursive_f()
+; CHECK-NEXT:    call void @recursive_f()
+; CHECK-NEXT:    ret i32 0
+;
+  call void @recursive_f()
+  call void @recursive_g()
+  ret i32 0
+}

diff  --git a/llvm/test/Transforms/MergeFunc/self-reference.ll b/llvm/test/Transforms/MergeFunc/self-reference.ll
new file mode 100644
index 0000000000000..99f6a87e44c0b
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/self-reference.ll
@@ -0,0 +1,67 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=mergefunc < %s | FileCheck %s
+
+; A function's address is observable when it is used as a value, so it must not
+; be replaced with the address of a forwarding thunk's target.
+
+define void @f(ptr %p) {
+; CHECK-LABEL: define void @f(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq ptr [[P]], @f
+; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    ret void
+;
+  %cmp = icmp eq ptr %p, @f
+  call void @llvm.assume(i1 %cmp)
+  ret void
+}
+
+define void @g(ptr %p) {
+; CHECK-LABEL: define void @g(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq ptr [[P]], @g
+; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    ret void
+;
+  %cmp = icmp eq ptr %p, @g
+  call void @llvm.assume(i1 %cmp)
+  ret void
+}
+
+; A self-reference passed as an argument is also observable and must remain a
+; normal value comparison rather than a call-target comparison.
+
+declare void @consume(ptr)
+
+define void @arg_f() {
+; CHECK-LABEL: define void @arg_f() {
+; CHECK-NEXT:    call void @consume(ptr @arg_f)
+; CHECK-NEXT:    ret void
+;
+  call void @consume(ptr @arg_f)
+  ret void
+}
+
+define void @arg_g() {
+; CHECK-LABEL: define void @arg_g() {
+; CHECK-NEXT:    call void @consume(ptr @arg_g)
+; CHECK-NEXT:    ret void
+;
+  call void @consume(ptr @arg_g)
+  ret void
+}
+
+define i32 @main() {
+; CHECK-LABEL: define i32 @main() {
+; CHECK-NEXT:    call void @f(ptr @f)
+; CHECK-NEXT:    call void @g(ptr @g)
+; CHECK-NEXT:    call void @arg_f()
+; CHECK-NEXT:    call void @arg_g()
+; CHECK-NEXT:    ret i32 0
+;
+  call void @f(ptr @f)
+  call void @g(ptr @g)
+  call void @arg_f()
+  call void @arg_g()
+  ret i32 0
+}


        


More information about the llvm-commits mailing list