[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