Skip to content

Commit ea606cc

Browse files
committed
Start of an llvm.coro.async implementation
This patch adds the `async` lowering of coroutines. This will be used by the Swift frontend to lower async functions. In contrast to the `retcon` lowering the frontend needs to be in control over control-flow at suspend points as execution might be suspended at these points. This is very much work in progress and the implementation will change as it evolves with the frontend. As such the documentation is lacking detail as some of it might change. rdar://70097093 Differential Revision: https://reviews.llvm.org/D90612
1 parent bf027da commit ea606cc

File tree

10 files changed

+815
-32
lines changed

10 files changed

+815
-32
lines changed

llvm/docs/Coroutines.rst

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,61 @@ 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+
its first argument. It is used to marshal arguments and return values of the
185+
coroutine. Therefore a async coroutine returns `void`.
186+
187+
.. code-block:: llvm
188+
define swiftcc void @async_coroutine(i8* %async.ctxt, i8*, i8*) {
189+
}
190+
191+
192+
Every suspend point takes an `async context` argument which provides the context
193+
and the coroutine frame of the callee function. Every
194+
suspend point has an associated `resume function` denoted by the
195+
`llvm.coro.async.resume` intrinsic. The coroutine is resumed by
196+
calling this `resume function` passing the `async context` as the first
197+
argument. It is assumed that the `resume function` can restore its (the
198+
caller's) `async context` by loading the first field in the `async context`.
199+
200+
.. code-block:: c
201+
202+
struct async_context {
203+
struct async_context *caller_context;
204+
...
205+
}
206+
207+
The frontend should provide a `async function pointer` struct associated with
208+
each async coroutine by `llvm.coro.id.async`'s argument. The initial size and
209+
alignment of the `async context` must be provided as arguments to the
210+
`llvm.coro.id.async` intrinsic. Lowering will update the size entry with the
211+
coroutine frame requirements. The frontend is responsible for allocating the
212+
memory for the `async context` but can use the `async function pointer` struct
213+
to obtain the required size.
214+
215+
.. code-block:: c
216+
struct async_function_pointer {
217+
uint32_t context_size;
218+
uint32_t relative_function_pointer_to_async_impl;
219+
}
220+
221+
Lowering will split an async coroutine into a ramp function and one resume
222+
function per suspend point.
223+
224+
How control-flow is passed between caller, suspension point, and back to
225+
resume function is left up to the frontend.
226+
227+
The suspend point takes a function and its arguments. The function is intended
228+
to model the transfer to the callee function. It will be tail called by
229+
lowering and therefore must have the same signature and calling convention as
230+
the async coroutine.
231+
177232
Coroutines by Example
178233
=====================
179234

@@ -1093,6 +1148,45 @@ duplicated.
10931148

10941149
A frontend should emit exactly one `coro.id` intrinsic per coroutine.
10951150

1151+
.. _coro.id.async:
1152+
1153+
'llvm.coro.id.async' Intrinsic
1154+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1155+
::
1156+
1157+
declare token @llvm.coro.id.async(i32 <context size>, i32 <align>,
1158+
i8* <context arg>,
1159+
i8* <async function pointer>)
1160+
1161+
Overview:
1162+
"""""""""
1163+
1164+
The '``llvm.coro.id.async``' intrinsic returns a token identifying an async coroutine.
1165+
1166+
Arguments:
1167+
""""""""""
1168+
1169+
The first argument provides the initial size of the `async context` as required
1170+
from the frontend. Lowering will add to this size the size required by the frame
1171+
storage and store that value to the `async function pointer`.
1172+
1173+
The second argument, is the alignment guarantee of the memory of the
1174+
`async context`. The frontend guarantees that the memory will be aligned by this
1175+
value.
1176+
1177+
The third argument is the `async context` argument in the current coroutine.
1178+
1179+
The fourth argument is the address of the `async function pointer` struct.
1180+
Lowering will update the context size requirement in this struct by adding the
1181+
coroutine frame size requirement to the initial size requirement as specified by
1182+
the first argument of this intrinisc.
1183+
1184+
1185+
Semantics:
1186+
""""""""""
1187+
1188+
A frontend should emit exactly one `coro.id.async` intrinsic per coroutine.
1189+
10961190
.. _coro.id.retcon:
10971191

10981192
'llvm.coro.id.retcon' Intrinsic
@@ -1380,6 +1474,46 @@ to the coroutine:
13801474
switch i8 %suspend1, label %suspend [i8 0, label %resume1
13811475
i8 1, label %cleanup]
13821476
1477+
.. _coro.suspend.async:
1478+
1479+
'llvm.coro.suspend.async' Intrinsic
1480+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1481+
::
1482+
1483+
declare {i8*, i8*, i8*} @llvm.coro.suspend.async(i8* <resume function>,
1484+
i8* <callee context>,
1485+
... <function to call>
1486+
... <arguments to function>)
1487+
1488+
Overview:
1489+
"""""""""
1490+
1491+
The '``llvm.coro.suspend.async``' intrinsic marks the point where
1492+
execution of a async coroutine is suspended and control is passed to a callee.
1493+
1494+
Arguments:
1495+
""""""""""
1496+
1497+
The first argument should be the result of the `llvm.coro.async.resume` intrinsic.
1498+
Lowering will replace this intrinsic with the resume function for this suspend
1499+
point.
1500+
1501+
The second argument is the `async context` allocation for the callee. It should
1502+
provide storage the `async context` header and the coroutine frame.
1503+
1504+
The third argument is the function that models tranfer to the callee at the
1505+
suspend point. It should take 3 arguments. Lowering will `musttail` call this
1506+
function.
1507+
1508+
The fourth to six argument are the arguments for the third argument.
1509+
1510+
Semantics:
1511+
""""""""""
1512+
1513+
The result of the intrinsic are mapped to the arguments of the resume function.
1514+
Execution is suspended at this intrinsic and resumed when the resume function is
1515+
called.
1516+
13831517
.. _coro.suspend.retcon:
13841518

13851519
'llvm.coro.suspend.retcon' Intrinsic

llvm/include/llvm/IR/Intrinsics.td

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,21 @@ def int_coro_id_retcon_once : Intrinsic<[llvm_token_ty],
11871187
llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty],
11881188
[]>;
11891189
def int_coro_alloc : Intrinsic<[llvm_i1_ty], [llvm_token_ty], []>;
1190+
def int_coro_id_async : Intrinsic<[llvm_token_ty],
1191+
[llvm_i32_ty, llvm_i32_ty, llvm_ptr_ty, llvm_ptr_ty],
1192+
[]>;
1193+
def int_coro_async_context_alloc : Intrinsic<[llvm_ptr_ty],
1194+
[llvm_ptr_ty, llvm_ptr_ty],
1195+
[]>;
1196+
def int_coro_async_context_dealloc : Intrinsic<[],
1197+
[llvm_ptr_ty],
1198+
[]>;
1199+
def int_coro_async_resume : Intrinsic<[llvm_ptr_ty],
1200+
[],
1201+
[]>;
1202+
def int_coro_suspend_async : Intrinsic<[llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty],
1203+
[llvm_ptr_ty, llvm_ptr_ty, llvm_vararg_ty],
1204+
[]>;
11901205
def int_coro_begin : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty],
11911206
[WriteOnly<ArgIndex<1>>]>;
11921207

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/CoroFrame.cpp

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

