Skip to content

Commit e5ef485

Browse files
chore: various async fixes (#527)
* chore: various async fixes This comit adds some fixes/rewordings for the async sections. Signed-off-by: Victor Adossi <[email protected]> * refactor: structured concurrency section Signed-off-by: Victor Adossi <[email protected]> * chore: clarify the function being referred to Signed-off-by: Victor Adossi <[email protected]> * fix: errant space Co-authored-by: Luke Wagner <[email protected]> * refactor: callstack sentence * fix: address code review comments * fix: prose host defined imports explanation prose Co-authored-by: Luke Wagner <[email protected]> --------- Signed-off-by: Victor Adossi <[email protected]> Co-authored-by: Luke Wagner <[email protected]>
1 parent b142e20 commit e5ef485

File tree

1 file changed

+47
-28
lines changed

1 file changed

+47
-28
lines changed

design/mvp/Async.md

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -186,22 +186,23 @@ interface filesystem {
186186
}
187187
}
188188
```
189-
a bindings generator in a language with `async` would only emit `async`
190-
functions for `read` and `fetch`. Since in many languages `new` expressions
191-
cannot be async, there is no `async constructor`. Use cases requiring
192-
asynchronous construction can instead use `static async` functions, similar to
193-
`from-stream` in this example.
189+
A bindings generator processing the above WIT for a language with `async` would
190+
only emit `async` functions for `read` and `from-stream`.
194191

192+
Since in many languages `new` expressions cannot be async, there is no
193+
`async constructor`. Use cases requiring asynchronous construction can instead
194+
use `static async` functions, similar to `from-stream` in this example.
195195

196196
### Task
197197

198198
Every time a lifted function is called (e.g., when a component's export is
199199
called by the outside world), a new **task** is created that logically contains
200200
all the transitive control-flow state of the export call and will be destroyed
201-
when the export call finishes. When all of a component's exports are lifted
202-
synchronously, there will be at most one task alive at any one time. However,
203-
when a component exports asynchronously-lifted functions, there can be multiple
204-
tasks alive at once.
201+
when the export call finishes.
202+
203+
When all of a component's exports are lifted synchronously, there will be at most one
204+
task alive at any one time. However, when a component exports asynchronously-lifted
205+
functions, there can be multiple tasks alive at once.
205206

206207
In the Canonical ABI explainer, a "task" is represented with the Python
207208
[`Task`] class. A new `Task` object is created (by [`canon_lift`]) each time
@@ -268,18 +269,27 @@ in the Canonical ABI explainer.
268269
### Structured concurrency
269270

270271
Calling *into* a component creates a `Task` to track ABI state related to the
271-
*callee* (like "number of outstanding borrows"). Calling *out* of a component
272-
creates a `Subtask` to track ABI state related to the *caller* (like "which
273-
handles have been lent"). When one component calls another, there is thus a
274-
`Subtask`+`Task` pair that collectively maintains the overall state of the call
275-
and enforces that both components uphold their end of the ABI contract. But
276-
when the host calls into a component, there is only a `Task` and,
277-
symmetrically, when a component calls into the host, there is only a `Subtask`.
278-
279-
Based on this, the call stack at any point in time when a component calls a
280-
host-defined import will have a callstack of the general form:
272+
*callee* (like "number of outstanding borrows").
273+
274+
Calling *out* of a component creates a `Subtask` to track ABI state related to
275+
the *caller* (like "which handles have been lent").
276+
277+
When one component calls another, there is thus a `Subtask`+`Task` pair that
278+
collectively maintains the overall state of the call and enforces that both
279+
components uphold their end of the ABI contract. But when the host calls into
280+
a component, there is only a `Task` and, symmetrically, when a component calls
281+
into the host, there is only a `Subtask`.
282+
283+
Based on this, the call stack when a component calls a host-defined import will
284+
have the general form:
281285
```
282-
[Host caller] <- [Task] <- [Subtask+Task]* <- [Subtask] <- [Host callee]
286+
[Host]
287+
↓ host calls component export
288+
[Component Task]
289+
↓ component calls import implemented by another component's export 0..N times
290+
[Component Subtask <> Component Task]*
291+
↓ component calls import implemented by the host
292+
[Component Subtask <> Host task]
283293
```
284294
Here, the `<-` arrow represents the `supertask` relationship that is immutably
285295
established when first making the call. A paired `Subtask` and `Task` have the
@@ -719,7 +729,8 @@ world w {
719729
export foo: func(s: string) -> string;
720730
}
721731
```
722-
the default sync export function signature is:
732+
733+
The default sync export function signature for export `foo` is:
723734
```wat
724735
;; sync
725736
(func (param $s-ptr i32) (param $s-len i32) (result $retp i32))
@@ -735,33 +746,40 @@ The async export ABI provides two flavors: stackful and stackless.
735746

736747
The stackful ABI is currently gated by the 🚟 feature.
737748

738-
The async stackful export function signature is:
749+
The async stackful export function signature for export `foo` (defined above
750+
in world `w`) is:
739751
```wat
740752
;; async, no callback
741753
(func (param $s-ptr i32) (param $s-len i32))
742754
```
743-
The parameters work just like synchronous parameters. There is no core function
744-
result because a callee [returns](#returning) their value by *calling* the
745-
*imported* `task.return` function which has signature:
755+
756+
The parameters work just like synchronous parameters.
757+
758+
There is no core function result because a callee [returns](#returning) their
759+
value by *calling* the *imported* `task.return` function which has signature:
746760
```wat
747761
;; task.return
748762
(func (param $ret-ptr i32) (result $ret-len i32))
749763
```
764+
750765
The parameters of `task.return` work the same as if the WIT return type was the
751766
WIT parameter type of a synchronous function. For example, if more than 16
752767
core parameters would be needed, a single `i32` pointer into linear memory is
753768
used.
754769

755770
##### Stackless Async Exports
756771

757-
The async stackless export function signature is:
772+
The async stackless export function signature for export `foo` (defined above
773+
in world `w`) is:
758774
```wat
759775
;; async, callback
760776
(func (param $s-ptr i32) (param $s-len i32) (result i32))
761777
```
778+
762779
The parameters also work just like synchronous parameters. The callee returns
763-
their value by calling `task.return` just like the stackful case. The `(result
764-
i32)` lets the core function return what it wants the runtime to do next:
780+
their value by calling `task.return` just like the stackful case.
781+
782+
The `(result i32)` lets the core function return what it wants the runtime to do next:
765783
* If the low 4 bits are `0`, the callee completed (and called `task.return`)
766784
without blocking.
767785
* If the low 4 bits are `1`, the callee wants to yield, allowing other code
@@ -776,6 +794,7 @@ must also be exported with signature:
776794
```wat
777795
(func (param i32 i32 i32) (result i32))
778796
```
797+
779798
The `(result i32)` has the same interpretation as the stackless export function
780799
and the runtime will repeatedly call the callback until a value of `0` is
781800
returned. The `i32` parameters describe what happened that caused the callback

0 commit comments

Comments
 (0)