Skip to content

Commit 6c3d8a2

Browse files
committed
CABI: improve and add cooperative thread built-ins
1 parent a714897 commit 6c3d8a2

6 files changed

Lines changed: 789 additions & 305 deletions

File tree

design/mvp/CanonicalABI.md

Lines changed: 277 additions & 183 deletions
Large diffs are not rendered by default.

design/mvp/Concurrency.md

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -386,34 +386,37 @@ 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: find a place to put [`thread.exit`]
390+
TODO: work in [`thread.{suspend,yield}-then-promote`] below
391+
389392
A suspended thread (identified by thread-table index) can be resumed at some
390393
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.
394+
contrast, the [`thread.yield-then-resume`] built-in switches execution to the
395+
given thread immediately, leaving the *calling* thread to be resumed at some
396+
nondeterministic point in the future. Lastly, the [`thread.suspend-then-resume`]
397+
built-in switches execution to the given thread immediately, like
398+
`thread.yield-then-resume`, but leaves the calling thread in the "suspended"
399+
state. These three functions can be used to resume both newly-created threads as
400+
well as threads that executed and then suspended.
398401

399402
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`).
403+
and `thread.suspend-then-resume`, threads can also explicitly suspend themselves
404+
via the [`thread.suspend`] built-in. Thus, there are three ways a thread
405+
*enters* the suspended state and three ways a thread *exits* the suspended state
406+
(with `thread.suspend-then-resume` serving in both categories). Together, these
407+
5 thread built-ins support both the "green thread" [use cases](#goals) where
408+
Core WebAssembly code running inside the component wants to fully control thread
409+
scheduling (via `thread.suspend-then-resume` and `thread.suspend`) as well as
410+
the "host thread" use cases where the Core WebAssembly code wants to let the
411+
containing runtime nondeterministically schedule threads (via
412+
`thread.resume-later` or `thread.yield-then-resume`).
410413

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

418421
### Thread-Local Storage
419422

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

467470
### Blocking
468471

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

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

514516
When an `async`-typed function is called with the async ABI and the call
@@ -678,12 +680,12 @@ degree of concurrency than synchronous exports. Stackful async exports ignore
678680
the lock entirely and thus achieve the highest degree of (cooperative)
679681
concurrency.
680682

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

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

715717
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.
718+
`task.return` when not in the "started" state traps.
719719

720720
### Borrows
721721

@@ -1538,13 +1538,16 @@ comes after:
15381538
[`waitable-set.wait`]: Explainer.md#-waitable-setwait
15391539
[`waitable-set.poll`]: Explainer.md#-waitable-setpoll
15401540
[`waitable.join`]: Explainer.md#-waitablejoin
1541-
[`thread.new-indirect`]: Explainer.md#-threadnew-indirect
15421541
[`thread.index`]: Explainer.md#-threadindex
1543-
[`thread.suspend`]: Explainer.md#-threadsuspend
1544-
[`thread.switch-to`]: Explainer.md#-threadswitch-to
1542+
[`thread.exit`]: Explainer.md#-threadexit
1543+
[`thread.new-indirect`]: Explainer.md#-threadnew-indirect
15451544
[`thread.resume-later`]: Explainer.md#-threadresume-later
1546-
[`thread.yield-to`]: Explainer.md#-threadyield-to
1545+
[`thread.suspend`]: Explainer.md#-threadsuspend
15471546
[`thread.yield`]: Explainer.md#-threadyield
1547+
[`thread.suspend-then-resume`]: Explainer.md#-threadsuspend-then-resume
1548+
[`thread.yield-then-resume`]: Explainer.md#-threadyield-then-resume
1549+
[`thread.suspend-then-promote`]: Explainer.md#-threadsuspend-then-promote
1550+
[`thread.yield-then-promote`]: Explainer.md#-threadyield-then-promote
15481551
[`{stream,future}.new`]: Explainer.md#-streamnew-and-futurenew
15491552
[`{stream,future}.{read,write}`]: Explainer.md#-streamread-and-streamwrite
15501553
[`stream.cancel-write`]: Explainer.md#-streamcancel-read-streamcancel-write-futurecancel-read-and-futurecancel-write

design/mvp/Explainer.md

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,12 +1464,15 @@ canon ::= ...
14641464
| (canon future.drop-readable <typeidx> (core func <id>?)) 🔀
14651465
| (canon future.drop-writable <typeidx> (core func <id>?)) 🔀
14661466
| (canon thread.index (core func <id>?)) 🧵
1467+
| (canon thread.exit (core func <id>?)) 🧵
14671468
| (canon thread.new-indirect <typeidx> <core:tableidx> (core func <id>?)) 🧵
14681469
| (canon thread.resume-later (core func <id>?)) 🧵
14691470
| (canon thread.suspend cancellable? (core func <id>?)) 🧵
14701471
| (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>?)) 🧵
1472+
| (canon thread.suspend-then-resume cancellable? (core func <id>?)) 🧵
1473+
| (canon thread.yield-then-resume cancellable? (core func <id>?)) 🧵
1474+
| (canon thread.suspend-then-promote cancellable? (core func <id>?)) 🧵
1475+
| (canon thread.yield-then-promote cancellable? (core func <id>?)) 🧵
14731476
| (canon error-context.new <canonopt>* (core func <id>?)) 📝
14741477
| (canon error-context.debug-message <canonopt>* (core func <id>?)) 📝
14751478
| (canon error-context.drop (core func <id>?)) 📝
@@ -2067,6 +2070,23 @@ learn their index via `thread.index`.
20672070
For details, see [Thread Built-ins] in the concurrency explainer and
20682071
[`canon_thread_index`] in the Canonical ABI explainer.
20692072

2073+
###### 🧵 `thread.exit`
2074+
2075+
| Synopsis | |
2076+
| -------------------------- | ---------- |
2077+
| Approximate WIT signature | `func()` |
2078+
| Canonical ABI signature | `[] -> []` |
2079+
2080+
The `thread.exit` built-in immediately exits the [current thread] without
2081+
running any Core WebAssembly handlers on the stack, as-if suspending with a
2082+
[stack-switching] tag not exposed to Core WebAssembly and then dropping the
2083+
generated `contref`. Thus, producer toolchains should be careful to only call
2084+
`thread.exit` when unwinding is not expected (e.g., when calling
2085+
`pthread_exit()`).
2086+
2087+
For details, see [Thread Built-ins] in the concurrency explainer and
2088+
[`canon_thread_exit`] in the Canonical ABI explainer.
2089+
20702090
###### 🧵 `thread.new-indirect`
20712091

20722092
| Synopsis | |
@@ -2089,7 +2109,7 @@ extended for [GC].
20892109

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

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

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-
21282145
For details, see [Thread Built-ins] in the concurrency explainer and
21292146
[`canon_thread_suspend`] in the Canonical ABI explainer.
21302147

@@ -2138,52 +2155,73 @@ For details, see [Thread Built-ins] in the concurrency explainer and
21382155
The `thread.yield` built-in allows the runtime to potentially switch to any
21392156
other thread in the "ready" state, enabling a long-running computation to
21402157
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.
2158+
thread to be resumed (as with `thread.yield-then-resume`). If `cancellable` is
2159+
set, `thread.yield` returns whether the current task was [cancelled] by the
2160+
caller; otherwise, `thread.yield` always returns `false`.
21512161

21522162
For details, see [Thread Built-ins] in the concurrency explainer and
21532163
[`canon_thread_yield`] in the Canonical ABI explainer.
21542164

2155-
###### 🧵 `thread.switch-to`
2165+
###### 🧵 `thread.suspend-then-resume`
21562166

21572167
| Synopsis | |
21582168
| -------------------------- | --------------------------------------- |
21592169
| Approximate WIT signature | `func<cancellable?>(t: thread) -> bool` |
21602170
| Canonical ABI signature | `[t:i32] -> [i32]` |
21612171

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`.
2172+
The `thread.suspend-then-resume` built-in suspends the [current thread] and
2173+
immediately resumes execution of the thread `t`, trapping if `t` is not in a
2174+
"suspended" state. If `cancellable` is set, `thread.suspend-then-resume` returns
2175+
whether the current task was [cancelled] by the caller; otherwise,
2176+
`thread.suspend-then-resume` always returns `false`.
21672177

