Skip to content

Commit 3fc1272

Browse files
authored
Merge pull request #1814 from swiftwasm/master
[pull] swiftwasm from master
2 parents 43e984b + 769034b commit 3fc1272

21 files changed

+285
-388
lines changed

docs/SIL.rst

Lines changed: 208 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -631,16 +631,98 @@ autorelease in the callee.
631631
type behaves like a non-generic type, as if the substitutions were
632632
actually applied to the underlying function type.
633633

634+
Async Functions
635+
```````````````
636+
637+
SIL function types may be ``@async``. ``@async`` functions run inside async
638+
tasks, and can have explicit *suspend points* where they suspend execution.
639+
``@async`` functions can only be called from other ``@async`` functions, but
640+
otherwise can be invoked with the normal ``apply`` and ``try_apply``
641+
instructions (or ``begin_apply`` if they are coroutines).
642+
643+
In Swift, the ``withUnsafeContinuation`` primitive is used to implement
644+
primitive suspend points. In SIL, ``@async`` functions represent this
645+
abstraction using the ``get_async_continuation[_addr]`` and
646+
``await_async_continuation`` instructions. ``get_async_continuation[_addr]``
647+
accesses a *continuation* value that can be used to resume the coroutine after
648+
it suspends. The resulting continuation value can then be passed into a
649+
completion handler, registered with an event loop, or scheduled by some other
650+
mechanism. Operations on the continuation can resume the async function's
651+
execution by passing a value back to the async function, or passing in an error
652+
that propagates as an error in the async function's context.
653+
The ``await_async_continuation`` instruction suspends execution of
654+
the coroutine until the continuation is invoked to resume it. A use of
655+
``withUnsafeContinuation`` in Swift::
656+
657+
func waitForCallback() async -> Int {
658+
return await withUnsafeContinuation { cc in
659+
registerCallback { cc.resume($0) }
660+
}
661+
}
662+
663+
might lower to the following SIL::
664+
665+
sil @waitForCallback : $@convention(thin) @async () -> Int {
666+
entry:
667+
%cc = get_async_continuation $Int
668+
%closure = function_ref @waitForCallback_closure
669+
: $@convention(thin) (UnsafeContinuation<Int>) -> ()
670+
apply %closure(%cc)
671+
await_async_continuation %cc, resume resume_cc
672+
673+
resume_cc(%result : $Int):
674+
return %result
675+
}
676+
677+
The closure may then be inlined into the ``waitForCallback`` function::
678+
679+
sil @waitForCallback : $@convention(thin) @async () -> Int {
680+
entry:
681+
%cc = get_async_continuation $Int
682+
%registerCallback = function_ref @registerCallback
683+
: $@convention(thin) (@convention(thick) () -> ()) -> ()
684+
%callback_fn = function_ref @waitForCallback_callback
685+
%callback = partial_apply %callback_fn(%cc)
686+
apply %registerCallback(%callback)
687+
await_async_continuation %cc, resume resume_cc
688+
689+
resume_cc(%result : $Int):
690+
return %result
691+
}
692+
693+
Every continuation value must be used exactly once to resume its associated
694+
async coroutine once. It is undefined behavior to attempt to resume the same
695+
continuation more than once. On the flip side, failing to resume a continuation
696+
will leave the async task stuck in the suspended state, leaking any memory or
697+
other resources it owns.
698+
634699
Coroutine Types
635700
```````````````
636701

637702
A coroutine is a function which can suspend itself and return control to
638703
its caller without terminating the function. That is, it does not need to
639-
obey a strict stack discipline.
640-
641-
SIL supports two kinds of coroutine: ``@yield_many`` and ``@yield_once``.
642-
Either of these attributes may be written before a function type to
643-
indicate that it is a coroutine type.
704+
obey a strict stack discipline. SIL coroutines have control flow that is
705+
tightly integrated with their callers, and they pass information back and forth
706+
between caller and callee in a structured way through yield points.
707+
*Generalized accessors* and *generators* in Swift fit this description: a
708+
``read`` or ``modify`` accessor coroutine projects a single value, yields
709+
ownership of that one value temporarily to the caller, and then takes ownership
710+
back when resumed, allowing the coroutine to clean up resources or otherwise
711+
react to mutations done by the caller. *Generators* similarly yield a stream of
712+
values one at a time to their caller, temporarily yielding ownership of each
713+
value in turn to the caller. The tight coupling of the caller's control flow
714+
with these coroutines allows the caller to *borrow* values produced by the
715+
coroutine, where a normal function return would need to transfer ownership of
716+
its return value, since a normal function's context ceases to exist and be able
717+
to maintain ownership of the value after it returns.
718+
719+
To support these concepts, SIL supports two kinds of coroutine:
720+
``@yield_many`` and ``@yield_once``. Either of these attributes may be
721+
written before a function type to indicate that it is a coroutine type.
722+
``@yield_many`` and ``@yield_once`` coroutines are allowed to also be
723+
``@async``. (Note that ``@async`` functions are not themselves modeled
724+
explicitly as coroutines in SIL, although the implementation may use a coroutine
725+
lowering strategy.)
644726

