Skip to content

Commit

Permalink
Merge pull request #1814 from swiftwasm/master
Browse files Browse the repository at this point in the history
[pull] swiftwasm from master
  • Loading branch information
pull[bot] authored Sep 23, 2020
2 parents 43e984b + 769034b commit 3fc1272
Show file tree
Hide file tree
Showing 21 changed files with 285 additions and 388 deletions.
221 changes: 208 additions & 13 deletions docs/SIL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -631,16 +631,98 @@ autorelease in the callee.
type behaves like a non-generic type, as if the substitutions were
actually applied to the underlying function type.

Async Functions
```````````````

SIL function types may be ``@async``. ``@async`` functions run inside async
tasks, and can have explicit *suspend points* where they suspend execution.
``@async`` functions can only be called from other ``@async`` functions, but
otherwise can be invoked with the normal ``apply`` and ``try_apply``
instructions (or ``begin_apply`` if they are coroutines).

In Swift, the ``withUnsafeContinuation`` primitive is used to implement
primitive suspend points. In SIL, ``@async`` functions represent this
abstraction using the ``get_async_continuation[_addr]`` and
``await_async_continuation`` instructions. ``get_async_continuation[_addr]``
accesses a *continuation* value that can be used to resume the coroutine after
it suspends. The resulting continuation value can then be passed into a
completion handler, registered with an event loop, or scheduled by some other
mechanism. Operations on the continuation can resume the async function's
execution by passing a value back to the async function, or passing in an error
that propagates as an error in the async function's context.
The ``await_async_continuation`` instruction suspends execution of
the coroutine until the continuation is invoked to resume it. A use of
``withUnsafeContinuation`` in Swift::

func waitForCallback() async -> Int {
return await withUnsafeContinuation { cc in
registerCallback { cc.resume($0) }
}
}

might lower to the following SIL::

sil @waitForCallback : $@convention(thin) @async () -> Int {
entry:
%cc = get_async_continuation $Int
%closure = function_ref @waitForCallback_closure
: $@convention(thin) (UnsafeContinuation<Int>) -> ()
apply %closure(%cc)
await_async_continuation %cc, resume resume_cc

resume_cc(%result : $Int):
return %result
}

The closure may then be inlined into the ``waitForCallback`` function::

sil @waitForCallback : $@convention(thin) @async () -> Int {
entry:
%cc = get_async_continuation $Int
%registerCallback = function_ref @registerCallback
: $@convention(thin) (@convention(thick) () -> ()) -> ()
%callback_fn = function_ref @waitForCallback_callback
%callback = partial_apply %callback_fn(%cc)
apply %registerCallback(%callback)
await_async_continuation %cc, resume resume_cc

resume_cc(%result : $Int):
return %result
}

Every continuation value must be used exactly once to resume its associated
async coroutine once. It is undefined behavior to attempt to resume the same
continuation more than once. On the flip side, failing to resume a continuation
will leave the async task stuck in the suspended state, leaking any memory or
other resources it owns.

Coroutine Types
```````````````

A coroutine is a function which can suspend itself and return control to
its caller without terminating the function. That is, it does not need to
obey a strict stack discipline.

SIL supports two kinds of coroutine: ``@yield_many`` and ``@yield_once``.
Either of these attributes may be written before a function type to
indicate that it is a coroutine type.
obey a strict stack discipline. SIL coroutines have control flow that is
tightly integrated with their callers, and they pass information back and forth
between caller and callee in a structured way through yield points.
*Generalized accessors* and *generators* in Swift fit this description: a
``read`` or ``modify`` accessor coroutine projects a single value, yields
ownership of that one value temporarily to the caller, and then takes ownership
back when resumed, allowing the coroutine to clean up resources or otherwise
react to mutations done by the caller. *Generators* similarly yield a stream of
values one at a time to their caller, temporarily yielding ownership of each
value in turn to the caller. The tight coupling of the caller's control flow
with these coroutines allows the caller to *borrow* values produced by the
coroutine, where a normal function return would need to transfer ownership of
its return value, since a normal function's context ceases to exist and be able
to maintain ownership of the value after it returns.

To support these concepts, SIL supports two kinds of coroutine:
``@yield_many`` and ``@yield_once``. Either of these attributes may be
written before a function type to indicate that it is a coroutine type.
``@yield_many`` and ``@yield_once`` coroutines are allowed to also be
``@async``. (Note that ``@async`` functions are not themselves modeled
explicitly as coroutines in SIL, although the implementation may use a coroutine
lowering strategy.)

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

Coroutines may contain the special ``yield`` and ``unwind`` instructions.
Coroutines may contain the special ``yield`` and ``unwind``
instructions.

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

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

