Skip to content

Commit d9449c9

Browse files
Merge pull request #10179 from nate-chandler/coro-ret-popless
[Coro] Adopt ret.popless intrinsic.
2 parents 527e084 + 3026ca3 commit d9449c9

File tree

9 files changed

+161
-11
lines changed

9 files changed

+161
-11
lines changed

llvm/include/llvm/IR/Intrinsics.td

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,20 @@ def int_localrecover : DefaultAttrsIntrinsic<[llvm_ptr_ty],
838838
[llvm_ptr_ty, llvm_ptr_ty, llvm_i32_ty],
839839
[IntrNoMem, ImmArg<ArgIndex<2>>]>;
840840

841+
// Marks the following ret instruction as a "popless" return, one that does not
842+
// not restore SP to its function-entry value (i.e., does not deallocate the
843+
// stack frame), allowing allocations made in the function to be accessible
844+
// by the caller.
845+
//
846+
// The function must be annotated with an appropriate target-specific calling
847+
// convention, so the caller can generate stack accesses accordingly, generally
848+
// by treating the call as a variably-sized alloca, so using FP-based addressing
849+
// for its own frame rather than relying on statically known SP offsets.
850+
//
851+
// Calls to this intrinsic need to be musttail, but don't follow the other ABI
852+
// requirements for musttail calls, since this is really annotating the ret.
853+
def int_ret_popless : DefaultAttrsIntrinsic<[], [], [IntrNoMem]>;
854+
841855
// Given the frame pointer passed into an SEH filter function, returns a
842856
// pointer to the local variable area suitable for use with llvm.localrecover.
843857
def int_eh_recoverfp : DefaultAttrsIntrinsic<[llvm_ptr_ty],
@@ -1754,10 +1768,6 @@ def int_coro_await_suspend_handle : Intrinsic<[],
17541768
[llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty],
17551769
[Throws]>;
17561770

1757-
// FIXME: enforce musttail
1758-
// XXX: attrs; not throws, wb DefaultAttrsIntrinsic
1759-
def int_coro_return : Intrinsic<[llvm_any_ty], [LLVMMatchType<0>], []>;
1760-
17611771
// Coroutine Lowering Intrinsics. Used internally by coroutine passes.
17621772

17631773
def int_coro_subfn_addr : DefaultAttrsIntrinsic<

