Skip to content

Commit 3ea396c

Browse files
committed
CABI: improve and add cooperative thread built-ins
1 parent a0a9398 commit 3ea396c

6 files changed

Lines changed: 656 additions & 266 deletions

File tree

design/mvp/CanonicalABI.md

Lines changed: 193 additions & 155 deletions
Large diffs are not rendered by default.

design/mvp/Concurrency.md

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -386,34 +386,36 @@ initially in a "suspended" state and must be explicitly "resumed" using one of
386386
the following 3 thread built-ins. Once the thread is resumed, the thread can
387387
learn its own index by calling the [`thread.index`] built-in.
388388

389+
TODO: work in [`thread.{suspend,yield}-then-promote`] below
390+
389391
A suspended thread (identified by thread-table index) can be resumed at some
390392
nondeterministic point in future via the [`thread.resume-later`] built-in. In
391-
contrast, the [`thread.yield-to`] built-in switches execution to the given
392-
thread immediately, leaving the *calling* thread to be resumed at some
393-
nondeterministic point in the future. Lastly, the [`thread.switch-to`]
394-
built-in switches execution to the given thread immediately, like `yield-to`,
395-
but leaves the calling thread in the "suspended" state. These three functions
396-
can be used to resume both newly-created threads as well as threads that
397-
executed and then suspended.
393+
contrast, the [`thread.yield-then-resume`] built-in switches execution to the
394+
given thread immediately, leaving the *calling* thread to be resumed at some
395+
nondeterministic point in the future. Lastly, the [`thread.suspend-then-resume`]
396+
built-in switches execution to the given thread immediately, like
397+
`thread.yield-then-resume`, but leaves the calling thread in the "suspended"
398+
state. These three functions can be used to resume both newly-created threads as
399+
well as threads that executed and then suspended.
398400