136136
BasicBlock *UseBB = I->getParent();
137137

138-
// As a special case, treat uses by an llvm.coro.suspend.retcon
139-
// as if they were uses in the suspend's single predecessor: the
140-
// uses conceptually occur before the suspend.
141-
if (isa<CoroSuspendRetconInst>(I)) {
138+
// As a special case, treat uses by an llvm.coro.suspend.retcon or an
139+
// llvm.coro.suspend.async as if they were uses in the suspend's single
140+
// predecessor: the uses conceptually occur before the suspend.
141+
if (isa<CoroSuspendRetconInst>(I) || isa<CoroSuspendAsyncInst>(I)) {
142142
UseBB = UseBB->getSinglePredecessor();
143143
assert(UseBB && "should have split coro.suspend into its own block");
144144
}
@@ -788,6 +788,18 @@ static StructType *buildFrameType(Function &F, coro::Shape &Shape,
788788
B.getStructAlign() <= Id->getStorageAlignment());
789789
break;
790790
}
791+
case coro::ABI::Async: {
792+
Shape.AsyncLowering.FrameOffset =
793+
alignTo(Shape.AsyncLowering.ContextHeaderSize, Shape.FrameAlign);
794+
Shape.AsyncLowering.ContextSize =
795+
Shape.AsyncLowering.FrameOffset + Shape.FrameSize;
796+
if (Shape.AsyncLowering.getContextAlignment() < Shape.FrameAlign) {
797+
report_fatal_error(
798+
"The alignment requirment of frame variables cannot be higher than "
799+
"the alignment of the async function context");
800+
}
801+
break;
802+
}
791803
}
792804

793805
return FrameTy;
@@ -1143,7 +1155,8 @@ static Instruction *insertSpills(const FrameDataInfo &FrameData,
11431155
Shape.AllocaSpillBlock = SpillBlock;
11441156

11451157
// retcon and retcon.once lowering assumes all uses have been sunk.
1146-
if (Shape.ABI == coro::ABI::Retcon || Shape.ABI == coro::ABI::RetconOnce) {
1158+
if (Shape.ABI == coro::ABI::Retcon || Shape.ABI == coro::ABI::RetconOnce ||
1159+
Shape.ABI == coro::ABI::Async) {
11471160
// If we found any allocas, replace all of their remaining uses with Geps.
11481161
Builder.SetInsertPoint(&SpillBlock->front());
11491162
for (const auto &P : FrameData.Allocas) {
@@ -1866,7 +1879,8 @@ static void sinkSpillUsesAfterCoroBegin(Function &F,
18661879
for (User *U : Def->users()) {
18671880
auto Inst = cast<Instruction>(U);
18681881
if (Inst->getParent() != CoroBegin->getParent() ||
1869-
Dom.dominates(CoroBegin, Inst))
1882+
Dom.dominates(CoroBegin, Inst) ||
1883+
isa<CoroIdAsyncInst>(Inst) /*'fake' use of async context argument*/)
18701884
continue;
18711885
if (ToMove.insert(Inst))
18721886
Worklist.push_back(Inst);
@@ -2162,7 +2176,8 @@ void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
21622176
}
21632177
}
21642178
LLVM_DEBUG(dumpSpills("Spills", FrameData.Spills));
2165-
if (Shape.ABI == coro::ABI::Retcon || Shape.ABI == coro::ABI::RetconOnce)
2179+
if (Shape.ABI == coro::ABI::Retcon || Shape.ABI == coro::ABI::RetconOnce ||
2180+
Shape.ABI == coro::ABI::Async)
21662181
sinkSpillUsesAfterCoroBegin(F, FrameData, Shape.CoroBegin);
21672182
Shape.FrameTy = buildFrameType(F, Shape, FrameData);
21682183
// Add PromiseAlloca to Allocas list so that it is processed in insertSpills.

0 commit comments

Comments
 (0)