Skip to content

Commit feb7e91

Browse files
committed
Only allow async ABI options for async-typed functions
1 parent 669d494 commit feb7e91

6 files changed

Lines changed: 79 additions & 74 deletions

File tree

design/mvp/CanonicalABI.md

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -777,15 +777,22 @@ class Task(Supertask):
777777
self.threads = []
778778
```
779779

780-
The `Task.needs_exclusive` predicate returns whether the Canonical ABI options
781-
indicate that the core wasm being executed does not expect to be reentered
782-
(e.g., because the code is using a single global linear memory shadow stack).
783-
Concretely, this is assumed to be the case when core wasm is lifted
784-
synchronously or with `async callback`. This predicate is used by the other
785-
`Task` methods to determine whether to acquire/release the component instance's
786-
`exclusive` lock.
780+
The `Task.needs_exclusive` method returns whether an `async`-typed function's
781+
ABI options indicate that the Core WebAssembly code requires serialized
782+
execution (with the common reason being that there is a single, global linear
783+
memory shadow stack). This serialized execution is implemented by
784+
acquiring/releasing the component-instance-wide `exclusive` lock before/after
785+
executing Core WebAssembly code executing on the task's *implicit thread*
786+
(explicit threads created by `thread.new-indirect` ignore the `exclusive` lock).
787+
Specifically, sync- and stackless-async-lifted (`async callback`) functions
788+
require the `exclusive` lock and stackful-async-lifted (`async`) functions
789+
ignore the `exclusive` lock (just like explicit threads). Note that
790+
non-`async`-typed functions' implicit threads also ignore the `exclusive` lock
791+
since they must complete synchronously without blocking and thus don't have to
792+
worry about non-LIFO stack interleaving.
787793
```python
788794
def needs_exclusive(self):
795+
assert(self.ft.async_)
789796
return not self.opts.async_ or self.opts.callback
790797
```
791798

@@ -3452,7 +3459,9 @@ present, is validated as such:
34523459
* if `realloc` is present then `memory` must be present
34533460
* `post-return` - only allowed on [`canon lift`](#canon-lift), which has rules
34543461
for validation
3455-
* 🔀 `async` - cannot be present with `post-return`
3462+
* 🔀 `async` - is only allowed when used with an `async` function type in
3463+
[`canon lift`](#canon-lift) or [`canon lower`](#canon-lower) and cannot be
3464+
present with `post-return`
34563465
* 🔀,not(🚟) `async` - `callback` must also be present. Note that with the 🚟
34573466
feature (the "stackful" ABI), this restriction is lifted.
34583467
* 🔀 `callback` - the function has type `(func (param i32 i32 i32) (result i32))`
@@ -3580,7 +3589,7 @@ function (specified as a `funcidx` immediate in `canon lift`) until the
35803589
[packed] = call_and_trap_on_throw(callee, flat_args)
35813590
code,si = unpack_callback_result(packed)
35823591
while code != CallbackCode.EXIT:
3583-
assert(inst.exclusive is task)
3592+
assert(task.needs_exclusive() and inst.exclusive is task)
35843593
inst.exclusive = None
35853594
match code:
35863595
case CallbackCode.YIELD:

design/mvp/Concurrency.md

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

77-
The new async ABI can be used alongside or instead of the existing Preview 2
78-
"sync ABI" to call or implement *any* WIT function type. When *calling* an
79-
imported function via the async ABI, if the callee [blocks](#blocking), control
80-
flow is returned immediately to the caller, and the callee continues executing
81-
concurrently. When *implementing* an exported function via the async ABI,
82-
multiple concurrent export calls are allowed to be made by the caller.
83-
Critically, both sync-ABI-calls-async-ABI and async-ABI-calls-sync-ABI pairings
84-
have well-defined, composable behavior for both inter-component and
85-
intra-component calls.
86-
8777
In addition to adding a new async *ABI* for use by the language's compiler and
8878
runtime, the Component Model also adds a new `async` [effect type] that can be
8979
added to function types (in both WIT and raw component function type
@@ -103,6 +93,16 @@ invariant is necessary to allow non-`async` component exports to be called in
10393
synchronous contexts (like event listeners, callbacks, getters, setters and
10494
constructors).
10595

96+
The new async ABI can be used alongside or instead of the existing Preview 2
97+
"sync ABI" to call or implement any `async`-typed functions. When *calling* an
98+
imported function via the async ABI, if the `async` callee [blocks](#blocking),
99+
control flow is returned immediately to the caller, and the callee continues
100+
executing concurrently. When *implementing* an `async` function via the async
101+
ABI, multiple concurrent export calls are allowed to be made by the caller.
102+
Critically, both sync-ABI-calls-async-ABI and async-ABI-calls-sync-ABI pairings
103+
have well-defined, composable behavior for both inter-component and
104+
intra-component calls.
105+
106106
Because `async` function exports may be implemented with the *sync* ABI and
107107
then call `async` function imports using the *sync* ABI, traditional sync code
108108
can compile directly to components exporting `async` functions without having
@@ -873,19 +873,12 @@ JS [top-level `await`] or I/O in C++ constructors executing during `start`.
873873

874874
## Async ABI
875875

876-
At an ABI level, native async in the Component Model defines for every WIT
877-
function an async-oriented core function signature that can be used instead of
878-
or in addition to the existing (Preview-2-defined) synchronous core function
879-
signature. This async-oriented core function signature is intended to be called
880-
or implemented by generated bindings which then map the low-level core async
881-
protocol to the languages' higher-level native concurrency features.
882-
883-
Note that *every* WIT-level function type can be lifted and lowered using the
884-
async (or sync) ABI. While calling a non-`async`-typed function import using
885-
the async ABI will never returned that the call "blocked" (as guaranteed by the
886-
Component Model trapping if the callee would have blocked), the async ABI is
887-
still allowed to be used (for the benefit of code generators that only want
888-
to think about one ABI).
876+
At an ABI level, native async in the Component Model defines for every
877+
`async`-typed function a non-blocking core function signature that can be
878+
used instead of or in addition to the existing (Preview-2-defined) synchronous
879+
core function signature. This non-blocking core function signature is intended
880+
to be called or implemented by generated bindings which then map the low-level
881+
core async protocol to the languages' higher-level native concurrency features.
889882

890883
### Async Import ABI
891884

design/mvp/Explainer.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,13 +1328,10 @@ be deallocated and destructors called. This immediate is always optional but,
13281328
if present, is validated to have parameters matching the callee's return type
13291329
and empty results.
13301330

1331-
🔀 The `async` option specifies that the component wants to make (for imports)
1332-
or support (for exports) multiple concurrent (asynchronous) calls. This option
1333-
can be applied to any component-level function type and changes the derived
1334-
Canonical ABI significantly. See the [concurrency explainer] for more details.
1335-
When a function signature contains a `future` or `stream`, validation of `canon
1336-
lower` requires the `async` option to be set (since a synchronous call to a
1337-
function using these types is highly likely to deadlock).
1331+
🔀 The `async` option may only be used with `async` function types and specifies
1332+
that the component wants to make (for imports) or support (for exports) multiple
1333+
concurrent (asynchronous) calls. This option changes the derived Canonical ABI
1334+
significantly; see the [concurrency explainer] for more details.
13381335

13391336
🔀 The `(callback ...)` option may only be present in `canon lift` when the
13401337
`async` option has also been set and specifies a core function that is

design/mvp/canonical-abi/definitions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ def __init__(self, ft, opts, inst, supertask, on_start, on_resolve):
449449
self.threads = []
450450

451451
def needs_exclusive(self):
452+
assert(self.ft.async_)
452453
return not self.opts.async_ or self.opts.callback
453454

454455
def may_block(self):
@@ -2104,7 +2105,7 @@ def thread_func():
21042105
[packed] = call_and_trap_on_throw(callee, flat_args)
21052106
code,si = unpack_callback_result(packed)
21062107
while code != CallbackCode.EXIT:
2107-
assert(inst.exclusive is task)
2108+
assert(task.needs_exclusive() and inst.exclusive is task)
21082109
inst.exclusive = None
21092110
match code:
21102111
case CallbackCode.YIELD:

test/async/cross-abi-calls.wast

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -175,62 +175,67 @@
175175
(export "task.return16" (func $task.return16))
176176
(export "task.return17" (func $task.return17))
177177
))))
178-
(func (export "sync-4-param") (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)
178+
(func (export "sync-4-param") async (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)
179179
(canon lift (core func $core "sync-4-param"))
180180
)
181-
(func (export "sync-5-param") (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64) (param "e" u32)
181+
(func (export "sync-5-param") async (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64) (param "e" u32)
182182
(canon lift (core func $core "sync-5-param"))
183183
)
184-
(func (export "sync-17-param") (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)
184+
(func (export "sync-17-param") async
185+
(param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)
185186
(param "e" u32) (param "f" u64) (param "g" f32) (param "h" f64)
186187
(param "i" u32) (param "j" u64) (param "k" f32) (param "l" f64)
187188
(param "m" u32) (param "n" u64) (param "o" f32) (param "p" f64)
188189
(param "q" u32)
189190
(canon lift (core func $core "sync-17-param") (memory $memory "mem") (realloc (func $memory "realloc")))
190191
)
191-
(func (export "sync-1-result") (result f64)
192+
(func (export "sync-1-result") async (result f64)
192193
(canon lift (core func $core "sync-1-result"))
193194
)
194-
(func (export "sync-16-result") (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64))
195+
(func (export "sync-16-result") async (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64))
195196
(canon lift (core func $core "sync-16-result") (memory $memory "mem"))
196197
)
197-
(func (export "sync-17-result") (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32))
198+
(func (export "sync-17-result") async (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32))
198199
(canon lift (core func $core "sync-17-result") (memory $memory "mem"))
199200
)
200-
(func (export "async-4-param") (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)
201+
(func (export "async-4-param") async
202+
(param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)
201203
(canon lift (core func $core "async-4-param") async (callback (func $core "unreachable-cb")))
202204
)
203-
(func (export "async-5-param") (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64) (param "e" u32)
205+
(func (export "async-5-param") async
206+
(param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64) (param "e" u32)
204207
(canon lift (core func $core "async-5-param") async (callback (func $core "unreachable-cb")))
205208
)
206-
(func (export "async-17-param") (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)
209+
(func (export "async-17-param") async
210+
(param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)
207211
(param "e" u32) (param "f" u64) (param "g" f32) (param "h" f64)
208212
(param "i" u32) (param "j" u64) (param "k" f32) (param "l" f64)
209213
(param "m" u32) (param "n" u64) (param "o" f32) (param "p" f64)
210214
(param "q" u32)
211215
(canon lift (core func $core "async-17-param") async (callback (func $core "unreachable-cb")) (memory $memory "mem") (realloc (func $memory "realloc")))
212216
)
213-
(func (export "async-1-result") (result f64)
217+
(func (export "async-1-result") async (result f64)
214218
(canon lift (core func $core "async-1-result") async (callback (func $core "unreachable-cb")))
215219
)
216-
(func (export "async-16-result") (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64))
220+
(func (export "async-16-result") async (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64))
217221
(canon lift (core func $core "async-16-result") async (callback (func $core "unreachable-cb")))
218222
)
219-
(func (export "async-17-result") (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32))
223+
(func (export "async-17-result") async (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32))
220224
(canon lift (core func $core "async-17-result") async (callback (func $core "unreachable-cb")) (memory $memory "mem") (realloc (func $memory "realloc")))
221225
)
222226
)
223227
(component $Bottom
224-
(import "func-4-param" (func $func-4-param (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)))
225-
(import "func-5-param" (func $func-5-param (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64) (param "e" u32)))
226-
(import "func-17-param" (func $func-17-param (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)
228+
(import "func-4-param" (func $func-4-param async (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)))
229+
(import "func-5-param" (func $func-5-param async (param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64) (param "e" u32)))
230+
(import "func-17-param" (func $func-17-param async
231+
(param "a" u32) (param "b" u64) (param "c" f32) (param "d" f64)
227232
(param "e" u32) (param "f" u64) (param "g" f32) (param "h" f64)
228233
(param "i" u32) (param "j" u64) (param "k" f32) (param "l" f64)
229234
(param "m" u32) (param "n" u64) (param "o" f32) (param "p" f64)
230235
(param "q" u32)))
231-
(import "func-1-result" (func $func-1-result (result f64)))
232-
(import "func-16-result" (func $func-16-result (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64))))
233-
(import "func-17-result" (func $func-17-result (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32))))
236+
(import "func-1-result" (func $func-1-result async (result f64)))
237+
(import "func-16-result" (func $func-16-result async (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64))))
238+
(import "func-17-result" (func $func-17-result async (result (tuple u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32 u64 f32 f64 u32))))
234239
(core module $Memory (memory (export "mem") 1))
235240
(core instance $memory (instantiate $Memory))
236241
(core module $Core
@@ -408,18 +413,18 @@
408413
(export "sync-17-result" (func $sync-17-result))
409414
(export "async-17-result" (func $async-17-result))
410415
))))
411-
(func (export "call-sync-4-param") (result u32) (canon lift (core func $core "call-sync-4-param")))
412-
(func (export "call-async-4-param") (result u32) (canon lift (core func $core "call-async-4-param")))
413-
(func (export "call-sync-5-param") (result u32) (canon lift (core func $core "call-sync-5-param")))
414-
(func (export "call-async-5-param") (result u32) (canon lift (core func $core "call-async-5-param")))
415-
(func (export "call-sync-17-param") (result u32) (canon lift (core func $core "call-sync-17-param")))
416-
(func (export "call-async-17-param") (result u32) (canon lift (core func $core "call-async-17-param")))
417-
(func (export "call-sync-1-result") (result u32) (canon lift (core func $core "call-sync-1-result")))
418-
(func (export "call-async-1-result") (result u32) (canon lift (core func $core "call-async-1-result")))
419-
(func (export "call-sync-16-result") (result u32) (canon lift (core func $core "call-sync-16-result")))
420-
(func (export "call-async-16-result") (result u32) (canon lift (core func $core "call-async-16-result")))
421-
(func (export "call-sync-17-result") (result u32) (canon lift (core func $core "call-sync-17-result")))
422-
(func (export "call-async-17-result") (result u32) (canon lift (core func $core "call-async-17-result")))
416+
(func (export "call-sync-4-param") async (result u32) (canon lift (core func $core "call-sync-4-param")))
417+
(func (export "call-async-4-param") async (result u32) (canon lift (core func $core "call-async-4-param")))
418+
(func (export "call-sync-5-param") async (result u32) (canon lift (core func $core "call-sync-5-param")))
419+
(func (export "call-async-5-param") async (result u32) (canon lift (core func $core "call-async-5-param")))
420+
(func (export "call-sync-17-param") async (result u32) (canon lift (core func $core "call-sync-17-param")))
421+
(func (export "call-async-17-param") async (result u32) (canon lift (core func $core "call-async-17-param")))
422+
(func (export "call-sync-1-result") async (result u32) (canon lift (core func $core "call-sync-1-result")))
423+
(func (export "call-async-1-result") async (result u32) (canon lift (core func $core "call-async-1-result")))
424+
(func (export "call-sync-16-result") async (result u32) (canon lift (core func $core "call-sync-16-result")))
425+
(func (export "call-async-16-result") async (result u32) (canon lift (core func $core "call-async-16-result")))
426+
(func (export "call-sync-17-result") async (result u32) (canon lift (core func $core "call-sync-17-result")))
427+
(func (export "call-async-17-result") async (result u32) (canon lift (core func $core "call-async-17-result")))
423428
)
424429
(instance $top (instantiate $Top))
425430
(instance $bottom-to-sync (instantiate $Bottom

test/async/trap-on-reenter.wast

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
)
1313
)
1414
(core instance $core_inner (instantiate $CoreInner))
15-
(func $a (canon lift
15+
(func $a async (canon lift
1616
(core func $core_inner "a")
1717
async (callback (func $core_inner "a-cb"))
1818
))
1919

2020
(component $Child
21-
(import "a" (func $a))
21+
(import "a" (func $a async))
2222

2323
(core module $Memory (memory (export "mem") 1))
2424
(core instance $memory (instantiate $Memory))
@@ -37,7 +37,7 @@
3737
(core instance $core_child (instantiate $CoreChild (with "" (instance
3838
(export "a" (func $a'))
3939
))))
40-
(func (export "b") (canon lift
40+
(func (export "b") async (canon lift
4141
(core func $core_child "b")
4242
async (callback (func $core_child "b-cb"))
4343
))
@@ -57,7 +57,7 @@
5757
(core instance $core_outer (instantiate $CoreOuter (with "" (instance
5858
(export "b" (func $b))
5959
))))
60-
(func $c (export "c") (canon lift
60+
(func $c (export "c") async (canon lift
6161
(core func $core_outer "c")
6262
async (callback (func $core_outer "c-cb"))
6363
))

0 commit comments

Comments
 (0)