Properties of Types
```````````````````

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

The type operand must be a lowered object type.

get_async_continuation
``````````````````````

::

sil-instruction ::= 'get_async_continuation' '[throws]'? sil-type

%0 = get_async_continuation $T
%0 = get_async_continuation [throws] $U

Begins a suspension of an ``@async`` function. This instruction can only be
used inside an ``@async`` function. The result of the instruction is an
``UnsafeContinuation<T>`` value, where ``T`` is the formal type argument to the
instruction, or an ``UnsafeThrowingContinuation<T>`` if the instruction
carries the ``[throws]`` attribute. ``T`` must be a loadable type.
The continuation must be consumed by a ``await_async_continuation`` terminator
on all paths. Between ``get_async_continuation`` and
``await_async_continuation``, the following restrictions apply:

- The function cannot ``return``, ``throw``, ``yield``, or ``unwind``.
- There cannot be nested suspend points; namely, the function cannot call
another ``@async`` function, nor can it initiate another suspend point with
``get_async_continuation``.

The function suspends execution when the matching ``await_async_continuation``
terminator is reached, and resumes execution when the continuation is resumed.
The continuation resumption operation takes a value of type ``T`` which is
passed back into the function when it resumes execution in the ``await_async_continuation`` instruction's
``resume`` successor block. If the instruction
has the ``[throws]`` attribute, it can also be resumed in an error state, in
which case the matching ``await_async_continuation`` instruction must also
have an ``error`` successor.

Within the enclosing SIL function, the result continuation is consumed by the
``await_async_continuation``, and cannot be referenced after the
``await_async_continuation`` executes. Dynamically, the continuation value must
be resumed exactly once in the course of the program's execution; it is
undefined behavior to resume the continuation more than once. Conversely,
failing to resume the continuation will leave the suspended async coroutine
hung in its suspended state, leaking any resources it may be holding.

get_async_continuation_addr
```````````````````````````

::

sil-instruction ::= 'get_async_continuation_addr' '[throws]'? sil-type ',' sil-operand

%1 = get_async_continuation_addr $T, %0 : $*T
%1 = get_async_continuation_addr [throws] $U, %0 : $*U

Begins a suspension of an ``@async`` function, like ``get_async_continuation``,
additionally binding a specific memory location for receiving the value
when the result continuation is resumed. The operand must be an address whose
type is the maximally-abstracted lowered type of the formal resume type. The
memory must be uninitialized, and must remain allocated until the matching
``await_async_continuation`` instruction(s) consuming the result continuation
have executed. The behavior is otherwise the same as
``get_async_continuation``, and the same restrictions apply on code appearing
between ``get_async_continuation_addr`` and ``await_async_continuation`` as
apply between ``get_async_continuation`` and ``await_async_continuation``.
Additionally, the state of the memory referenced by the operand is indefinite
between the execution of ``get_async_continuation_addr`` and
``await_async_continuation``, and it is undefined behavior to read or modify
the memory during this time. After the ``await_async_continuation`` resumes
normally to its ``resume`` successor, the memory referenced by the operand is
initialized with the resume value, and that value is then owned by the current
function. If ``await_async_continuation`` instead resumes to its ``error``
successor, then the memory remains uninitialized.

dealloc_stack
`````````````
::
Expand Down Expand Up @@ -6230,6 +6376,55 @@ destination (if it returns with ``throw``).

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

await_async_continuation
````````````````````````

::

sil-terminator ::= 'await_async_continuation' sil-value
',' 'resume' sil-identifier
(',' 'error' sil-identifier)?

await_async_continuation %0 : $UnsafeContinuation<T>, resume bb1
await_async_continuation %0 : $UnsafeThrowingContinuation<T>, resume bb1, error bb2

bb1(%1 : @owned $T):
bb2(%2 : @owned $Error):

Suspends execution of an ``@async`` function until the continuation
is resumed. The continuation must be the result of a
``get_async_continuation`` or ``get_async_continuation_addr``
instruction within the same function; see the documentation for
``get_async_continuation`` for discussion of further constraints on the
IR between ``get_async_continuation[_addr]`` and ``await_async_continuation``.
This terminator can only appear inside an ``@async`` function. The
instruction must always have a ``resume`` successor, but must have an
``error`` successor if and only if the operand is an
``UnsafeThrowingContinuation<T>``.

If the operand is the result of a ``get_async_continuation`` instruction,
then the ``resume`` successor block must take an argument whose type is the
maximally-abstracted lowered type of ``T``, matching the type argument of the
``Unsafe[Throwing]Continuation<T>`` operand. The value of the ``resume``
argument is owned by the current function. If the operand is the result of a
``get_async_continuation_addr`` instruction, then the ``resume`` successor
block must *not* take an argument; the resume value will be written to the
memory referenced by the operand to the ``get_async_continuation_addr``
instruction, after which point the value in that memory becomes owned by the
current function. With either variant, if the ``await_async_continuation``
instruction has an ``error`` successor block, the ``error`` block must take a
single ``Error`` argument, and that argument is owned by the enclosing
function. The memory referenced by a ``get_async_continuation_addr``
instruction remains uninitialized when ``await_async_continuation`` resumes on
the ``error`` successor.

It is possible for a continuation to be resumed before
``await_async_continuation``. In this case, the resume operation returns
immediately to its caller. When the ``await_async_continuation`` instruction
later executes, it then immediately transfers control to
its ``resume`` or ``error`` successor block, using the resume or error value
that the continuation was already resumed with.

Differentiable Programming
~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
Loading

0 comments on commit 3fc1272

Please sign in to comment.