Skip to content

Commit 1e27e2f

Browse files
committed
Make 'async' part of the function type, not a hint
1 parent cfc6e54 commit 1e27e2f

File tree

7 files changed

+318
-175
lines changed

7 files changed

+318
-175
lines changed

design/mvp/Binary.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ valtype ::= i:<typeidx> => i
216216
resourcetype ::= 0x3f 0x7f f?:<funcidx>? => (resource (rep i32) (dtor f)?)
217217
| 0x3e 0x7f f:<funcidx> cb?:<funcidx>? => (resource (rep i32) (dtor async f (callback cb)?)) 🚝
218218
functype ::= 0x40 ps:<paramlist> rs:<resultlist> => (func ps rs)
219+
| 0x43 ps:<paramlist> rs:<resultlist> => (func async ps rs)
219220
paramlist ::= lt*:vec(<labelvaltype>) => (param lt)*
220221
resultlist ::= 0x00 t:<valtype> => (result t)
221222
| 0x01 0x00 =>
@@ -288,7 +289,6 @@ canon ::= 0x00 0x00 f:<core:funcidx> opts:<opts> ft:<typeidx> => (canon lift
288289
| 0x01 0x00 f:<funcidx> opts:<opts> => (canon lower f opts (core func))
289290
| 0x02 rt:<typeidx> => (canon resource.new rt (core func))
290291
| 0x03 rt:<typeidx> => (canon resource.drop rt (core func))
291-
| 0x07 rt:<typeidx> => (canon resource.drop rt async (core func)) 🚝
292292
| 0x04 rt:<typeidx> => (canon resource.rep rt (core func))
293293
| 0x08 => (canon backpressure.set (core func)) 🔀✕
294294
| 0x24 => (canon backpressure.inc (core func)) 🔀
@@ -515,7 +515,8 @@ named once.
515515

516516
* The opcodes (for types, canon built-ins, etc) should be re-sorted
517517
* The two `depname` cases should be merged into one (`dep=<...>`)
518-
* The two `list` type codes should be merged into one with an optional immediate.
518+
* The two `list` type codes should be merged into one with an optional immediate
519+
and similarly for `func`.
519520
* The `0x00` variant of `importname'` and `exportname'` will be removed. Any
520521
remaining variant(s) will be renumbered or the prefix byte will be removed or
521522
repurposed.

design/mvp/CanonicalABI.md

Lines changed: 122 additions & 37 deletions
Large diffs are not rendered by default.

design/mvp/Concurrency.md

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ the same way that they already bind to various OS's concurrent I/O APIs (such
7171
as `select`, `epoll`, `io_uring`, `kqueue` and Overlapped I/O) making the
7272
Component Model "just another OS" from the language toolchain's perspective.
7373

74+
TODO
7475
The new async ABI can be used alongside or instead of the existing Preview 2
7576
"sync ABI" to call or implement *any* WIT function type, not just functions
7677
with specific signatures. This allows *all* function types to be called or
@@ -83,6 +84,7 @@ pairings have well-defined, composable behavior for both inter-component and
8384
intra-component calls, so that functions and components are not forced to pick
8485
a "[color]".
8586

87+
TODO
8688
Although Component Model function *types* are colorless, it can still be
8789
beneficial, especially in languages with `async`/`await`-style concurrency, to
8890
give the bindings generator a *hint* as to whether or not a particular function
@@ -159,6 +161,7 @@ any more until I let some of them complete". Thus, the Component Model provides
159161
a built-in way for a component instance to apply and release backpressure that
160162
callers must always be prepared to handle.
161163

164+
TODO: ("through an `async` function type so that blocking doesn't trap")
162165
With this backpressure mechanism in place, there is a natural way for sync and
163166
async code to interoperate:
164167
1. If an async component calls a sync component and the sync component blocks,
@@ -558,6 +561,9 @@ attempting to `thread.resume-later` a thread waiting on `waitable-set.wait` or
558561
a synchronous import call will trap. Thus, language runtimes and compilers have
559562
to be careful when using a mix of explicit and implicit suspension/resumption.
560563

