Skip to content

Commit 012b976

Browse files
committed
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 0852121 commit 012b976

File tree

127 files changed

+741
-313
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

+741
-313
lines changed

clang/lib/CodeGen/CGCoroutine.cpp

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

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

clang/test/CodeGenCoroutines/coro-builtins.c

+1-1
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

+2-2
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

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

158158
The coroutine frame is maintained in a fixed-size buffer that is
159159
passed to the `coro.id` intrinsic, which guarantees a certain size
@@ -304,7 +304,7 @@ The LLVM IR for this coroutine looks like this:
304304
call void @free(ptr %mem)
305305
br label %suspend
306306
suspend:
307-
%unused = call i1 @llvm.coro.end(ptr %hdl, i1 false)
307+
%unused = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none)
308308
ret ptr %hdl
309309
}
310310
@@ -631,7 +631,7 @@ store the current value produced by a coroutine.
631631
call void @free(ptr %mem)
632632
br label %suspend
633633
suspend:
634-
%unused = call i1 @llvm.coro.end(ptr %hdl, i1 false)
634+
%unused = call i1 @llvm.coro.end(ptr %hdl, i1 false, token none)
635635
ret ptr %hdl
636636
}
637637
@@ -1313,8 +1313,8 @@ Arguments:
13131313
""""""""""
13141314

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

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

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

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

1351+
Non-trivial (non-none) token argument can only be specified for unique-suspend
1352+
returned-continuation coroutines where it must be a token value produced by
1353+
'``llvm.coro.end.results``' intrinsic.
1354+
1355+
Only none token is allowed for coro.end calls in unwind sections
1356+
13511357
Semantics:
13521358
""""""""""
13531359
The purpose of this intrinsic is to allow frontends to mark the cleanup and
@@ -1379,7 +1385,7 @@ For landingpad based exception model, it is expected that frontend uses the
13791385
.. code-block:: llvm
13801386
13811387
ehcleanup:
1382-
%InResumePart = call i1 @llvm.coro.end(ptr null, i1 true)
1388+
%InResumePart = call i1 @llvm.coro.end(ptr null, i1 true, token none)
13831389
br i1 %InResumePart, label %eh.resume, label %cleanup.cont
13841390
13851391
cleanup.cont:
@@ -1404,7 +1410,7 @@ referring to an enclosing cleanuppad as follows:
14041410
14051411
ehcleanup:
14061412
%tok = cleanuppad within none []
1407-
%unused = call i1 @llvm.coro.end(ptr null, i1 true) [ "funclet"(token %tok) ]
1413+
%unused = call i1 @llvm.coro.end(ptr null, i1 true, token none) [ "funclet"(token %tok) ]
14081414
cleanupret from %tok unwind label %RestOfTheCleanup
14091415
14101416
The `CoroSplit` pass, if the funclet bundle is present, will insert
@@ -1429,6 +1435,53 @@ The following table summarizes the handling of `coro.end`_ intrinsic.
14291435
| | Landingpad | mark coroutine as done | mark coroutine done |
14301436
+------------+-------------+------------------------+---------------------------------+
14311437

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

llvm/include/llvm/IR/Intrinsics.td

+2-1
Original file line numberDiff line numberDiff line change
@@ -1631,7 +1631,8 @@ def int_coro_free : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty],
16311631
[IntrReadMem, IntrArgMemOnly,
16321632
ReadOnly<ArgIndex<1>>,
16331633
NoCapture<ArgIndex<1>>]>;
1634-
def int_coro_end : Intrinsic<[llvm_i1_ty], [llvm_ptr_ty, llvm_i1_ty], []>;
1634+
def int_coro_end : Intrinsic<[llvm_i1_ty], [llvm_ptr_ty, llvm_i1_ty, llvm_token_ty], []>;
1635+
def int_coro_end_results : Intrinsic<[llvm_token_ty], [llvm_vararg_ty]>;
16351636
def int_coro_end_async
16361637
: Intrinsic<[llvm_i1_ty], [llvm_ptr_ty, llvm_i1_ty, llvm_vararg_ty], []>;
16371638

llvm/lib/IR/AutoUpgrade.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,12 @@ static bool UpgradeIntrinsicFunction1(Function *F, Function *&NewFn) {
929929
F->arg_begin()->getType());
930930
return true;
931931
}
932+
if (Name.equals("coro.end") && F->arg_size() == 2) {
933+
rename(F);
934+
NewFn = Intrinsic::getDeclaration(F->getParent(), Intrinsic::coro_end);
935+
return true;
936+
}
937+
932938
break;
933939
}
934940
case 'd': {
@@ -4307,6 +4313,13 @@ void llvm::UpgradeIntrinsicCall(CallBase *CI, Function *NewFn) {
43074313
break;
43084314
}
43094315

4316+
case Intrinsic::coro_end: {
4317+
SmallVector<Value *, 3> Args(CI->args());
4318+
Args.push_back(ConstantTokenNone::get(CI->getContext()));
4319+
NewCall = Builder.CreateCall(NewFn, Args);
4320+
break;
4321+
}
4322+
43104323
case Intrinsic::vector_extract: {
43114324
StringRef Name = F->getName();
43124325
Name = Name.substr(5); // Strip llvm

llvm/lib/Transforms/Coroutines/CoroInstr.h

+39-1
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

+35-2
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

+7
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

+2-2
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

+2-2
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)