[llvm] d68eb98 - [Local] Preserve !callees when merging calls (#225307)

via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 24 00:01:51 PDT 2026


Author: Mian Miftah
Date: 2026-09-24T09:01:42+02:00
New Revision: d68eb988705ce5e14ca6eba12c74ebdadf573ad7

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

LOG: [Local] Preserve !callees when merging calls (#225307)

When `SimplifyCFG` hoists or sinks two calls, `combineMetadata` drops `!callees`
even if both calls have it. Take the union of the lists when the retained
call moves, since it must allow the targets of either call. If either call
has no `!callees`, drop it from the merged call. For CSE, keep the list on
the call that stays in place.

Added: 
    llvm/test/Transforms/GVN/callees-metadata.ll
    llvm/test/Transforms/SimplifyCFG/merge-callees-metadata.ll

Modified: 
    llvm/include/llvm/IR/Metadata.h
    llvm/lib/IR/Metadata.cpp
    llvm/lib/Transforms/Utils/Local.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/IR/Metadata.h b/llvm/include/llvm/IR/Metadata.h
index 017fd98e704ea..9347a3dd93e13 100644
--- a/llvm/include/llvm/IR/Metadata.h
+++ b/llvm/include/llvm/IR/Metadata.h
@@ -1477,6 +1477,7 @@ class MDNode : public Metadata {
   LLVM_ABI static MDNode *getMergedCallsiteMetadata(MDNode *A, MDNode *B);
   LLVM_ABI static MDNode *getMergedCalleeTypeMetadata(const MDNode *A,
                                                       const MDNode *B);
+  LLVM_ABI static MDNode *getMergedCalleesMetadata(MDNode *A, MDNode *B);
   LLVM_ABI static MDNode *getMergedAllocTokenMetadata(const MDNode *A,
                                                       const MDNode *B);
 

diff  --git a/llvm/lib/IR/Metadata.cpp b/llvm/lib/IR/Metadata.cpp
index 28873462748b2..a947bcfd6fa96 100644
--- a/llvm/lib/IR/Metadata.cpp
+++ b/llvm/lib/IR/Metadata.cpp
@@ -1332,6 +1332,18 @@ MDNode *MDNode::getMergedCalleeTypeMetadata(const MDNode *A, const MDNode *B) {
   return MDNode::get(A->getContext(), AB);
 }
 
+MDNode *MDNode::getMergedCalleesMetadata(MDNode *A, MDNode *B) {
+  // The callees of the merged call are unknown unless both calls list theirs.
+  if (!A || !B)
+    return nullptr;
+  if (A == B)
+    return A;
+  // The merged call may target any callee of either call.
+  SmallSetVector<Metadata *, 8> Callees(llvm::from_range, A->operands());
+  Callees.insert_range(B->operands());
+  return MDNode::get(A->getContext(), Callees.getArrayRef());
+}
+
 MDNode *MDNode::getMergedAllocTokenMetadata(const MDNode *A, const MDNode *B) {
   // Drop !alloc_token metadata if either instruction lacks it to avoid mis-
   // classifying unclassified allocations, where the fallback token must be

diff  --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp
index 3984b6cb00ea4..8c17e8923187a 100644
--- a/llvm/lib/Transforms/Utils/Local.cpp
+++ b/llvm/lib/Transforms/Utils/Local.cpp
@@ -3020,6 +3020,12 @@ static void combineMetadata(Instruction *K, const Instruction *J,
                          MDNode::getMergedCalleeTypeMetadata(KMD, JMD));
         }
         break;
+      case LLVMContext::MD_callees:
+        // If K moves, it replaces J on J's path and must allow J's callees as
+        // well. If K does not move, its callees remain valid.
+        if (!AAOnly && DoesKMove)
+          K->setMetadata(Kind, MDNode::getMergedCalleesMetadata(KMD, JMD));
+        break;
       case LLVMContext::MD_align:
         if (!AAOnly && (DoesKMove || !K->hasMetadata(LLVMContext::MD_noundef)))
           K->setMetadata(

diff  --git a/llvm/test/Transforms/GVN/callees-metadata.ll b/llvm/test/Transforms/GVN/callees-metadata.ll
new file mode 100644
index 0000000000000..460b34c0f588f
--- /dev/null
+++ b/llvm/test/Transforms/GVN/callees-metadata.ll
@@ -0,0 +1,31 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -passes=gvn -S < %s | FileCheck %s
+
+; The first call stays where it is when it replaces the second one, so its
+; callees remain valid. The lists overlap so their union and intersection
+; both 
diff er from the retained list.
+
+declare i32 @a()
+declare i32 @b()
+declare i32 @c()
+
+define i32 @cse(ptr %fp) {
+; CHECK-LABEL: define i32 @cse(
+; CHECK-SAME: ptr [[FP:%.*]]) {
+; CHECK-NEXT:    [[X:%.*]] = call i32 [[FP]]() #[[ATTR0:[0-9]+]], !callees [[META0:![0-9]+]]
+; CHECK-NEXT:    [[SUM:%.*]] = add i32 [[X]], [[X]]
+; CHECK-NEXT:    ret i32 [[SUM]]
+;
+  %x = call i32 %fp() memory(none), !callees !0
+  %y = call i32 %fp() memory(none), !callees !1
+  %sum = add i32 %x, %y
+  ret i32 %sum
+}
+
+!0 = !{ptr @a, ptr @b}
+!1 = !{ptr @b, ptr @c}
+;.
+; CHECK: attributes #[[ATTR0]] = { memory(none) }
+;.
+; CHECK: [[META0]] = !{ptr @a, ptr @b}
+;.

diff  --git a/llvm/test/Transforms/SimplifyCFG/merge-callees-metadata.ll b/llvm/test/Transforms/SimplifyCFG/merge-callees-metadata.ll
new file mode 100644
index 0000000000000..bc5a5f23a18a8
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/merge-callees-metadata.ll
@@ -0,0 +1,164 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -passes='simplifycfg<hoist-common-insts;sink-common-insts>' -S < %s | FileCheck %s --implicit-check-not='!callees'
+
+; Hoisting or sinking replaces the calls on both paths with a single call,
+; which must allow the callees of either of them.
+
+declare void @a()
+declare void @b()
+declare void @c()
+declare void @use(i32)
+
+define void @same_set(i1 %cond, ptr %fp) {
+; CHECK-LABEL: define void @same_set(
+; CHECK-SAME: i1 [[COND:%.*]], ptr [[FP:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    call void [[FP]](), !callees [[META0:![0-9]+]]
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %cond, label %then, label %else
+
+then:
+  call void %fp(), !callees !0
+  br label %end
+
+else:
+  call void %fp(), !callees !0
+  br label %end
+
+end:
+  ret void
+}
+
+define void @
diff erent_sets(i1 %cond, ptr %fp) {
+; CHECK-LABEL: define void @
diff erent_sets(
+; CHECK-SAME: i1 [[COND:%.*]], ptr [[FP:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    call void [[FP]](), !callees [[META1:![0-9]+]]
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %cond, label %then, label %else
+
+then:
+  call void %fp(), !callees !0
+  br label %end
+
+else:
+  call void %fp(), !callees !1
+  br label %end
+
+end:
+  ret void
+}
+
+; Sinking merges calls through 
diff erent pointers into a call through a select
+; of them, which may target the callees of both.
+define void @
diff erent_pointers(i1 %cond, ptr %fp1, ptr %fp2) {
+; CHECK-LABEL: define void @
diff erent_pointers(
+; CHECK-SAME: i1 [[COND:%.*]], ptr [[FP1:%.*]], ptr [[FP2:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[DOT:%.*]] = select i1 [[COND]], i32 1, i32 2
+; CHECK-NEXT:    [[FP1_FP2:%.*]] = select i1 [[COND]], ptr [[FP1]], ptr [[FP2]]
+; CHECK-NEXT:    call void @use(i32 [[DOT]])
+; CHECK-NEXT:    call void [[FP1_FP2]](), !callees [[META2:![0-9]+]]
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %cond, label %then, label %else
+
+then:
+  call void @use(i32 1)
+  call void %fp1(), !callees !3
+  br label %end
+
+else:
+  call void @use(i32 2)
+  call void %fp2(), !callees !4
+  br label %end
+
+end:
+  ret void
+}
+
+; The callees of the merged call are unknown if either call does not list them.
+define void @only_then_has_set(i1 %cond, ptr %fp) {
+; CHECK-LABEL: define void @only_then_has_set(
+; CHECK-SAME: i1 [[COND:%.*]], ptr [[FP:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    call void [[FP]]()
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %cond, label %then, label %else
+
+then:
+  call void %fp(), !callees !0
+  br label %end
+
+else:
+  call void %fp()
+  br label %end
+
+end:
+  ret void
+}
+
+define void @only_else_has_set(i1 %cond, ptr %fp) {
+; CHECK-LABEL: define void @only_else_has_set(
+; CHECK-SAME: i1 [[COND:%.*]], ptr [[FP:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    call void [[FP]]()
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %cond, label %then, label %else
+
+then:
+  call void %fp()
+  br label %end
+
+else:
+  call void %fp(), !callees !0
+  br label %end
+
+end:
+  ret void
+}
+
+; A null operand is left behind when a listed function is deleted. Preserve
+; it when merging the lists.
+define void @deleted_callee(i1 %cond, ptr %fp) {
+; CHECK-LABEL: define void @deleted_callee(
+; CHECK-SAME: i1 [[COND:%.*]], ptr [[FP:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    call void [[FP]](), !callees [[META3:![0-9]+]]
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %cond, label %then, label %else
+
+then:
+  call void %fp(), !callees !2
+  br label %end
+
+else:
+  call void %fp(), !callees !1
+  br label %end
+
+end:
+  ret void
+}
+
+!0 = !{ptr @a, ptr @b}
+!1 = !{ptr @b, ptr @c}
+!2 = !{ptr @a, null}
+!3 = !{ptr @a}
+!4 = !{ptr @c}
+;.
+; CHECK: [[META0]] = !{ptr @a, ptr @b}
+; CHECK: [[META1]] = !{ptr @a, ptr @b, ptr @c}
+; CHECK: [[META2]] = !{ptr @c, ptr @a}
+; CHECK: [[META3]] = !{ptr @a, null, ptr @b, ptr @c}
+;.


        


More information about the llvm-commits mailing list