llvm/lib/Analysis/InlineCost.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2363,6 +2363,7 @@ bool CallAnalyzer::visitCallBase(CallBase &Call) {
23632363
return false;
23642364
case Intrinsic::icall_branch_funnel:
23652365
case Intrinsic::localescape:
2366+
case Intrinsic::ret_popless:
23662367
HasUninlineableIntrinsic = true;
23672368
return false;
23682369
case Intrinsic::vastart:

llvm/lib/IR/BasicBlock.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,14 @@ const CallInst *BasicBlock::getTerminatingMustTailCall() const {
306306
if (!Prev)
307307
return nullptr;
308308

309+
// Some musttail intrinsic calls are special in being really simply ret
310+
// annotations, and only need to be the last instruction before the ret.
311+
// We don't need to look through the return value in those cases.
312+
// FIXME: we should generalize getTerminatingDeoptimizeCall for this case.
313+
if (auto *CI = dyn_cast<CallInst>(Prev))
314+
if (CI->isMustTailCall() && CI->getIntrinsicID() == Intrinsic::ret_popless)
315+
return CI;
316+
309317
if (Value *RV = RI->getReturnValue()) {
310318
if (RV != Prev)
311319
return nullptr;

llvm/lib/IR/Verifier.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3912,6 +3912,15 @@ void Verifier::verifyMustTailCall(CallInst &CI) {
39123912
&CI);
39133913
}
39143914
#endif
3915+
Check(CI.getIntrinsicID() != Intrinsic::ret_popless,
3916+
"llvm.ret.popless call must be musttail", &CI);
3917+
return;
3918+
}
3919+
3920+
// Some musttail intrinsic calls are special, and don't have all the rules.
3921+
if (CI.getIntrinsicID() == Intrinsic::ret_popless) {
3922+
ReturnInst *Ret = dyn_cast_or_null<ReturnInst>(CI.getNextNode());
3923+
Check(Ret, "musttail intrinsic call must precede a ret", &CI);
39153924
return;
39163925
}
39173926

llvm/lib/Transforms/Coroutines/CoroCleanup.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,6 @@ bool Lowerer::lower(Function &F) {
7272
case Intrinsic::coro_id_async:
7373
II->replaceAllUsesWith(ConstantTokenNone::get(Context));
7474
break;
75-
case Intrinsic::coro_return:
76-
// FIXME: Remove this case with backend support.
77-
II->replaceAllUsesWith(II->getArgOperand(0));
78-
break;
7975
case Intrinsic::coro_subfn_addr:
8076
lowerSubFn(Builder, cast<CoroSubFnInst>(II));
8177
break;

llvm/lib/Transforms/Coroutines/CoroSplit.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1987,9 +1987,12 @@ static void splitRetconCoroutine(Function &F, coro::Shape &Shape,
19871987
F.getContext(), "coro.return.normal", &F, NewSuspendBB);
19881988
Builder.CreateCondBr(NullAllocator, PoplessReturnBB, NormalReturnBB);
19891989
IRBuilder<> PoplessBuilder(PoplessReturnBB);
1990-
auto *WrapRetV = PoplessBuilder.CreateIntrinsic(
1991-
RetV->getType(), Intrinsic::coro_return, {RetV});
1992-
PoplessBuilder.CreateRet(WrapRetV);
1990+
auto &Context = F.getContext();
1991+
auto *VoidTy = Type::getVoidTy(Context);
1992+
auto *RetPopless =
1993+
PoplessBuilder.CreateIntrinsic(VoidTy, Intrinsic::ret_popless, {});
1994+
RetPopless->setTailCallKind(CallInst::TailCallKind::TCK_MustTail);
1995+
PoplessBuilder.CreateRet(RetV);
19931996
IRBuilder<> NormalBuilder(NormalReturnBB);
19941997
NormalBuilder.CreateRet(RetV);
19951998
} else {

llvm/lib/Transforms/Utils/SimplifyCFG.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,6 +1481,12 @@ static bool isSafeToHoistInstr(Instruction *I, unsigned Flags) {
14811481
if (CB->getIntrinsicID() == Intrinsic::experimental_deoptimize)
14821482
return false;
14831483

1484+
// Similarly for llvm.ret.popless (and likely generalizable to all musttail
1485+
// intrinsics).
1486+
if (auto *CB = dyn_cast<CallBase>(I))
1487+
if (CB->getIntrinsicID() == Intrinsic::ret_popless)
1488+
return false;
1489+
14841490
// It's also unsafe/illegal to hoist an instruction above its instruction
14851491
// operands
14861492
BasicBlock *BB = I->getParent();
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
; RUN: opt < %s -passes='module(coro-early),cgscc(coro-split),module(coro-cleanup)' -S | FileCheck %s
2+
3+
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
4+
target triple = "arm64-apple-macos99.99"
5+
6+
; CHECK-LABEL: %func.Frame = type { ptr }
7+
8+
; CHECK-LABEL: @func_cfp = constant <{ i32, i32 }>
9+
; CHECK-SAME: <{
10+
; CHECK-SAME: i32 trunc
11+
; CHECK-SAME: i32 16
12+
; CHECK-SAME: }>
13+
@func_cfp = constant <{ i32, i32 }>
14+
<{ i32 trunc ( ; offset to @func from @func_cfp
15+
i64 sub (
16+
i64 ptrtoint (ptr @func to i64),
17+
i64 ptrtoint (ptr getelementptr inbounds (<{ i32, i32 }>, ptr @func_cfp, i32 0, i32 1) to i64)
18+
)
19+
to i32),
20+
i32 64 ; frame size
21+
}>
22+
23+
24+
; CHECK-LABEL: @func(
25+
; CHECK-SAME: ptr %buffer,
26+
; CHECK-SAME: ptr %allocator
27+
; CHECK-SAME: ptr %array
28+
; CHECK-SAME: ) {
29+
; CHECK: %array.spill.addr = getelementptr inbounds %func.Frame, ptr %buffer, i32 0, i32 0
30+
; CHECK: store ptr %array, ptr %array.spill.addr
31+
; CHECK: %load = load i32, ptr %array
32+
; CHECK: %load.positive = icmp sgt i32 %load, 0
33+
; CHECK: [[CONTINUATION:%.*]] = select i1 %load.positive
34+
; CHECK-SAME: ptr @func.resume.0
35+
; CHECK-SAME: ptr @func.resume.1
36+
; CHECK: [[RETVAL_1:%.*]] = insertvalue { ptr, i32 } poison, ptr [[CONTINUATION:%.*]], 0
37+
; CHECK: [[RETVAL_2:%.*]] = insertvalue { ptr, i32 } [[RETVAL_1:%.*]], i32 %load, 1
38+
; CHECK: [[DONT_POP:%.*]] = icmp eq ptr %allocator, null
39+
; CHECK: br i1 [[DONT_POP:%[^,]+]],
40+
; CHECK-SAME: label %coro.return.popless
41+
; CHECK-SAME: label %coro.return.normal
42+
; CHECK: coro.return.popless:
43+
; CHECK: musttail call void @llvm.ret.popless()
44+
; CHECK: ret { ptr, i32 } [[RETVAL_2:%.*]]
45+
; CHECK: coro.return.normal:
46+
; CHECK: ret { ptr, i32 } [[RETVAL_2:%.*]]
47+
; CHECK: }
48+
49+
; CHECK-LABEL: @func.resume.0(
50+
; CHECK-SAME: ptr [[BUFFER:[^,]+]]
51+
; CHECK-SAME: ptr [[ALLOCATOR:%[^)]+]]
52+
; CHECK-SAME: ) {
53+
; CHECK: %array.reload.addr3 = getelementptr inbounds %func.Frame, ptr [[BUFFER:%.*]], i32 0, i32 0
54+
; CHECK: %array.reload4 = load ptr, ptr %array.reload.addr3
55+
; CHECK: store i32 0, ptr %array.reload4
56+
; CHECK: ret void
57+
; CHECK: }
58+
59+
; CHECK-LABEL: @func.resume.1(
60+
; CHECK-SAME: ptr [[BUFFER:[^,]+]]
61+
; CHECK-SAME: ptr [[ALLOCATOR:%[^)]+]]
62+
; CHECK-SAME: ) {
63+
; CHECK: %array.reload.addr = getelementptr inbounds %func.Frame, ptr [[BUFFER:%.*]], i32 0, i32 0
64+
; CHECK: %array.reload = load ptr, ptr %array.reload.addr
65+
; CHECK: store i32 10, ptr %array.reload
66+
; CHECK: ret void
67+
; CHECK: }
68+
define swiftcorocc {ptr, i32} @func(ptr %buffer, ptr %allocator, ptr %array) {
69+
entry:
70+
%id = call token @llvm.coro.id.retcon.once.dynamic(
71+
i32 -1,
72+
i32 16,
73+
ptr @func_cfp,
74+
ptr %allocator,
75+
ptr %buffer,
76+
ptr @continuation_prototype,
77+
ptr @allocate,
78+
ptr @deallocate
79+
)
80+
%handle = call ptr @llvm.coro.begin(token %id, ptr null)
81+
%load = load i32, ptr %array
82+
%load.positive = icmp sgt i32 %load, 0
83+
br i1 %load.positive, label %positive, label %negative
84+
85+
positive:
86+
call ptr (...) @llvm.coro.suspend.retcon.p0(i32 %load)
87+
store i32 0, ptr %array, align 4
88+
br label %cleanup
89+
90+
negative:
91+
call ptr (...) @llvm.coro.suspend.retcon.p0(i32 %load)
92+
store i32 10, ptr %array, align 4
93+
br label %cleanup
94+
95+
cleanup:
96+
call i1 @llvm.coro.end(ptr %handle, i1 0, token none)
97+
unreachable
98+
}
99+
100+
declare void @continuation_prototype(ptr, ptr)
101+
102+
declare swiftcorocc noalias ptr @allocate(i32 %size)
103+
declare void @deallocate(ptr %ptr)

llvm/test/Verifier/ret_popless.ll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
; RUN: not opt -passes=verify < %s 2>&1 | FileCheck %s
2+
3+
define void @test_ret_popless_not_musttail() {
4+
; CHECK: llvm.ret.popless call must be musttail
5+
call void @llvm.ret.popless()
6+
ret void
7+
}
8+
9+
define i64 @test_ret_popless_not_returned(i64 %a) {
10+
; CHECK: musttail intrinsic call must precede a ret
11+
musttail call void @llvm.ret.popless()
12+
%res = bitcast i64 %a to i64
13+
ret i64 %res
14+
}

0 commit comments

Comments
 (0)