[llvm] [SimplifyCFG] Don't hoist a musttail call separately from the terminator (PR #207094)

Henry Jiang via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 1 19:17:30 PDT 2026


https://github.com/mustartt updated https://github.com/llvm/llvm-project/pull/207094

>From 381d5b0bb16a8e52464031b53091be97db6d87e9 Mon Sep 17 00:00:00 2001
From: Henry Jiang <henry_jiang2 at apple.com>
Date: Wed, 1 Jul 2026 15:36:56 -0700
Subject: [PATCH 1/2] [SimplifyCFG] Don't hoist a musttail call separately from
 the terminator

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 12 +++++++
 .../hoist-common-skip-pseudoprobe.ll          | 31 +++++++++++++++++++
 .../SimplifyCFG/hoist-common-skip.ll          | 31 +++++++++++++++++++
 3 files changed, 74 insertions(+)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 1e9c66c5a660c..b1b88efe0c2d6 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -2004,6 +2004,18 @@ bool SimplifyCFGOpt::hoistCommonCodeFromSuccessors(Instruction *TI,
           });
     }
 
+    // A musttail call must be immediately followed by a ret, so hoisting is
+    // only legal if its ret is hoisted with it on the next iteration. That is,
+    // no instruction has been skipped (the entire successor can be hoisted into
+    // the predecessor) and the call is directly followed by a ret.
+    if (auto *CI = dyn_cast<CallInst>(I1);
+        AllInstsAreIdentical && CI && CI->isMustTailCall()) {
+      AllInstsAreIdentical =
+          NumSkipped == 0 && all_of(SuccIterPairs, [](const SuccIterPair &P) {
+            return isa<ReturnInst>(*std::next(P.first));
+          });
+    }
+
     if (AllInstsAreIdentical) {
       BB1ItrPair.first++;
       // For a normal instruction, we just move one to right before the
diff --git a/llvm/test/Transforms/SimplifyCFG/hoist-common-skip-pseudoprobe.ll b/llvm/test/Transforms/SimplifyCFG/hoist-common-skip-pseudoprobe.ll
index 9b5e8798072c2..4a3e3b63bb0fa 100644
--- a/llvm/test/Transforms/SimplifyCFG/hoist-common-skip-pseudoprobe.ll
+++ b/llvm/test/Transforms/SimplifyCFG/hoist-common-skip-pseudoprobe.ll
@@ -101,3 +101,34 @@ if.else:
 if.end:
   ret void
 }