399401
In addition to threads entering the suspended state via `thread.new-indirect`
400-
and `thread.switch-to`, threads can also explicitly suspend themselves via the
401-
[`thread.suspend`] built-in. Thus, there are three ways a thread *enters* the
402-
suspended state and three ways a thread *exits* the suspended state (with
403-
`thread.switch-to` serving in both categories). Together, these 5 thread
404-
built-ins support both the "green thread" [use cases](#goals) where Core
405-
WebAssembly code running inside the component wants to fully control thread
406-
scheduling (via `thread.switch-to` and `thread.suspend`) as well as the "host
407-
thread" use cases where the Core WebAssembly code wants to let the containing
408-
runtime nondeterministically schedule threads (via `thread.resume-later` or
409-
`thread.yield-to`).
402+
and `thread.suspend-then-resume`, threads can also explicitly suspend themselves
403+
via the [`thread.suspend`] built-in. Thus, there are three ways a thread
404+
*enters* the suspended state and three ways a thread *exits* the suspended state
405+
(with `thread.suspend-then-resume` serving in both categories). Together, these
406+
5 thread built-ins support both the "green thread" [use cases](#goals) where
407+
Core WebAssembly code running inside the component wants to fully control thread
408+
scheduling (via `thread.suspend-then-resume` and `thread.suspend`) as well as
409+
the "host thread" use cases where the Core WebAssembly code wants to let the
410+
containing runtime nondeterministically schedule threads (via
411+
`thread.resume-later` or `thread.yield-then-resume`).
410412

411413
Lastly, since threads are cooperative, there is a [`thread.yield`] built-in
412414
that can be called in the middle of long-running computations to allow the
413415
runtime to nondeterministically switch execution to another thread.
414416
`thread.yield` is equivalent to (but obviously more efficient than) creating a
415417
new thread with a no-op function (via `thread.new-indirect`) and then yielding
416-
to it (via `thread.yield-to`).
418+
to it (via `thread.yield-then-resume`).
417419

418420
### Thread-Local Storage
419421

@@ -466,6 +468,8 @@ For more information, see [`context.get`] in the AST explainer.
466468

467469
### Blocking
468470

471+
TODO: this section has to be updated
472+
469473
When a thread calls an import using the async ABI, the Component Model
470474
guarantees that if the callee **blocks**, control flow is immediately returned
471475
back to the caller. When the callee is implemented by the *host*, what counts as
@@ -506,9 +510,6 @@ type does not contain `async`.
506510
that suspends the current thread to produce a continuation that can be resumed
507511
once the reason for blocking is addressed.
508512

509-
The [Canonical ABI explainer] defines the above behavior more precisely; search
510-
for `may_block` to see all the relevant points.
511-
512513
### Waitables and Waitable Sets
513514

514515
When an `async`-typed function is called with the async ABI and the call
@@ -678,12 +679,12 @@ degree of concurrency than synchronous exports. Stackful async exports ignore
678679
the lock entirely and thus achieve the highest degree of (cooperative)
679680
concurrency.
680681

681-
Since non-`async` functions are not allowed to block (including due to
682-
backpressure) and also don't pile up like `async` functions, non-`async`
683-
functions ignore backpressure (explicit and implicit) entirely. If a
684-
component exports a mix of `async` and non-`async` functions, code generation
685-
must therefore be prepared to handle non-`async` functions executing at
686-
any cooperative yield point, even in the middle of a `callback`.
682+
Since non-`async` functions are not allowed to [block](#blocking) (including due
683+
to backpressure) and also don't pile up like `async` functions, non-`async`
684+
functions ignore backpressure (explicit and implicit) entirely. If a component
685+
exports a mix of `async` and non-`async` functions, code generation must
686+
therefore be prepared to handle non-`async` functions executing at any
687+
cooperative yield point, even in the middle of a `callback`.
687688

688689
Once a task is allowed to start according to these backpressure rules, its
689690
arguments are lowered into the callee's linear memory and the task is in
@@ -713,9 +714,7 @@ the readable end passed for `in`) and `stream.write`s (of the writable end it
713714
`stream.new`ed) before exiting the task.
714715

715716
Once `task.return` is called, the task is in the "returned" state. Calling
716-
`task.return` when not in the "started" state traps. Once in a "returned" state,
717-
non-`async` functions may block using cooperative threads that were created
718-
before the synchronous task's implicit thread returned.
717+
`task.return` when not in the "started" state traps.
719718

720719
### Borrows
721720

@@ -1538,13 +1537,15 @@ comes after:
15381537
[`waitable-set.wait`]: Explainer.md#-waitable-setwait
15391538
[`waitable-set.poll`]: Explainer.md#-waitable-setpoll
15401539
[`waitable.join`]: Explainer.md#-waitablejoin
1541-
[`thread.new-indirect`]: Explainer.md#-threadnew-indirect
15421540
[`thread.index`]: Explainer.md#-threadindex
1543-
[`thread.suspend`]: Explainer.md#-threadsuspend
1544-
[`thread.switch-to`]: Explainer.md#-threadswitch-to
1541+
[`thread.new-indirect`]: Explainer.md#-threadnew-indirect
15451542
[`thread.resume-later`]: Explainer.md#-threadresume-later
1546-
[`thread.yield-to`]: Explainer.md#-threadyield-to
1543+
[`thread.suspend`]: Explainer.md#-threadsuspend
15471544
[`thread.yield`]: Explainer.md#-threadyield
1545+
[`thread.suspend-then-resume`]: Explainer.md#-threadsuspend-then-resume
1546+
[`thread.yield-then-resume`]: Explainer.md#-threadyield-then-resume
1547+
[`thread.suspend-then-promote`]: Explainer.md#-threadsuspend-then-promote
1548+
[`thread.yield-then-promote`]: Explainer.md#-threadyield-then-promote
15481549
[`{stream,future}.new`]: Explainer.md#-streamnew-and-futurenew
15491550
[`{stream,future}.{read,write}`]: Explainer.md#-streamread-and-streamwrite
15501551
[`stream.cancel-write`]: Explainer.md#-streamcancel-read-streamcancel-write-futurecancel-read-and-futurecancel-write

design/mvp/Explainer.md

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,8 +1468,10 @@ canon ::= ...
14681468
| (canon thread.resume-later (core func <id>?)) 🧵
14691469
| (canon thread.suspend cancellable? (core func <id>?)) 🧵
14701470
| (canon thread.yield cancellable? (core func <id>?)) 🔀
1471-
| (canon thread.switch-to cancellable? (core func <id>?)) 🧵
1472-
| (canon thread.yield-to cancellable? (core func <id>?)) 🧵
1471+
| (canon thread.suspend-then-resume cancellable? (core func <id>?)) 🧵
1472+
| (canon thread.yield-then-resume cancellable? (core func <id>?)) 🧵
1473+
| (canon thread.suspend-then-promote cancellable? (core func <id>?)) 🧵
1474+
| (canon thread.yield-then-promote cancellable? (core func <id>?)) 🧵
14731475
| (canon error-context.new <canonopt>* (core func <id>?)) 📝
14741476
| (canon error-context.debug-message <canonopt>* (core func <id>?)) 📝
14751477
| (canon error-context.drop (core func <id>?)) 📝
@@ -2089,7 +2091,7 @@ extended for [GC].
20892091

20902092
As explained in the [concurrency explainer], a thread created by
20912093
`thread.new-indirect` is initially in a suspended state and must be resumed
2092-
eagerly or lazily by [`thread.yield-to`](#-threadyield-to) or
2094+
eagerly or lazily by [`thread.yield-then-resume`](#-threadyield-then-resume) or
20932095
[`thread.resume-later`](#-threadresume-later), resp., to begin execution.
20942096

20952097
For details, see [Thread Built-ins] in the concurrency explainer and
@@ -2122,9 +2124,6 @@ explicitly resumed by some other thread calling a built-in such as
21222124
the current task was [cancelled] by the caller; otherwise, `thread.suspend`
21232125
always returns `false`.
21242126

2125-
A non-`async`-typed function export that has not yet returned a value traps if
2126-
it transitively attempts to call `thread.suspend`.
2127-
21282127
For details, see [Thread Built-ins] in the concurrency explainer and
21292128
[`canon_thread_suspend`] in the Canonical ABI explainer.
21302129

@@ -2138,52 +2137,73 @@ For details, see [Thread Built-ins] in the concurrency explainer and
21382137
The `thread.yield` built-in allows the runtime to potentially switch to any
21392138
other thread in the "ready" state, enabling a long-running computation to
21402139
cooperatively interleave execution without specifically requesting another
2141-
thread to be resumed (as with `thread.yield-to`). If `cancellable` is set,
2142-
`thread.yield` returns whether the current task was [cancelled] by the caller;
2143-
otherwise, `thread.yield` always returns `false`.
2144-
2145-
If `thread.yield` is called from a synchronous- or `async callback`-lifted
2146-
export, it returns immediately without blocking (instead of trapping, as with
2147-
other possibly-blocking operations like `waitable-set.wait`). This is because,
2148-
unlike other built-ins, `thread.yield` may be scattered liberally throughout
2149-
code that might show up in the transitive call tree of a synchronous function
2150-
call.
2140+
thread to be resumed (as with `thread.yield-then-resume`). If `cancellable` is
2141+
set, `thread.yield` returns whether the current task was [cancelled] by the
2142+
caller; otherwise, `thread.yield` always returns `false`.
21512143

21522144
For details, see [Thread Built-ins] in the concurrency explainer and
21532145
[`canon_thread_yield`] in the Canonical ABI explainer.
21542146

2155-
###### 🧵 `thread.switch-to`
2147+
###### 🧵 `thread.suspend-then-resume`
2148+
2149+
| Synopsis | |
2150+
| -------------------------- | --------------------------------------- |
2151+
| Approximate WIT signature | `func<cancellable?>(t: thread) -> bool` |
2152+
| Canonical ABI signature | `[t:i32] -> [i32]` |
2153+
2154+
The `thread.suspend-then-resume` built-in suspends the [current thread] and
2155+
immediately resumes execution of the thread `t`, trapping if `t` is not in a
2156+
"suspended" state. If `cancellable` is set, `thread.suspend-then-resume` returns
2157+
whether the current task was [cancelled] by the caller; otherwise,
2158+
`thread.suspend-then-resume` always returns `false`.
2159+
2160+
For details, see [Thread Built-ins] in the concurrency explainer and
2161+
[`canon_thread_suspend_then_resume`] in the Canonical ABI explainer.
2162+
2163+
###### 🧵 `thread.yield-then-resume`
21562164

21572165
| Synopsis | |
21582166
| -------------------------- | --------------------------------------- |
21592167
| Approximate WIT signature | `func<cancellable?>(t: thread) -> bool` |
21602168
| Canonical ABI signature | `[t:i32] -> [i32]` |
21612169

2162-
The `thread.switch-to` built-in suspends the [current thread] and immediately
2163-
resumes execution of the thread `t`, trapping if `t` is not in a "suspended"
2164-
state. If `cancellable` is set, `thread.switch-to` returns whether the current
2165-
task was [cancelled] by the caller; otherwise, `thread.switch-to` always returns
2166-
`false`.
2170+
The `thread.yield-then-resume` built-in immediately resumes execution of the
2171+
thread `t`, (trapping if `t` is not in a "suspended" state) leaving the [current
2172+
thread] in a "ready" state so that the runtime can nondeterministically resume
2173+
the current thread at some point in the future. If `cancellable` is set,
2174+
`thread.yield-then-resume` returns whether the current task was [cancelled] by
2175+
the caller; otherwise, `thread.yield-then-resume` always returns `false`.
21672176

21682177
For details, see [Thread Built-ins] in the concurrency explainer and
2169-
[`canon_thread_switch_to`] in the Canonical ABI explainer.
2178+
[`canon_thread_yield_then_resume`] in the Canonical ABI explainer.
21702179

2171-
###### 🧵 `thread.yield-to`
2180+
###### 🧵 `thread.suspend-then-promote`
21722181

21732182
| Synopsis | |
21742183
| -------------------------- | --------------------------------------- |
21752184
| Approximate WIT signature | `func<cancellable?>(t: thread) -> bool` |
21762185
| Canonical ABI signature | `[t:i32] -> [i32]` |
21772186

2178-
The `thread.yield-to` built-in immediately resumes execution of the thread `t`,
2179-
(trapping if `t` is not in a "suspended" state) leaving the [current thread] in
2180-
a "ready" state so that the runtime can nondeterministically resume the current
2181-
thread at some point in the future. If `cancellable` is set, `thread.yield-to`
2182-
returns whether the current task was [cancelled] by the caller; otherwise,
2183-
`thread.yield-to` always returns `false`.
2187+
The `thread.suspend-then-promote` built-in immediately resumes execution of the
2188+
thread `t` if `t` is in a "ready" state, in any case leaving the current thread
2189+
in a "suspended" state.
21842190

21852191
For details, see [Thread Built-ins] in the concurrency explainer and
2186-
[`canon_thread_yield_to`] in the Canonical ABI explainer.
2192+
[`canon_thread_suspend_then_promote`] in the Canonical ABI explainer.
2193+
2194+
###### 🧵 `thread.yield-then-promote`
2195+
2196+
| Synopsis | |
2197+
| -------------------------- | --------------------------------------- |
2198+
| Approximate WIT signature | `func<cancellable?>(t: thread) -> bool` |
2199+
| Canonical ABI signature | `[t:i32] -> [i32]` |
2200+
2201+
The `thread.yield-then-promote` built-in immediately resumes execution of the
2202+
thread `t` if `t` is in a "ready" state, in any case leaving the current thread
2203+
in a "ready" state.
2204+
2205+
For details, see [Thread Built-ins] in the concurrency explainer and
2206+
[`canon_thread_yield_then_promote`] in the Canonical ABI explainer.
21872207

21882208
###### 🧵② `thread.spawn-ref`
21892209

@@ -3283,11 +3303,13 @@ For some use-case-focused, worked examples, see:
32833303
[`canon_error_context_drop`]: CanonicalABI.md#-canon-error-contextdrop
32843304
[`canon_thread_index`]: CanonicalABI.md#-canon-threadindex
32853305
[`canon_thread_new_indirect`]: CanonicalABI.md#-canon-threadnew-indirect
3286-
[`canon_thread_suspend`]: CanonicalABI.md#-canon-threadsuspend
3287-
[`canon_thread_switch_to`]: CanonicalABI.md#-canon-threadswitch-to
32883306
[`canon_thread_resume_later`]: CanonicalABI.md#-canon-threadresume-later
3289-
[`canon_thread_yield_to`]: CanonicalABI.md#-canon-threadyield-to
3307+
[`canon_thread_suspend`]: CanonicalABI.md#-canon-threadsuspend
32903308
[`canon_thread_yield`]: CanonicalABI.md#-canon-threadyield
3309+
[`canon_thread_suspend_then_resume`]: CanonicalABI.md#-canon-threadsuspend-then-resume
3310+
[`canon_thread_yield_then_resume`]: CanonicalABI.md#-canon-threadyield-then-resume
3311+
[`canon_thread_suspend_then_promote`]: CanonicalABI.md#-canon-threadsuspend-then-promote
3312+
[`canon_thread_yield_then_promote`]: CanonicalABI.md#-canon-threadyield-then-promote
32913313
[`canon_thread_spawn_ref`]: CanonicalABI.md#-canon-threadspawn-ref
32923314
[`canon_thread_spawn_indirect`]: CanonicalABI.md#-canon-threadspawn-indirect
32933315
[`canon_thread_available_parallelism`]: CanonicalABI.md#-canon-threadavailable_parallelism

0 commit comments

Comments
 (0)