@@ -174,6 +174,88 @@ This may be acceptable if LLVM's coroutine support is primarily being
174
174
used for low-level lowering and inlining is expected to be applied
175
175
earlier in the pipeline.
176
176
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
+
177
259
Coroutines by Example
178
260
=====================
179
261
@@ -1093,6 +1175,45 @@ duplicated.
1093
1175
1094
1176
A frontend should emit exactly one `coro.id ` intrinsic per coroutine.
1095
1177
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
+
1096
1217
.. _coro.id.retcon :
1097
1218
1098
1219
'llvm.coro.id.retcon' Intrinsic
@@ -1380,6 +1501,68 @@ to the coroutine:
1380
1501
switch i8 %suspend1, label %suspend [i8 0, label %resume1
1381
1502
i8 1, label %cleanup]
1382
1503
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
+
1383
1566
.. _coro.suspend.retcon :
1384
1567
1385
1568
'llvm.coro.suspend.retcon' Intrinsic
0 commit comments