+
+declare ptr @callee(ptr, i32, i32)
+
+define ptr @dont_hoist_musttail_past_pseudoprobe(ptr %a, i32 %b, i32 %c) {
+; CHECK-LABEL: @dont_hoist_musttail_past_pseudoprobe(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[COND:%.*]] = icmp eq i32 [[C:%.*]], 0
+; CHECK-NEXT:    br i1 [[COND]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; CHECK:       if.then:
+; CHECK-NEXT:    call void @llvm.pseudoprobe(i64 3, i64 1, i32 0, i64 -1)
+; CHECK-NEXT:    [[CALL:%.*]] = musttail call ptr @callee(ptr [[A:%.*]], i32 [[B:%.*]], i32 [[C]])
+; CHECK-NEXT:    ret ptr [[CALL]]
+; CHECK:       if.else:
+; CHECK-NEXT:    call void @llvm.pseudoprobe(i64 3, i64 2, i32 0, i64 -1)
+; CHECK-NEXT:    [[CALL2:%.*]] = musttail call ptr @callee(ptr [[A]], i32 [[B]], i32 [[C]])
+; CHECK-NEXT:    ret ptr [[CALL2]]
+;
+entry:
+  %cond = icmp eq i32 %c, 0
+  br i1 %cond, label %if.then, label %if.else
+
+if.then:
+  call void @llvm.pseudoprobe(i64 3, i64 1, i32 0, i64 -1)
+  %call = musttail call ptr @callee(ptr %a, i32 %b, i32 %c)
+  ret ptr %call
+
+if.else:
+  call void @llvm.pseudoprobe(i64 3, i64 2, i32 0, i64 -1)
+  %call2 = musttail call ptr @callee(ptr %a, i32 %b, i32 %c)
+  ret ptr %call2
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll b/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll
index e2a32cf444749..7f67a9c2b1407 100644
--- a/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll
+++ b/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll
@@ -1038,3 +1038,34 @@ declare void @inalloca_i64(ptr inalloca(i64))
 declare void @inalloca_i32(ptr inalloca(i32))
 declare ptr @llvm.stacksave()
 declare void @llvm.stackrestore(ptr)
+
+declare ptr @callee(ptr, i32)
+
+define ptr @dont_hoist_musttail_past_skip(ptr %a, i32 %b) {
+; CHECK-LABEL: @dont_hoist_musttail_past_skip(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[C:%.*]] = icmp eq i32 [[B:%.*]], 0
+; CHECK-NEXT:    br i1 [[C]], label [[T:%.*]], label [[E:%.*]]
+; CHECK:       t:
+; CHECK-NEXT:    [[X:%.*]] = add i32 [[B]], 1
+; CHECK-NEXT:    [[CALL:%.*]] = musttail call ptr @callee(ptr [[A:%.*]], i32 [[B]])
+; CHECK-NEXT:    ret ptr [[CALL]]
+; CHECK:       e:
+; CHECK-NEXT:    [[Y:%.*]] = add i32 [[B]], 2
+; CHECK-NEXT:    [[CALL2:%.*]] = musttail call ptr @callee(ptr [[A]], i32 [[B]])
+; CHECK-NEXT:    ret ptr [[CALL2]]
+;
+entry:
+  %c = icmp eq i32 %b, 0
+  br i1 %c, label %t, label %e
+
+t:
+  %x = add i32 %b, 1
+  %call = musttail call ptr @callee(ptr %a, i32 %b)
+  ret ptr %call
+
+e:
+  %y = add i32 %b, 2
+  %call2 = musttail call ptr @callee(ptr %a, i32 %b)
+  ret ptr %call2
+}

>From 07984c44b0e50c1da39e37e8c247bf5e58c13985 Mon Sep 17 00:00:00 2001
From: Henry Jiang <henry_jiang2 at apple.com>
Date: Wed, 1 Jul 2026 19:16:54 -0700
Subject: [PATCH 2/2] testcase for musttail diff

---
 .../SimplifyCFG/hoist-common-skip.ll          | 81 +++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll b/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll
index 7f67a9c2b1407..3e6f31176315b 100644
--- a/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll
+++ b/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll
@@ -1069,3 +1069,84 @@ e:
   %call2 = musttail call ptr @callee(ptr %a, i32 %b)
   ret ptr %call2
 }
+
+;; Positive: identical musttail calls at the block heads, nothing skipped and
+;; each directly followed by a return, so they ARE hoisted and the blocks are
+;; merged into the predecessor.
+define ptr @hoist_musttail_no_skip(ptr %a, i32 %b) {
+; CHECK-LABEL: @hoist_musttail_no_skip(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[CALL:%.*]] = musttail call ptr @callee(ptr [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT:    ret ptr [[CALL]]
+;
+entry:
+  %c = icmp eq i32 %b, 0
+  br i1 %c, label %t, label %e
+
+t:
+  %call = musttail call ptr @callee(ptr %a, i32 %b)
+  ret ptr %call
+
+e:
+  %call2 = musttail call ptr @callee(ptr %a, i32 %b)
+  ret ptr %call2
+}
+
+define ptr @no_hoist_musttail_tail(ptr %a, i32 %b) {
+; CHECK-LABEL: @no_hoist_musttail_tail(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[C:%.*]] = icmp eq i32 [[B:%.*]], 0
+; CHECK-NEXT:    br i1 [[C]], label [[T:%.*]], label [[E:%.*]]
+; CHECK:       t:
+; CHECK-NEXT:    [[CALL:%.*]] = musttail call ptr @callee(ptr [[A:%.*]], i32 [[B]])
+; CHECK-NEXT:    ret ptr [[CALL]]
+; CHECK:       e:
+; CHECK-NEXT:    [[CALL2:%.*]] = tail call ptr @callee(ptr [[A]], i32 [[B]])
+; CHECK-NEXT:    ret ptr [[CALL2]]
+;
+entry:
+  %c = icmp eq i32 %b, 0
+  br i1 %c, label %t, label %e
+
+t:
+  %call = musttail call ptr @callee(ptr %a, i32 %b)
+  ret ptr %call
+
+e:
+  %call2 = tail call ptr @callee(ptr %a, i32 %b)
+  ret ptr %call2
+}
+
+define ptr @hoist_tail_past_skipped(ptr %a, i32 %b) {
+; CHECK-LABEL: @hoist_tail_past_skipped(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[C:%.*]] = icmp eq i32 [[B:%.*]], 0
+; CHECK-NEXT:    [[CALL:%.*]] = tail call ptr @callee(ptr [[A:%.*]], i32 [[B]])
+; CHECK-NEXT:    br i1 [[C]], label [[T:%.*]], label [[E:%.*]]
+; CHECK:       t:
+; CHECK-NEXT:    [[X:%.*]] = add i32 [[B]], 1
+; CHECK-NEXT:    br label [[J:%.*]]
+; CHECK:       e:
+; CHECK-NEXT:    [[Y:%.*]] = add i32 [[B]], 2
+; CHECK-NEXT:    br label [[J]]
+; CHECK:       j:
+; CHECK-NEXT:    ret ptr [[CALL]]
+;
+entry:
+  %c = icmp eq i32 %b, 0
+  br i1 %c, label %t, label %e
+
+t:
+  %x = add i32 %b, 1
+  %call = tail call ptr @callee(ptr %a, i32 %b)
+  br label %j
+
+e:
+  %y = add i32 %b, 2
+  %call2 = tail call ptr @callee(ptr %a, i32 %b)
+  br label %j
+
+j:
+  %p = phi ptr [ %call, %t ], [ %call2, %e ]
+  ret ptr %p
+}



More information about the llvm-commits mailing list