564+
TODO: suspend before returning value from a non-`async` function traps,
565+
specifically: `thread.suspend`, `waitable-set.wait`. Also others
566+
561567
Lastly, when an async function is implemented using the `callback` suboption
562568
(mentioned in the [summary](#summary)), instead of calling `wait`, `poll` or
563569
`yield`, as an optimization, the `callback` function can *return* to wait in
@@ -589,6 +595,12 @@ event, allowing a higher degree of concurrency than synchronous exports.
589595
Stackfull async exports ignore the lock entirely and thus achieve the highest
590596
degree of (cooperative) concurrency.
591597

598+
TODO: non-`async` functions don't pile up like `async` functions and aren't
599+
allowed to block, and thus can and must skip backpressure. If a single
600+
component exports by `async` and non-`async` functions, sync functions ignore
601+
implicit backpressure and thus barge in like signal handlers. Codegen must
602+
be aware.
603+
592604
Once a task is allowed to start according to these backpressure rules, its
593605
arguments are lowered into the callee's linear memory and the task is in
594606
the "started" state.
@@ -619,6 +631,8 @@ transitively created by that thread) must call `task.return`.
619631
Once `task.return` is called, the task is in the "returned" state. Calling
620632
`task.return` when not in the "started" state traps.
621633

634+
TODO: once in the "returned" state, even non-`async` functions can block
635+
622636
### Borrows
623637

624638
Component Model async support is careful to ensure that `borrow`ed handles work
@@ -686,13 +700,14 @@ the next cancellable wait. In the worst case, though, a component may never
686700
wait cancellably and thus cancellation may be silently ignored.
687701

688702
`subtask.cancel` can be called synchronously or asynchronously. If called
689-
synchronously, `subtask.cancel` waits until the subtask reaches a resolved
690-
state and returns which state was reached. If called asynchronously, then if a
691-
cancellable subtask thread is resumed *and* the subtask reaches a resolved
692-
state before suspending itself for whatever reason `subtask.cancel` will return
693-
which state was reached. Otherwise, `subtask.cancel` will return a "blocked"
694-
sentinel value and the caller must [wait][waiting] via waitable set until the
695-
subtask reaches a resolved state.
703+
synchronously, `subtask.cancel` blocks until the subtask reaches a resolved
704+
state and returns which state was reached. (TODO: traps if not allowed to
705+
block) If called asynchronously, then if a cancellable subtask thread is
706+
resumed *and* the subtask reaches a resolved state before suspending itself for
707+
whatever reason `subtask.cancel` will return which state was reached.
708+
Otherwise, `subtask.cancel` will return a "blocked" sentinel value and the
709+
caller must [wait][waiting] via waitable set until the subtask reaches a
710+
resolved state.
696711

697712
The Component Model does not provide a mechanism to force prompt termination of
698713
threads as this can lead to leaks and corrupt state in a still-live component
@@ -807,8 +822,10 @@ function an async-oriented core function signature that can be used instead of
807822
or in addition to the existing (Preview-2-defined) synchronous core function
808823
signature. This async-oriented core function signature is intended to be called
809824
or implemented by generated bindings which then map the low-level core async
810-
protocol to the languages' higher-level native concurrency features. Because
811-
the WIT-level `async` attribute is purely a *hint* (as mentioned
825+
protocol to the languages' higher-level native concurrency features.
826+
827+
TODO
828+
Because the WIT-level `async` attribute is purely a *hint* (as mentioned
812829
[above](#summary)), *every* WIT function has an async core function signature;
813830
`async` just provides hints to the bindings generator for which to use by
814831
default.
@@ -818,10 +835,10 @@ default.
818835
Given these imported WIT functions (using the fixed-length-list feature 🔧):
819836
```wit
820837
world w {
821-
import foo: func(s: string) -> u32;
822-
import bar: func(s: string) -> string;
823-
import baz: func(t: list<u64; 5>) -> string;
824-
import quux: func(t: list<u32; 17>) -> string;
838+
import foo: async func(s: string) -> u32;
839+
import bar: async func(s: string) -> string;
840+
import baz: async func(t: list<u64; 5>) -> string;
841+
import quux: async func(t: list<u32; 17>) -> string;
825842
}
826843
```
827844
the default/synchronous lowered import function signatures are:
@@ -871,22 +888,22 @@ receiving an event indicating that the async subtask has started/returned.
871888

872889
Other example asynchronous lowered signatures:
873890

874-
| WIT function type | Async ABI |
875-
| ----------------------------------------- | --------------------- |
876-
| `func()` | `(func (result i32))` |
877-
| `func() -> string` | `(func (param $out-ptr i32) (result i32))` |
878-
| `func(x: f32) -> f32` | `(func (param $x f32) (param $out-ptr i32) (result i32))` |
879-
| `func(s: string, t: string)` | `(func (param $s-ptr i32) (param $s-len i32) (result $t-ptr i32) (param $t-len i32) (result i32))` |
891+
| WIT function type | Async ABI |
892+
| ---------------------------------- | --------------------- |
893+
| `async func()` | `(func (result i32))` |
894+
| `async func() -> string` | `(func (param $out-ptr i32) (result i32))` |
895+
| `async func(x: f32) -> f32` | `(func (param $x f32) (param $out-ptr i32) (result i32))` |
896+
| `async func(s: string, t: string)` | `(func (param $s-ptr i32) (param $s-len i32) (result $t-ptr i32) (param $t-len i32) (result i32))` |
880897

881898
`future` and `stream` can appear anywhere in the parameter or result types. For example:
882899
```wit
883-
func(s1: stream<future<string>>, s2: list<stream<string>>) -> result<stream<string>, stream<error>>
900+
async func(s1: stream<future<string>>, s2: list<stream<string>>) -> result<stream<string>, stream<error>>
884901
```
885902
In *both* the sync and async ABIs, a `future` or `stream` in the WIT-level type
886903
translates to a single `i32` in the ABI. This `i32` is an index into the
887904
current component instance's handle table. For example, for the WIT function type:
888905
```wit
889-
func(f: future<string>) -> future<u32>
906+
async func(f: future<string>) -> future<u32>
890907
```
891908
the synchronous ABI has signature:
892909
```wat
@@ -911,7 +928,7 @@ Explainer]. For a complete description of how async imports work, see
911928
Given an exported WIT function:
912929
```wit
913930
world w {
914-
export foo: func(s: string) -> string;
931+
export foo: async func(s: string) -> string;
915932
}
916933
```
917934

@@ -1004,7 +1021,7 @@ Starting with the stackful ABI, the meat of this example component is replaced
10041021
with `...` to focus on the overall flow of function calls:
10051022
```wat
10061023
(component
1007-
(import "fetch" (func $fetch (param "url" string) (result (list u8))))
1024+
(import "fetch" (func $fetch async (param "url" string) (result (list u8))))
10081025
(core module $Libc
10091026
(memory (export "mem") 1)
10101027
(func (export "realloc") (param i32 i32 i32 i32) (result i32) ...)
@@ -1064,7 +1081,7 @@ with `...` to focus on the overall flow of function calls:
10641081
))))
10651082
(canon lift (core func $main "summarize")
10661083
async (memory $mem) (realloc $realloc)
1067-
(func $summarize (param "urls" (list string)) (result string)))
1084+
(func $summarize async (param "urls" (list string)) (result string)))
10681085
(export "summarize" (func $summarize))
10691086
)
10701087
```
@@ -1087,6 +1104,10 @@ call to `waitable-set.wait` blocks, the runtime will suspend its callstack
10871104
entry point and store it in context-local storage (via `context.set`) instead
10881105
of simply using a `global`, as in a synchronous function.
10891106

1107+
Note that removing `async` from the type of `summarize` specified in the `canon
1108+
lift` definition would cause the above component to trap when it attempted to
1109+
call `waitable-set.wait`.
1110+
10901111
### Stackless ABI example
10911112

10921113
The stackful example can be re-written to use the `callback` immediate (thereby
@@ -1102,7 +1123,7 @@ core wasm code between events, not externally-visible behavior.
11021123

11031124
```wat
11041125
(component
1105-
(import "fetch" (func $fetch (param "url" string) (result (list u8))))
1126+
(import "fetch" (func $fetch async (param "url" string) (result (list u8))))
11061127
(core module $Libc
11071128
(memory (export "mem") 1)
11081129
(func (export "realloc") (param i32 i32 i32 i32) (result i32) ...)
@@ -1169,7 +1190,7 @@ core wasm code between events, not externally-visible behavior.
11691190
))))
11701191
(canon lift (core func $main "summarize")
11711192
async (callback (core func $main "cb")) (memory $mem) (realloc $realloc)
1172-
(func $summarize (param "urls" (list string)) (result string)))
1193+
(func $summarize async (param "urls" (list string)) (result string)))
11731194
(export "summarize" (func $summarize))
11741195
)
11751196
```

design/mvp/Explainer.md

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ valtype ::= <typeidx>
568568
| <defvaltype>
569569
resourcetype ::= (resource (rep i32) (dtor <funcidx>)?)
570570
| (resource (rep i32) (dtor async <funcidx> (callback <funcidx>)?)?) 🚝
571-
functype ::= (func (param "<label>" <valtype>)* (result <valtype>)?)
571+
functype ::= (func async? (param "<label>" <valtype>)* (result <valtype>)?)
572572
componenttype ::= (component <componentdecl>*)
573573
instancetype ::= (instance <instancedecl>*)
574574
componentdecl ::= <importdecl>
@@ -737,11 +737,10 @@ are useful for:
737737

738738
A `future<T>` asynchronously delivers exactly one `T` value from a source to a
739739
destination, unless the destination signals that it doesn't want the `T` value
740-
any more. Because [all imports can be called asynchronously][summary], futures
741-
are not necessary to express a traditional `async` function -- all functions
742-
are effectively `async`. Instead futures are useful in more advanced scenarios
743-
where a parameter or result value may not be ready at the same time as the
744-
other synchronous parameters or results.
740+
any more. When function types contain the [`async`][summary] effect, the return
741+
value is returned asynchronously if the call blocks (and is returned
742+
synchronously otherwise) and thus there is no need to *additionally* return a
743+
`future<T>` value unless *additional* asynchronous signalling is required.
745744

746745
The `T` element type of `stream` and `future` is an optional `valtype`. As with
747746
variant-case payloads and function results, when `T` is absent, the "value(s)"
@@ -793,7 +792,10 @@ shared-nothing functions, resources, components, and component instances:
793792

794793
The `func` type constructor describes a component-level function definition
795794
that takes a list of `valtype` parameters with [strongly-unique] names and
796-
optionally returns a `valtype`.
795+
optionally returns a `valtype`. The optional `async` effect type indicates that
796+
calling the function may block (because the function transitively calls a
797+
blocking built-in like `waitable-set.wait` or blocks on an imported `async`
798+
function).
797799

798800
The `resource` type constructor creates a fresh type for each instance of the
799801
containing component (with "freshness" and its interaction with general
@@ -1413,7 +1415,6 @@ dynamically interact with Canonical ABI entities like resources,
14131415
canon ::= ...
14141416
| (canon resource.new <typeidx> (core func <id>?))
14151417
| (canon resource.drop <typeidx> (core func <id>?))
1416-
| (canon resource.drop <typeidx> async (core func <id>?)) 🚝
14171418
| (canon resource.rep <typeidx> (core func <id>?))
14181419
| (canon context.get <valtype> <u32> (core func <id>?)) 🔀
14191420
| (canon context.set <valtype> <u32> (core func <id>?)) 🔀
@@ -1482,32 +1483,16 @@ For details, see [`canon_resource_new`] in the Canonical ABI explainer.
14821483

14831484
###### `resource.drop`
14841485

1485-
When the `async` immediate is false:
1486-
14871486
| Synopsis | |
14881487
| -------------------------- | ---------------------------------- |
14891488
| Approximate WIT signature | `func<T>(t: T)` |
14901489
| Canonical ABI signature | `[t:i32] -> []` |
14911490

1492-
🚝 When the `async` immediate is true:
1493-
1494-
| Synopsis | |
1495-
| -------------------------- | ---------------------------------- |
1496-
| Approximate WIT signature | `func<T>(t: T) -> option<subtask>` |
1497-
| Canonical ABI signature | `[t:i32] -> [i32]` |
1498-
14991491
The `resource.drop` built-in drops a resource handle `t` (with resource type
15001492
`T`). If the dropped handle owns the resource, the resource's `dtor` is called,
15011493
if present. Validation only allows `resource.rep T` to be used within the
15021494
component that defined `T`.
15031495

1504-
When the `async` immediate is true, the returned value indicates whether the
1505-
drop completed eagerly, or if not, identifies the in-progress drop.
1506-
1507-
In the Canonical ABI, the returned `i32` is either `0` (if the drop completed
1508-
eagerly) or the index of the in-progress drop subtask (representing the
1509-
in-progress `dtor` call).
1510-
15111496
For details, see [`canon_resource_drop`] in the Canonical ABI explainer.
15121497

15131498
###### `resource.rep`
@@ -2521,12 +2506,9 @@ importname ::= <exportname>
25212506
| <urlname>
25222507
| <hashname>
25232508
plainname ::= <label>
2524-
| '[async]' <label> 🔀
25252509
| '[constructor]' <label>
25262510
| '[method]' <label> '.' <label>
2527-
| '[async method]' <label> '.' <label> 🔀
25282511
| '[static]' <label> '.' <label>
2529-
| '[async static]' <label> '.' <label> 🔀
25302512
label ::= <fragment>
25312513
| <label> '-' <fragment>
25322514
fragment ::= <word>
@@ -2671,10 +2653,10 @@ annotations trigger additional type-validation rules (listed in
26712653
* Similarly, an import or export named `[method]R.foo` must be a function whose
26722654
first parameter must be `(param "self" (borrow $R))`.
26732655

2674-
When a function is annotated with `async`, bindings generators are expected to
2656+
When a function's type is `async`, bindings generators are expected to
26752657
emit whatever asynchronous language construct is appropriate (such as an
2676-
`async` function in JS, Python or Rust). Note the absence of
2677-
`[async constructor]`. See the [concurrency explainer] for more details.
2658+
`async` function in JS, Python or Rust). See the [concurrency explainer] for
2659+
more details.
26782660

26792661
The `label` production used inside `plainname` as well as the labels of
26802662
`record` and `variant` types are required to have [kebab case]. The reason for
@@ -2806,7 +2788,8 @@ Thus, the following names are strongly-unique:
28062788
* `foo`, `foo-bar`, `[constructor]foo`, `[method]foo.bar`, `[method]foo.baz`
28072789

28082790
but attempting to add *any* of the following names would be a validation error:
2809-
* `foo`, `foo-BAR`, `[constructor]foo-BAR`, `[method]foo.foo`, `[async]foo`, `[method]foo.BAR`
2791+
* `foo`, `foo-BAR`, `[constructor]foo-BAR`, `[method]foo.foo`,
2792+
`[static]foo.foo`, `[method]foo.BAR`
28102793

28112794
Note that additional validation rules involving types apply to names with
28122795
annotations. For example, the validation rules for `[constructor]foo` require

design/mvp/WIT.md

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,16 +1450,11 @@ named-type-list ::= ϵ
14501450
named-type ::= id ':' ty
14511451
```
14521452

