Skip to content

Commit 286aaa0

Browse files
Merge pull request #2101 from aschwaighofer/rdar_70097093_async_lowering
Cherry-pick of llvm.coro async lowering
2 parents 6042c0f + ef6a781 commit 286aaa0

File tree

11 files changed

+989
-52
lines changed

11 files changed

+989
-52
lines changed

llvm/docs/Coroutines.rst

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,88 @@ This may be acceptable if LLVM's coroutine support is primarily being
174174
used for low-level lowering and inlining is expected to be applied
175175
earlier in the pipeline.
176176

177+
Async Lowering
178+
--------------
179+
180+
In async-continuation lowering, signaled by the use of `llvm.coro.id.async`,
181+
handling of control-flow must be handled explicitly by the frontend.
182+
183+
In this lowering, a coroutine is assumed to take the current `async context` as
184+
one of its arguments (the argument position is determined by
185+
`llvm.coro.id.async`). It is used to marshal arguments and return values of the
186+
coroutine. Therefore an async coroutine returns `void`.
187+
188+
.. code-block:: llvm
189+
190+
define swiftcc void @async_coroutine(i8* %async.ctxt, i8*, i8*) {
191+
}
192+
193+
Values live accross a suspend point need to be stored in the coroutine frame to
194+
be available in the continuation function. This frame is stored as a tail to the
195+
`async context`.
196+
197+
Every suspend point takes an `context projection function` argument which
198+
describes how-to obtain the continuations `async context` and every suspend
199+
point has an associated `resume function` denoted by the
200+
`llvm.coro.async.resume` intrinsic. The coroutine is resumed by calling this
201+
`resume function` passing the `async context` as the one of its arguments
202+
argument. The `resume function` can restore its (the caller's) `async context`
203+
by applying a `context projection function` that is provided by the frontend as
204+
a parameter to the `llvm.coro.suspend.async` intrinsic.
205+
206+
.. code-block:: c
207+
208+
// For example:
209+
struct async_context {
210+
struct async_context *caller_context;
211+
...
212+
}
213+
214+
char *context_projection_function(struct async_context *callee_ctxt) {
215+
return callee_ctxt->caller_context;
216+
}
217+
218+
.. code-block:: llvm
219+
220+
%resume_func_ptr = call i8* @llvm.coro.async.resume()
221+
call {i8*, i8*, i8*} (i8*, i8*, ...) @llvm.coro.suspend.async(
222+
i8* %resume_func_ptr,
223+
i8* %context_projection_function
224+
225+
The frontend should provide a `async function pointer` struct associated with
226+
each async coroutine by `llvm.coro.id.async`'s argument. The initial size and
227+
alignment of the `async context` must be provided as arguments to the
228+
`llvm.coro.id.async` intrinsic. Lowering will update the size entry with the
229+
coroutine frame requirements. The frontend is responsible for allocating the
230+
memory for the `async context` but can use the `async function pointer` struct
231+
to obtain the required size.
232+
233+
.. code-block:: c
234+
235+
struct async_function_pointer {
236+
uint32_t relative_function_pointer_to_async_impl;
237+
uint32_t context_size;
238+
}
239+
240+
Lowering will split an async coroutine into a ramp function and one resume
241+
function per suspend point.
242+
243+
How control-flow is passed between caller, suspension point, and back to
244+
resume function is left up to the frontend.
245+
246+
The suspend point takes a function and its arguments. The function is intended
247+
to model the transfer to the callee function. It will be tail called by
248+
lowering and therefore must have the same signature and calling convention as
249+
the async coroutine.
250+
251+
.. code-block:: llvm
252+
253+
call {i8*, i8*, i8*} (i8*, i8*, ...) @llvm.coro.suspend.async(
254+
i8* %resume_func_ptr,
255+
i8* %context_projection_function,
256+
i8* (bitcast void (i8*, i8*, i8*)* to i8*) %suspend_function,
257+
i8* %arg1, i8* %arg2, i8 %arg3)
258+
177259
Coroutines by Example
178260
=====================
179261

@@ -1093,6 +1175,45 @@ duplicated.
10931175

10941176
A frontend should emit exactly one `coro.id` intrinsic per coroutine.
10951177

1178+
.. _coro.id.async:
1179+
1180+
'llvm.coro.id.async' Intrinsic
1181+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1182+
::
1183+
1184+
declare token @llvm.coro.id.async(i32 <context size>, i32 <align>,
1185+
i8* <context arg>,
1186+
i8* <async function pointer>)
1187+
1188+
Overview:
1189+
"""""""""
1190+
1191+
The '``llvm.coro.id.async``' intrinsic returns a token identifying an async coroutine.
1192+
1193+
Arguments:
1194+
""""""""""
1195+
1196+
The first argument provides the initial size of the `async context` as required
1197+
from the frontend. Lowering will add to this size the size required by the frame
1198+
storage and store that value to the `async function pointer`.
1199+
1200+
The second argument, is the alignment guarantee of the memory of the
1201+
`async context`. The frontend guarantees that the memory will be aligned by this
1202+
value.
1203+
1204+
The third argument is the `async context` argument in the current coroutine.
1205+
1206+
The fourth argument is the address of the `async function pointer` struct.
1207+
Lowering will update the context size requirement in this struct by adding the
1208+
coroutine frame size requirement to the initial size requirement as specified by
1209+
the first argument of this intrinisc.
1210+
1211+
1212+
Semantics:
1213+
""""""""""
1214+
1215+
A frontend should emit exactly one `coro.id.async` intrinsic per coroutine.
1216+
10961217
.. _coro.id.retcon:
10971218

10981219
'llvm.coro.id.retcon' Intrinsic
@@ -1380,6 +1501,68 @@ to the coroutine:
13801501
switch i8 %suspend1, label %suspend [i8 0, label %resume1
13811502
i8 1, label %cleanup]
13821503
1504+
.. _coro.suspend.async:
1505+
1506+
'llvm.coro.suspend.async' Intrinsic
1507+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1508+
::
1509+
1510+
declare {i8*, i8*, i8*} @llvm.coro.suspend.async(
1511+
i8* <resume function>,
1512+
i8* <context projection function>,
1513+
... <function to call>
1514+
... <arguments to function>)
1515+
1516+
Overview:
1517+
"""""""""
1518+
1519+
The '``llvm.coro.suspend.async``' intrinsic marks the point where
1520+
execution of a async coroutine is suspended and control is passed to a callee.
1521+
1522+
Arguments:
1523+
""""""""""
1524+
1525+
The first argument should be the result of the `llvm.coro.async.resume` intrinsic.
1526+
Lowering will replace this intrinsic with the resume function for this suspend
1527+
point.
1528+
1529+
The second argument is the `context projection function`. It should describe
1530+
how-to restore the `async context` in the continuation function from the first
1531+
argument of the continuation function. Its type is `i8* (i8*)`.
1532+
1533+
The third argument is the function that models tranfer to the callee at the
1534+
suspend point. It should take 3 arguments. Lowering will `musttail` call this
1535+
function.
1536+
1537+
The fourth to six argument are the arguments for the third argument.
1538+
1539+
Semantics:
1540+
""""""""""
1541+
1542+
The result of the intrinsic are mapped to the arguments of the resume function.
1543+
Execution is suspended at this intrinsic and resumed when the resume function is
1544+
called.
1545+
1546+
.. _coro.prepare.async:
1547+
1548+
'llvm.coro.prepare.async' Intrinsic
1549+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1550+
::
1551+
1552+
declare i8* @llvm.coro.prepare.async(i8* <coroutine function>)
1553+
1554+
Overview:
1555+
"""""""""
1556+
1557+
The '``llvm.coro.prepare.async``' intrinsic is used to block inlining of the
1558+
async coroutine until after coroutine splitting.
1559+
1560+
Arguments:
1561+
""""""""""
1562+
1563+
The first argument should be an async coroutine of type `void (i8*, i8*, i8*)`.
1564+
Lowering will replace this intrinsic with its coroutine function argument.
1565+
13831566
.. _coro.suspend.retcon:
13841567

13851568
'llvm.coro.suspend.retcon' Intrinsic

llvm/include/llvm/IR/Intrinsics.td

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,23 @@ def int_coro_id_retcon_once : Intrinsic<[llvm_token_ty],
11391139
llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty],
11401140
[]>;
11411141
def int_coro_alloc : Intrinsic<[llvm_i1_ty], [llvm_token_ty], []>;
1142+
def int_coro_id_async : Intrinsic<[llvm_token_ty],
1143+
[llvm_i32_ty, llvm_i32_ty, llvm_ptr_ty, llvm_ptr_ty],
1144+
[]>;
1145+
def int_coro_async_context_alloc : Intrinsic<[llvm_ptr_ty],
1146+
[llvm_ptr_ty, llvm_ptr_ty],
1147+
[]>;
1148+
def int_coro_async_context_dealloc : Intrinsic<[],
1149+
[llvm_ptr_ty],
1150+
[]>;
1151+
def int_coro_async_resume : Intrinsic<[llvm_ptr_ty],
1152+
[],
1153+
[]>;
1154+
def int_coro_suspend_async : Intrinsic<[llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty],
1155+
[llvm_ptr_ty, llvm_ptr_ty, llvm_vararg_ty],
1156+
[]>;
1157+
def int_coro_prepare_async : Intrinsic<[llvm_ptr_ty], [llvm_ptr_ty],
1158+
[IntrNoMem]>;
11421159
def int_coro_begin : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty],
11431160
[WriteOnly<ArgIndex<1>>]>;
11441161