21682178
For details, see [Thread Built-ins] in the concurrency explainer and
2169-
[`canon_thread_switch_to`] in the Canonical ABI explainer.
2179+
[`canon_thread_suspend_then_resume`] in the Canonical ABI explainer.
21702180

2171-
###### 🧵 `thread.yield-to`
2181+
###### 🧵 `thread.yield-then-resume`
21722182

21732183
| Synopsis | |
21742184
| -------------------------- | --------------------------------------- |
21752185
| Approximate WIT signature | `func<cancellable?>(t: thread) -> bool` |
21762186
| Canonical ABI signature | `[t:i32] -> [i32]` |
21772187

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`.
2188+
The `thread.yield-then-resume` built-in immediately resumes execution of the
2189+
thread `t`, (trapping if `t` is not in a "suspended" state) leaving the [current
2190+
thread] in a "ready" state so that the runtime can nondeterministically resume
2191+
the current thread at some point in the future. If `cancellable` is set,
2192+
`thread.yield-then-resume` returns whether the current task was [cancelled] by
2193+
the caller; otherwise, `thread.yield-then-resume` always returns `false`.
21842194

21852195
For details, see [Thread Built-ins] in the concurrency explainer and
2186-
[`canon_thread_yield_to`] in the Canonical ABI explainer.
2196+
[`canon_thread_yield_then_resume`] in the Canonical ABI explainer.
2197+
2198+
###### 🧵 `thread.suspend-then-promote`
2199+
2200+
| Synopsis | |
2201+
| -------------------------- | --------------------------------------- |
2202+
| Approximate WIT signature | `func<cancellable?>(t: thread) -> bool` |
2203+
| Canonical ABI signature | `[t:i32] -> [i32]` |
2204+
2205+
The `thread.suspend-then-promote` built-in immediately resumes execution of the
2206+
thread `t` if `t` is in a "ready" state, in any case leaving the current thread
2207+
in a "suspended" state.
2208+
2209+
For details, see [Thread Built-ins] in the concurrency explainer and
2210+
[`canon_thread_suspend_then_promote`] in the Canonical ABI explainer.
2211+
2212+
###### 🧵 `thread.yield-then-promote`
2213+
2214+
| Synopsis | |
2215+
| -------------------------- | --------------------------------------- |
2216+
| Approximate WIT signature | `func<cancellable?>(t: thread) -> bool` |
2217+
| Canonical ABI signature | `[t:i32] -> [i32]` |
2218+
2219+
The `thread.yield-then-promote` built-in immediately resumes execution of the
2220+
thread `t` if `t` is in a "ready" state, in any case leaving the current thread
2221+
in a "ready" state.
2222+
2223+
For details, see [Thread Built-ins] in the concurrency explainer and
2224+
[`canon_thread_yield_then_promote`] in the Canonical ABI explainer.
21872225

21882226
###### 🧵② `thread.spawn-ref`
21892227

@@ -3282,12 +3320,15 @@ For some use-case-focused, worked examples, see:
32823320
[`canon_error_context_debug_message`]: CanonicalABI.md#-canon-error-contextdebug-message
32833321
[`canon_error_context_drop`]: CanonicalABI.md#-canon-error-contextdrop
32843322
[`canon_thread_index`]: CanonicalABI.md#-canon-threadindex
3323+
[`canon_thread_exit`]: CanonicalABI.md#-canon-threadexit
32853324
[`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
32883325
[`canon_thread_resume_later`]: CanonicalABI.md#-canon-threadresume-later
3289-
[`canon_thread_yield_to`]: CanonicalABI.md#-canon-threadyield-to
3326+
[`canon_thread_suspend`]: CanonicalABI.md#-canon-threadsuspend
32903327
[`canon_thread_yield`]: CanonicalABI.md#-canon-threadyield
3328+
[`canon_thread_suspend_then_resume`]: CanonicalABI.md#-canon-threadsuspend-then-resume
3329+
[`canon_thread_yield_then_resume`]: CanonicalABI.md#-canon-threadyield-then-resume
3330+
[`canon_thread_suspend_then_promote`]: CanonicalABI.md#-canon-threadsuspend-then-promote
3331+
[`canon_thread_yield_then_promote`]: CanonicalABI.md#-canon-threadyield-then-promote
32913332
[`canon_thread_spawn_ref`]: CanonicalABI.md#-canon-threadspawn-ref
32923333
[`canon_thread_spawn_indirect`]: CanonicalABI.md#-canon-threadspawn-indirect
32933334
[`canon_thread_available_parallelism`]: CanonicalABI.md#-canon-threadavailable_parallelism

0 commit comments

Comments
 (0)