Skip to content

Commit 51d5d7b

Browse files
authored
Extend retcon.once coroutines lowering to optionally produce a normal result (llvm#66333)
One of the main user of these kind of coroutines is swift. There yield-once (`retcon.once`) coroutines are used to temporary "expose" pointers to internal fields of various objects creating borrow scopes. However, in some cases it might be useful also to allow these coroutines to produce a normal result, but there is no convenient way to represent this (as compared to switched-resume kind of coroutines where C++ `co_return` is transformed to a member / callback call on promise object). The extension is simple: we allow continuation function to have a non-void result and accept optional extra arguments via a special `llvm.coro.end.result` intrinsic that would essentially forward them as normal results.
1 parent 1f33911 commit 51d5d7b

File tree

127 files changed

+740
-312
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+740
-312
lines changed

clang/lib/CodeGen/CGCoroutine.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,11 @@ struct CallCoroEnd final : public EHScopeStack::Cleanup {
403403
llvm::Function *CoroEndFn = CGM.getIntrinsic(llvm::Intrinsic::coro_end);
404404
// See if we have a funclet bundle to associate coro.end with. (WinEH)
405405
auto Bundles = getBundlesForCoroEnd(CGF);
406-
auto *CoroEnd = CGF.Builder.CreateCall(
407-
CoroEndFn, {NullPtr, CGF.Builder.getTrue()}, Bundles);
406+
auto *CoroEnd =
407+
CGF.Builder.CreateCall(CoroEndFn,
408+
{NullPtr, CGF.Builder.getTrue(),
409+
llvm::ConstantTokenNone::get(CoroEndFn->getContext())},
410+
Bundles);
408411
if (Bundles.empty()) {
409412
// Otherwise, (landingpad model), create a conditional branch that leads
410413
// either to a cleanup block or a block with EH resume instruction.
@@ -755,7 +758,9 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
755758
// Emit coro.end before getReturnStmt (and parameter destructors), since
756759
// resume and destroy parts of the coroutine should not include them.
757760
llvm::Function *CoroEnd = CGM.getIntrinsic(llvm::Intrinsic::coro_end);
758-
Builder.CreateCall(CoroEnd, {NullPtr, Builder.getFalse()});
761+
Builder.CreateCall(CoroEnd,
762+
{NullPtr, Builder.getFalse(),
763+
llvm::ConstantTokenNone::get(CoroEnd->getContext())});
759764

760765
if (Stmt *Ret = S.getReturnStmt()) {
761766
// Since we already emitted the return value above, so we shouldn't
@@ -824,7 +829,11 @@ RValue CodeGenFunction::EmitCoroutineIntrinsic(const CallExpr *E,
824829
}
825830
for (const Expr *Arg : E->arguments())
826831
Args.push_back(EmitScalarExpr(Arg));
827-
832+
// @llvm.coro.end takes a token parameter. Add token 'none' as the last
833+
// argument.
834+
if (IID == llvm::Intrinsic::coro_end)
835+
Args.push_back(llvm::ConstantTokenNone::get(getLLVMContext()));
836+
828837
llvm::Function *F = CGM.getIntrinsic(IID);
829838
llvm::CallInst *Call = Builder.CreateCall(F, Args);
830839

clang/test/CodeGenCoroutines/coro-builtins.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ void f(int n) {
3737
// CHECK-NEXT: call ptr @llvm.coro.free(token %[[COROID]], ptr %[[FRAME]])
3838
__builtin_coro_free(__builtin_coro_frame());
3939

40-
// CHECK-NEXT: call i1 @llvm.coro.end(ptr %[[FRAME]], i1 false)
40+
// CHECK-NEXT: call i1 @llvm.coro.end(ptr %[[FRAME]], i1 false, token none)
4141
__builtin_coro_end(__builtin_coro_frame(), 0);
4242

4343
// CHECK-NEXT: call i8 @llvm.coro.suspend(token none, i1 true)

clang/test/CodeGenCoroutines/coro-eh-cleanup.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ coro_t f() {
6060

6161
// CHECK: [[COROENDBB]]:
6262
// CHECK-NEXT: %[[CLPAD:.+]] = cleanuppad within none
63-
// CHECK-NEXT: call i1 @llvm.coro.end(ptr null, i1 true) [ "funclet"(token %[[CLPAD]]) ]
63+
// CHECK-NEXT: call i1 @llvm.coro.end(ptr null, i1 true, token none) [ "funclet"(token %[[CLPAD]]) ]
6464
// CHECK-NEXT: cleanupret from %[[CLPAD]] unwind label
6565

6666
// CHECK-LPAD: @_Z1fv(
@@ -76,7 +76,7 @@ coro_t f() {
7676
// CHECK-LPAD: to label %{{.+}} unwind label %[[UNWINDBB:.+]]
7777

7878
// CHECK-LPAD: [[UNWINDBB]]:
79-
// CHECK-LPAD: %[[I1RESUME:.+]] = call i1 @llvm.coro.end(ptr null, i1 true)
79+
// CHECK-LPAD: %[[I1RESUME:.+]] = call i1 @llvm.coro.end(ptr null, i1 true, token none)
8080
// CHECK-LPAD: br i1 %[[I1RESUME]], label %[[EHRESUME:.+]], label
8181
// CHECK-LPAD: [[EHRESUME]]:
8282
// CHECK-LPAD-NEXT: %[[exn:.+]] = load ptr, ptr %exn.slot, align 8

llvm/docs/Coroutines.rst

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ lowerings:
151151
- In yield-once returned-continuation lowering, the coroutine must
152152
suspend itself exactly once (or throw an exception). The ramp
153153
function returns a continuation function pointer and yielded
154-
values, but the continuation function simply returns `void`
155-
when the coroutine has run to completion.
154+
values, the continuation function may optionally return ordinary
155+
results when the coroutine has run to completion.
156156

157157
The coroutine frame is maintained in a fixed-size buffer that is
158158
passed to the `coro.id` intrinsic, which guarantees a certain size
@@ -303,7 +303,7 @@ The LLVM IR for this coroutine looks like this:
303303
call void @free(ptr %mem)
304304
br label %suspend
305305
suspend:
306-
%unused = call i1 @llvm.coro.end(ptr %hdl, i1 false)
306+
%unused = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none)
307307
ret ptr %hdl
308308
}
309309
@@ -630,7 +630,7 @@ store the current value produced by a coroutine.
630630
call void @free(ptr %mem)
631631
br label %suspend
632632
suspend:
633-
%unused = call i1 @llvm.coro.end(ptr %hdl, i1 false)
633+
%unused = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none)
634634
ret ptr %hdl
635635
}
636636
@@ -1312,8 +1312,8 @@ Arguments:
13121312
""""""""""
13131313

13141314
As for ``llvm.core.id.retcon``, except that the return type of the
1315-
continuation prototype must be `void` instead of matching the
1316-
coroutine's return type.
1315+
continuation prototype must represent the normal return type of the continuation
1316+
(instead of matching the coroutine's return type).
13171317

13181318
Semantics:
13191319
""""""""""
@@ -1326,7 +1326,7 @@ A frontend should emit function attribute `presplitcoroutine` for the coroutine.
13261326
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
13271327
::
13281328

1329-
declare i1 @llvm.coro.end(ptr <handle>, i1 <unwind>)
1329+
declare i1 @llvm.coro.end(ptr <handle>, i1 <unwind>, token <result.token>)
13301330

13311331
Overview:
13321332
"""""""""
@@ -1347,6 +1347,12 @@ The second argument should be `true` if this coro.end is in the block that is
13471347
part of the unwind sequence leaving the coroutine body due to an exception and
13481348
`false` otherwise.
13491349

1350+
Non-trivial (non-none) token argument can only be specified for unique-suspend
1351+
returned-continuation coroutines where it must be a token value produced by
1352+
'``llvm.coro.end.results``' intrinsic.
1353+
1354+
Only none token is allowed for coro.end calls in unwind sections
1355+
13501356
Semantics:
13511357
""""""""""
13521358
The purpose of this intrinsic is to allow frontends to mark the cleanup and
@@ -1378,7 +1384,7 @@ For landingpad based exception model, it is expected that frontend uses the
13781384
.. code-block:: llvm
13791385
13801386
ehcleanup:
1381-
%InResumePart = call i1 @llvm.coro.end(ptr null, i1 true)
1387+
%InResumePart = call i1 @llvm.coro.end(ptr null, i1 true, token none)
13821388
br i1 %InResumePart, label %eh.resume, label %cleanup.cont
13831389
13841390
cleanup.cont:
@@ -1403,7 +1409,7 @@ referring to an enclosing cleanuppad as follows:
14031409
14041410
ehcleanup:
14051411
%tok = cleanuppad within none []
1406-
%unused = call i1 @llvm.coro.end(ptr null, i1 true) [ "funclet"(token %tok) ]
1412+
%unused = call i1 @llvm.coro.end(ptr null, i1 true, token none) [ "funclet"(token %tok) ]
14071413
cleanupret from %tok unwind label %RestOfTheCleanup
14081414
14091415
The `CoroSplit` pass, if the funclet bundle is present, will insert
@@ -1428,6 +1434,53 @@ The following table summarizes the handling of `coro.end`_ intrinsic.
14281434
| | Landingpad | mark coroutine as done | mark coroutine done |
14291435
+------------+-------------+------------------------+---------------------------------+
14301436

1437+
.. _coro.end.results:
1438+
1439+
'llvm.coro.end.results' Intrinsic
1440+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1441+
::
1442+
1443+
declare token @llvm.coro.end.results(...)
1444+
1445+
Overview:
1446+
"""""""""
1447+
1448+
The '``llvm.coro.end.results``' intrinsic captures values to be returned from
1449+
unique-suspend returned-continuation coroutines.
1450+
1451+
Arguments:
1452+
""""""""""
1453+
1454+
The number of arguments must match the return type of the continuation function:
1455+
1456+
- if the return type of the continuation function is ``void`` there must be no
1457+
arguments
1458+
1459+
- if the return type of the continuation function is a ``struct``, the arguments
1460+
will be of element types of that ``struct`` in order;
1461+
1462+
- otherwise, it is just the return value of the continuation function.
1463+
1464+
.. code-block:: llvm
1465+
1466+
define {ptr, ptr} @g(ptr %buffer, ptr %ptr, i8 %val) presplitcoroutine {
1467+
entry:
1468+
%id = call token @llvm.coro.id.retcon.once(i32 8, i32 8, ptr %buffer,
1469+
ptr @prototype,
1470+
ptr @allocate, ptr @deallocate)
1471+
%hdl = call ptr @llvm.coro.begin(token %id, ptr null)
1472+
1473+
...
1474+
1475+
cleanup:
1476+
%tok = call token (...) @llvm.coro.end.results(i8 %val)
1477+
call i1 @llvm.coro.end(ptr %hdl, i1 0, token %tok)
1478+
unreachable
1479+
1480+
...
1481+
1482+
declare i8 @prototype(ptr, i1 zeroext)
1483+
14311484
14321485
'llvm.coro.end.async' Intrinsic
14331486
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

llvm/include/llvm/IR/Intrinsics.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,8 @@ def int_coro_free : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty],
16431643
[IntrReadMem, IntrArgMemOnly,
16441644
ReadOnly<ArgIndex<1>>,
16451645
NoCapture<ArgIndex<1>>]>;
1646-
def int_coro_end : Intrinsic<[llvm_i1_ty], [llvm_ptr_ty, llvm_i1_ty], []>;
1646+
def int_coro_end : Intrinsic<[llvm_i1_ty], [llvm_ptr_ty, llvm_i1_ty, llvm_token_ty], []>;
1647+
def int_coro_end_results : Intrinsic<[llvm_token_ty], [llvm_vararg_ty]>;
16471648
def int_coro_end_async
16481649
: Intrinsic<[llvm_i1_ty], [llvm_ptr_ty, llvm_i1_ty, llvm_vararg_ty], []>;
16491650

llvm/lib/IR/AutoUpgrade.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,12 @@ static bool UpgradeIntrinsicFunction1(Function *F, Function *&NewFn) {
951951
F->arg_begin()->getType());
952952
return true;
953953
}
954+
if (Name.equals("coro.end") && F->arg_size() == 2) {
955+
rename(F);
956+
NewFn = Intrinsic::getDeclaration(F->getParent(), Intrinsic::coro_end);
957+
return true;
958+
}
959+
954960
break;
955961
}
956962
case 'd':
@@ -4207,6 +4213,13 @@ void llvm::UpgradeIntrinsicCall(CallBase *CI, Function *NewFn) {
42074213
break;
42084214
}
42094215

4216+
case Intrinsic::coro_end: {
4217+
SmallVector<Value *, 3> Args(CI->args());
4218+
Args.push_back(ConstantTokenNone::get(CI->getContext()));
4219+
NewCall = Builder.CreateCall(NewFn, Args);
4220+
break;
4221+
}
4222+
42104223
case Intrinsic::vector_extract: {
42114224
StringRef Name = F->getName();
42124225
Name = Name.substr(5); // Strip llvm

llvm/lib/Transforms/Coroutines/CoroInstr.h

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,15 +611,53 @@ class LLVM_LIBRARY_VISIBILITY CoroAlignInst : public IntrinsicInst {
611611
}
612612
};
613613

614+
/// This represents the llvm.end.results instruction.
615+
class LLVM_LIBRARY_VISIBILITY CoroEndResults : public IntrinsicInst {
616+
public:
617+
op_iterator retval_begin() { return arg_begin(); }
618+
const_op_iterator retval_begin() const { return arg_begin(); }
619+
620+
op_iterator retval_end() { return arg_end(); }
621+
const_op_iterator retval_end() const { return arg_end(); }
622+
623+
iterator_range<op_iterator> return_values() {
624+
return make_range(retval_begin(), retval_end());
625+
}
626+
iterator_range<const_op_iterator> return_values() const {
627+
return make_range(retval_begin(), retval_end());
628+
}
629+
630+
unsigned numReturns() const {
631+
return std::distance(retval_begin(), retval_end());
632+
}
633+
634+
// Methods to support type inquiry through isa, cast, and dyn_cast:
635+
static bool classof(const IntrinsicInst *I) {
636+
return I->getIntrinsicID() == Intrinsic::coro_end_results;
637+
}
638+
static bool classof(const Value *V) {
639+
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
640+
}
641+
};
642+
614643
class LLVM_LIBRARY_VISIBILITY AnyCoroEndInst : public IntrinsicInst {
615-
enum { FrameArg, UnwindArg };
644+
enum { FrameArg, UnwindArg, TokenArg };
616645

617646
public:
618647
bool isFallthrough() const { return !isUnwind(); }
619648
bool isUnwind() const {
620649
return cast<Constant>(getArgOperand(UnwindArg))->isOneValue();
621650
}
622651

652+
bool hasResults() const {
653+
return !isa<ConstantTokenNone>(getArgOperand(TokenArg));
654+
}
655+
656+
CoroEndResults *getResults() const {
657+
assert(hasResults());
658+
return cast<CoroEndResults>(getArgOperand(TokenArg));
659+
}
660+
623661
// Methods to support type inquiry through isa, cast, and dyn_cast:
624662
static bool classof(const IntrinsicInst *I) {
625663
auto ID = I->getIntrinsicID();

llvm/lib/Transforms/Coroutines/CoroSplit.cpp

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ static void replaceFallthroughCoroEnd(AnyCoroEndInst *End,
234234
switch (Shape.ABI) {
235235
// The cloned functions in switch-lowering always return void.
236236
case coro::ABI::Switch:
237+
assert(!cast<CoroEndInst>(End)->hasResults() &&
238+
"switch coroutine should not return any values");
237239
// coro.end doesn't immediately end the coroutine in the main function
238240
// in this lowering, because we need to deallocate the coroutine.
239241
if (!InResume)
@@ -251,14 +253,45 @@ static void replaceFallthroughCoroEnd(AnyCoroEndInst *End,
251253

252254
// In unique continuation lowering, the continuations always return void.
253255
// But we may have implicitly allocated storage.
254-
case coro::ABI::RetconOnce:
256+
case coro::ABI::RetconOnce: {
255257
maybeFreeRetconStorage(Builder, Shape, FramePtr, CG);
256-
Builder.CreateRetVoid();
258+
auto *CoroEnd = cast<CoroEndInst>(End);
259+
auto *RetTy = Shape.getResumeFunctionType()->getReturnType();
260+
261+
if (!CoroEnd->hasResults()) {
262+
assert(RetTy->isVoidTy());
263+
Builder.CreateRetVoid();
264+
break;
265+
}
266+
267+
auto *CoroResults = CoroEnd->getResults();
268+
unsigned NumReturns = CoroResults->numReturns();
269+
270+
if (auto *RetStructTy = dyn_cast<StructType>(RetTy)) {
271+
assert(RetStructTy->getNumElements() == NumReturns &&
272+
"numbers of returns should match resume function singature");
273+
Value *ReturnValue = UndefValue::get(RetStructTy);
274+
unsigned Idx = 0;
275+
for (Value *RetValEl : CoroResults->return_values())
276+
ReturnValue = Builder.CreateInsertValue(ReturnValue, RetValEl, Idx++);
277+
Builder.CreateRet(ReturnValue);
278+
} else if (NumReturns == 0) {
279+
assert(RetTy->isVoidTy());
280+
Builder.CreateRetVoid();
281+
} else {
282+
assert(NumReturns == 1);
283+
Builder.CreateRet(*CoroResults->retval_begin());
284+
}
285+
CoroResults->replaceAllUsesWith(ConstantTokenNone::get(CoroResults->getContext()));
286+
CoroResults->eraseFromParent();
257287
break;
288+
}
258289

259290
// In non-unique continuation lowering, we signal completion by returning
260291
// a null continuation.
261292
case coro::ABI::Retcon: {
293+
assert(!cast<CoroEndInst>(End)->hasResults() &&
294+
"retcon coroutine should not return any values");
262295
maybeFreeRetconStorage(Builder, Shape, FramePtr, CG);
263296
auto RetTy = Shape.getResumeFunctionType()->getReturnType();
264297
auto RetStructTy = dyn_cast<StructType>(RetTy);

llvm/test/Assembler/auto_upgrade_intrinsics.ll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ entry:
4747
ret void
4848
}
4949

50+
declare i1 @llvm.coro.end(ptr, i1)
51+
define void @test.coro.end(ptr %ptr) {
52+
; CHECK-LABEL: @test.coro.end(
53+
; CHECK: call i1 @llvm.coro.end(ptr %ptr, i1 false, token none)
54+
call i1 @llvm.coro.end(ptr %ptr, i1 false)
55+
ret void
56+
}
5057

5158
@a = private global [60 x i8] zeroinitializer, align 1
5259

llvm/test/Transforms/Coroutines/ArgAddr.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ coro_Cleanup:
4545
br label %coro_Suspend
4646

4747
coro_Suspend:
48-
call i1 @llvm.coro.end(ptr null, i1 false)
48+
call i1 @llvm.coro.end(ptr null, i1 false, token none)
4949
ret ptr %1
5050
}
5151

@@ -69,7 +69,7 @@ declare i32 @llvm.coro.size.i32()
6969
declare ptr @llvm.coro.begin(token, ptr)
7070
declare i8 @llvm.coro.suspend(token, i1)
7171
declare ptr @llvm.coro.free(token, ptr)
72-
declare i1 @llvm.coro.end(ptr, i1)
72+
declare i1 @llvm.coro.end(ptr, i1, token)
7373

7474
declare void @llvm.coro.resume(ptr)
7575
declare void @llvm.coro.destroy(ptr)

llvm/test/Transforms/Coroutines/coro-align16.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ cleanup:
2424
br label %suspend
2525

2626
suspend:
27-
call i1 @llvm.coro.end(ptr %hdl, i1 0)
27+
call i1 @llvm.coro.end(ptr %hdl, i1 0, token none)
2828
ret ptr %hdl
2929
}
3030

@@ -44,7 +44,7 @@ declare void @llvm.coro.destroy(ptr)
4444
declare token @llvm.coro.id(i32, ptr, ptr, ptr)
4545
declare i1 @llvm.coro.alloc(token)
4646
declare ptr @llvm.coro.begin(token, ptr)
47-
declare i1 @llvm.coro.end(ptr, i1)
47+
declare i1 @llvm.coro.end(ptr, i1, token)
4848

4949
declare void @capture_call(ptr)
5050
declare void @nocapture_call(ptr nocapture)

0 commit comments

Comments
 (0)