645727
A coroutine type may declare any number of *yielded values*, which is to
646728
say, values which are provided to the caller at a yield point. Yielded
@@ -658,19 +740,13 @@ coroutine can be called with the ``begin_apply`` instruction. There
658740
is no support yet for calling a throwing yield-once coroutine or for
659741
calling a yield-many coroutine of any kind.
660742

661-
Coroutines may contain the special ``yield`` and ``unwind`` instructions.
743+
Coroutines may contain the special ``yield`` and ``unwind``
744+
instructions.
662745

663746
A ``@yield_many`` coroutine may yield as many times as it desires.
664747
A ``@yield_once`` coroutine may yield exactly once before returning,
665748
although it may also ``throw`` before reaching that point.
666749

667-
This coroutine representation is well-suited to coroutines whose control
668-
flow is tightly integrated with their callers and which intend to pass
669-
information back and forth. This matches the needs of generalized
670-
accessor and generator features in Swift. It is not a particularly good
671-
match for ``async``/``await``-style features; a simpler representation
672-
would probably do fine for that.
673-
674750
Properties of Types
675751
```````````````````
676752

@@ -2608,6 +2684,76 @@ undefined behavior if the global variable has already been initialized.
26082684

26092685
The type operand must be a lowered object type.
26102686

2687+
get_async_continuation
2688+
``````````````````````
2689+
2690+
::
2691+
2692+
sil-instruction ::= 'get_async_continuation' '[throws]'? sil-type
2693+
2694+
%0 = get_async_continuation $T
2695+
%0 = get_async_continuation [throws] $U
2696+
2697+
Begins a suspension of an ``@async`` function. This instruction can only be
2698+
used inside an ``@async`` function. The result of the instruction is an
2699+
``UnsafeContinuation<T>`` value, where ``T`` is the formal type argument to the
2700+
instruction, or an ``UnsafeThrowingContinuation<T>`` if the instruction
2701+
carries the ``[throws]`` attribute. ``T`` must be a loadable type.
2702+
The continuation must be consumed by a ``await_async_continuation`` terminator
2703+
on all paths. Between ``get_async_continuation`` and
2704+
``await_async_continuation``, the following restrictions apply:
2705+
2706+
- The function cannot ``return``, ``throw``, ``yield``, or ``unwind``.
2707+
- There cannot be nested suspend points; namely, the function cannot call
2708+
another ``@async`` function, nor can it initiate another suspend point with
2709+
``get_async_continuation``.
2710+
2711+
The function suspends execution when the matching ``await_async_continuation``
2712+
terminator is reached, and resumes execution when the continuation is resumed.
2713+
The continuation resumption operation takes a value of type ``T`` which is
2714+
passed back into the function when it resumes execution in the ``await_async_continuation`` instruction's
2715+
``resume`` successor block. If the instruction
2716+
has the ``[throws]`` attribute, it can also be resumed in an error state, in
2717+
which case the matching ``await_async_continuation`` instruction must also
2718+
have an ``error`` successor.
2719+
2720+
Within the enclosing SIL function, the result continuation is consumed by the
2721+
``await_async_continuation``, and cannot be referenced after the
2722+
``await_async_continuation`` executes. Dynamically, the continuation value must
2723+
be resumed exactly once in the course of the program's execution; it is
2724+
undefined behavior to resume the continuation more than once. Conversely,
2725+
failing to resume the continuation will leave the suspended async coroutine
2726+
hung in its suspended state, leaking any resources it may be holding.
2727+
2728+
get_async_continuation_addr
2729+
```````````````````````````
2730+
2731+
::
2732+
2733+
sil-instruction ::= 'get_async_continuation_addr' '[throws]'? sil-type ',' sil-operand
2734+
2735+
%1 = get_async_continuation_addr $T, %0 : $*T
2736+
%1 = get_async_continuation_addr [throws] $U, %0 : $*U
2737+
2738+
Begins a suspension of an ``@async`` function, like ``get_async_continuation``,
2739+
additionally binding a specific memory location for receiving the value
2740+
when the result continuation is resumed. The operand must be an address whose
2741+
type is the maximally-abstracted lowered type of the formal resume type. The
2742+
memory must be uninitialized, and must remain allocated until the matching
2743+
``await_async_continuation`` instruction(s) consuming the result continuation
2744+
have executed. The behavior is otherwise the same as
2745+
``get_async_continuation``, and the same restrictions apply on code appearing
2746+
between ``get_async_continuation_addr`` and ``await_async_continuation`` as
2747+
apply between ``get_async_continuation`` and ``await_async_continuation``.
2748+
Additionally, the state of the memory referenced by the operand is indefinite
2749+
between the execution of ``get_async_continuation_addr`` and
2750+
``await_async_continuation``, and it is undefined behavior to read or modify
2751+
the memory during this time. After the ``await_async_continuation`` resumes
2752+
normally to its ``resume`` successor, the memory referenced by the operand is
2753+
initialized with the resume value, and that value is then owned by the current
2754+
function. If ``await_async_continuation`` instead resumes to its ``error``
2755+
successor, then the memory remains uninitialized.
2756+
26112757
dealloc_stack
26122758
`````````````
26132759
::
@@ -6230,6 +6376,55 @@ destination (if it returns with ``throw``).
62306376

62316377
The rules on generic substitutions are identical to those of ``apply``.
62326378

6379+
await_async_continuation
6380+
````````````````````````
6381+
6382+
::
6383+
6384+
sil-terminator ::= 'await_async_continuation' sil-value
6385+
',' 'resume' sil-identifier
6386+
(',' 'error' sil-identifier)?
6387+
6388+
await_async_continuation %0 : $UnsafeContinuation<T>, resume bb1
6389+
await_async_continuation %0 : $UnsafeThrowingContinuation<T>, resume bb1, error bb2
6390+
6391+
bb1(%1 : @owned $T):
6392+
bb2(%2 : @owned $Error):
6393+
6394+
Suspends execution of an ``@async`` function until the continuation
6395+
is resumed. The continuation must be the result of a
6396+
``get_async_continuation`` or ``get_async_continuation_addr``
6397+
instruction within the same function; see the documentation for
6398+
``get_async_continuation`` for discussion of further constraints on the
6399+
IR between ``get_async_continuation[_addr]`` and ``await_async_continuation``.
6400+
This terminator can only appear inside an ``@async`` function. The
6401+
instruction must always have a ``resume`` successor, but must have an
6402+
``error`` successor if and only if the operand is an
6403+
``UnsafeThrowingContinuation<T>``.
6404+
6405+
If the operand is the result of a ``get_async_continuation`` instruction,
6406+
then the ``resume`` successor block must take an argument whose type is the
6407+
maximally-abstracted lowered type of ``T``, matching the type argument of the
6408+
``Unsafe[Throwing]Continuation<T>`` operand. The value of the ``resume``
6409+
argument is owned by the current function. If the operand is the result of a
6410+
``get_async_continuation_addr`` instruction, then the ``resume`` successor
6411+
block must *not* take an argument; the resume value will be written to the
6412+
memory referenced by the operand to the ``get_async_continuation_addr``
6413+
instruction, after which point the value in that memory becomes owned by the
6414+
current function. With either variant, if the ``await_async_continuation``
6415+
instruction has an ``error`` successor block, the ``error`` block must take a
6416+
single ``Error`` argument, and that argument is owned by the enclosing
6417+
function. The memory referenced by a ``get_async_continuation_addr``
6418+
instruction remains uninitialized when ``await_async_continuation`` resumes on
6419+
the ``error`` successor.
6420+
6421+
It is possible for a continuation to be resumed before
6422+
``await_async_continuation``. In this case, the resume operation returns
6423+
immediately to its caller. When the ``await_async_continuation`` instruction
6424+
later executes, it then immediately transfers control to
6425+
its ``resume`` or ``error`` successor block, using the resume or error value
6426+
that the continuation was already resumed with.
6427+
62336428
Differentiable Programming
62346429
~~~~~~~~~~~~~~~~~~~~~~~~~~
62356430

0 commit comments

Comments
 (0)