Skip to content

Commit 83d6b09

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

4 files changed

Lines changed: 40 additions & 40 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:

0 commit comments

Comments
 (0)