llvm/lib/Transforms/Coroutines/CoroCleanup.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ bool Lowerer::lowerRemainingCoroIntrinsics(Function &F) {
7474
case Intrinsic::coro_id:
7575
case Intrinsic::coro_id_retcon:
7676
case Intrinsic::coro_id_retcon_once:
77+
case Intrinsic::coro_id_async:
7778
II->replaceAllUsesWith(ConstantTokenNone::get(Context));
7879
break;
7980
case Intrinsic::coro_subfn_addr:

llvm/lib/Transforms/Coroutines/CoroEarly.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ bool Lowerer::lowerEarlyIntrinsics(Function &F) {
187187
break;
188188
case Intrinsic::coro_id_retcon:
189189
case Intrinsic::coro_id_retcon_once:
190+
case Intrinsic::coro_id_async:
190191
F.addFnAttr(CORO_PRESPLIT_ATTR, PREPARED_FOR_SPLIT);
191192
break;
192193
case Intrinsic::coro_resume:

llvm/lib/Transforms/Coroutines/CoroElide.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ static bool replaceDevirtTrigger(Function &F) {
366366
}
367367

368368
static bool declaresCoroElideIntrinsics(Module &M) {
369-
return coro::declaresIntrinsics(M, {"llvm.coro.id"});
369+
return coro::declaresIntrinsics(M, {"llvm.coro.id", "llvm.coro.id.async"});
370370
}
371371

372372
PreservedAnalyses CoroElidePass::run(Function &F, FunctionAnalysisManager &AM) {

llvm/lib/Transforms/Coroutines/CoroFrame.cpp

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,10 @@ struct SuspendCrossingInfo {
126126

127127
BasicBlock *UseBB = I->getParent();
128128

129-
// As a special case, treat uses by an llvm.coro.suspend.retcon
130-
// as if they were uses in the suspend's single predecessor: the
131-
// uses conceptually occur before the suspend.
132-
if (isa<CoroSuspendRetconInst>(I)) {
129+
// As a special case, treat uses by an llvm.coro.suspend.retcon or an
130+
// llvm.coro.suspend.async as if they were uses in the suspend's single
131+
// predecessor: the uses conceptually occur before the suspend.
132+
if (isa<CoroSuspendRetconInst>(I) || isa<CoroSuspendAsyncInst>(I)) {
133133
UseBB = UseBB->getSinglePredecessor();
134134
assert(UseBB && "should have split coro.suspend into its own block");
135135
}
@@ -617,6 +617,18 @@ static StructType *buildFrameType(Function &F, coro::Shape &Shape,
617617
B.getStructAlign() <= Id->getStorageAlignment());
618618
break;
619619
}
620+
case coro::ABI::Async: {
621+
Shape.AsyncLowering.FrameOffset =
622+
alignTo(Shape.AsyncLowering.ContextHeaderSize, Shape.FrameAlign);
623+
Shape.AsyncLowering.ContextSize =
624+
Shape.AsyncLowering.FrameOffset + Shape.FrameSize;
625+
if (Shape.AsyncLowering.getContextAlignment() < Shape.FrameAlign) {
626+
report_fatal_error(
627+
"The alignment requirment of frame variables cannot be higher than "
628+
"the alignment of the async function context");
629+
}
630+
break;
631+
}
620632
}
621633

622634
return FrameTy;
@@ -901,7 +913,8 @@ static Instruction *insertSpills(const SpillInfo &Spills, coro::Shape &Shape) {
901913
Shape.AllocaSpillBlock = SpillBlock;
902914

903915
// retcon and retcon.once lowering assumes all uses have been sunk.
904-
if (Shape.ABI == coro::ABI::Retcon || Shape.ABI == coro::ABI::RetconOnce) {
916+
if (Shape.ABI == coro::ABI::Retcon || Shape.ABI == coro::ABI::RetconOnce ||
917+
Shape.ABI == coro::ABI::Async) {
905918
// If we found any allocas, replace all of their remaining uses with Geps.
906919
Builder.SetInsertPoint(&SpillBlock->front());
907920
for (auto &P : Allocas) {
@@ -1514,7 +1527,8 @@ static void sinkSpillUsesAfterCoroBegin(Function &F, const SpillInfo &Spills,
15141527
for (User *U : SpillDef->users()) {
15151528
auto Inst = cast<Instruction>(U);
15161529
if (Inst->getParent() != CoroBegin->getParent() ||
1517-
Dom.dominates(CoroBegin, Inst))
1530+
Dom.dominates(CoroBegin, Inst) ||
1531+
isa<CoroIdAsyncInst>(Inst) /*'fake' use of async context argument*/)
15181532
continue;
15191533
if (ToMove.insert(Inst))
15201534
Worklist.push_back(Inst);
@@ -1754,7 +1768,8 @@ void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
17541768
}
17551769
}
17561770
LLVM_DEBUG(dump("Spills", Spills));
1757-
if (Shape.ABI == coro::ABI::Retcon || Shape.ABI == coro::ABI::RetconOnce)
1771+
if (Shape.ABI == coro::ABI::Retcon || Shape.ABI == coro::ABI::RetconOnce ||
1772+
Shape.ABI == coro::ABI::Async)
17581773
sinkSpillUsesAfterCoroBegin(F, Spills, Shape.CoroBegin);
17591774
Shape.FrameTy = buildFrameType(F, Shape, Spills);
17601775
Shape.FramePtr = insertSpills(Spills, Shape);

0 commit comments

Comments
 (0)