1453-
The optional `async` hint in a WIT function type indicates that the callee
1454-
is expected to block and thus the caller should emit whatever asynchronous
1455-
language bindings are appropriate (e.g., in JS, Python, C# or Rust, an `async`
1456-
WIT function would emit an `async` JS/Python/C#/Rust function). Because `async`
1457-
is just a hint and not enforced by the runtime, it is technically possible for
1458-
a non-`async` callee to block. In that case, though, it is the *callee's* fault
1459-
for any resultant loss of concurrency, not the caller's. Thus, `async` is
1460-
primarily intended to document expectations in a way that can be taken
1461-
advantage of by bindings generators. (For more details, see the [concurrency
1462-
explainer](Concurrency.md).)
1453+
The optional `async` prefix in a WIT function type indicates that the callee
1454+
may block and thus the caller should emit asynchronous source-language bindings
1455+
if appropriate (e.g., in JS, Python, C# or Rust, an `async` WIT function would
1456+
emit an `async` JS/Python/C#/Rust function). (For more details, see the
1457+
[concurrency explainer](Concurrency.md).)
14631458

14641459

14651460
## Item: `use`
@@ -1689,8 +1684,8 @@ resource-method ::= func-item
16891684
| 'constructor' param-list ';'
16901685
```
16911686

1692-
The optional `async` hint on `static` functions has the same meaning as
1693-
in a non-`static` `func-item`.
1687+
The optional `async` on `static` functions has the same meaning as in a
1688+
non-`static` `func-item`.
16941689

16951690
The syntax for handle types is presented [below](#handles).
16961691

0 commit comments

Comments
 (0)