Skip to content

Commit d93242b

Browse files
abrownlukewagner
authored andcommitted
Add thread.spawn_indirect
This change codifies the conclusions we arrived to in [#89]. It adds a new way to spawn threads, `thread.spawn_indirect`, which retrieves the thread start function from a table. This prompted me to rename `thread.spawn` to `thread.spawn_ref`. [#89]: WebAssembly/shared-everything-threads#89
1 parent 7b724e1 commit d93242b

File tree

4 files changed

+86
-27
lines changed

4 files changed

+86
-27
lines changed

design/mvp/Async.md

+13-14
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,11 @@ these languages' concurrency features are already bound (making the Component
7878
Model "just another OS" from the language toolchains' perspective).
7979

8080
Moreover, this async ABI does not require components to use preemptive
81-
multi-threading ([`thread.spawn`]) in order to achieve concurrency. Instead,
82-
concurrency can be achieved by cooperatively switching between different
83-
logical tasks running on a single thread. This switching may require the use of
84-
[fibers] or a [CPS transform], but may also be avoided entirely when a
85-
component's producer toolchain is engineered to always return to an
86-
[event loop].
81+
multi-threading ([`thread.spawn*`]) in order to achieve concurrency. Instead,
82+
concurrency can be achieved by cooperatively switching between different logical
83+
tasks running on a single thread. This switching may require the use of [fibers]
84+
or a [CPS transform], but may also be avoided entirely when a component's
85+
producer toolchain is engineered to always return to an [event loop].
8786

8887
To avoid partitioning the world along sync/async lines as mentioned in the
8988
Goals section, the Component Model allows *every* component-level function type
@@ -672,11 +671,11 @@ by declarative instantiation and `start` above.
672671

673672
## Interaction with multi-threading
674673

675-
For now, the integration between multi-threading (via [`thread.spawn`]) and
676-
native async is limited. In particular, because all [lift and lower
677-
definitions] produce non-`shared` functions, any threads spawned by a component
678-
via `thread.spawn` will not be able to directly call imports (synchronously
679-
*or* asynchronously) and will thus have to use Core WebAssembly `atomics.*`
674+
For now, the integration between multi-threading (via [`thread.spawn*`]) and
675+
native async is limited. In particular, because all [lift and lower definitions]
676+
produce non-`shared` functions, any threads spawned by a component via
677+
`thread.spawn*` will not be able to directly call imports (synchronously *or*
678+
asynchronously) and will thus have to use Core WebAssembly `atomics.*`
680679
instructions to switch back to a non-`shared` function running on the "main"
681680
thread (i.e., whichever thread was used to call the component's exports).
682681

@@ -693,8 +692,8 @@ composition story described above could naturally be extended to a
693692
sync+async+shared composition story, continuing to avoid the "what color is
694693
your function" problem (where `shared` is the [color]).
695694

696-
Even without any use of `thread.new`, native async provides an opportunity to
697-
achieve some automatic parallelism "for free". In particular, due to the
695+
Even without any use of [`thread.spawn*`], native async provides an opportunity
696+
to achieve some automatic parallelism "for free". In particular, due to the
698697
shared-nothing nature of components, each component instance could be given a
699698
separate thread on which to interleave all tasks executing in that instance.
700699
Thus, in a cross-component call from `C1` to `C2`, `C2`'s task can run in a
@@ -750,7 +749,7 @@ comes after:
750749
[`yield`]: Explainer.md#-yield
751750
[`waitable-set.wait`]: Explainer.md#-waitable-setwait
752751
[`waitable-set.poll`]: Explainer.md#-waitable-setpoll
753-
[`thread.spawn`]: Explainer.md#-threadspawn
752+
[`thread.spawn*`]: Explainer.md#-threadspawnref
754753
[ESM-integration]: Explainer.md#ESM-integration
755754

756755
[Canonical ABI Explainer]: CanonicalABI.md

design/mvp/Binary.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,10 @@ canon ::= 0x00 0x00 f:<core:funcidx> opts:<opts> ft:<typeidx> => (canon lift
286286
| 0x01 0x00 f:<funcidx> opts:<opts> => (canon lower f opts (core func))
287287
| 0x02 rt:<typeidx> => (canon resource.new rt (core func))
288288
| 0x03 rt:<typeidx> => (canon resource.drop rt (core func))
289-
| 0x07 rt:<typdidx> => (canon resource.drop rt async (core func)) 🔀
289+
| 0x07 rt:<typeidx> => (canon resource.drop rt async (core func)) 🔀
290290
| 0x04 rt:<typeidx> => (canon resource.rep rt (core func))
291-
| 0x05 ft:<typeidx> => (canon thread.spawn ft (core func)) 🧵
291+
| 0x05 ft:<typeidx> => (canon thread.spawn_ref ft (core func)) 🧵
292+
| 0x24 ft:<typeidx> t:<core:tabidx> => (canon thread.spawn_indirect ft (table t) (core func)) 🧵
292293
| 0x06 => (canon thread.available_parallelism (core func)) 🧵
293294
| 0x08 => (canon backpressure.set (core func)) 🔀
294295
| 0x09 rs:<resultlist> opts:<opts> => (canon task.return rs opts (core func)) 🔀

design/mvp/CanonicalABI.md

+45-3
Original file line numberDiff line numberDiff line change
@@ -3778,11 +3778,11 @@ async def canon_error_context_drop(task, i):
37783778
```
37793779

37803780

3781-
### 🧵 `canon thread.spawn`
3781+
### 🧵 `canon thread.spawn_ref`
37823782

37833783
For a canonical definition:
37843784
```wat
3785-
(canon thread.spawn (type $ft) (core func $st))
3785+
(canon thread.spawn_ref (type $ft) (core func $st))
37863786
```
37873787
validation specifies:
37883788
* `$ft` must refer to a `shared` function type; initially, only the type `(func
@@ -3807,7 +3807,7 @@ thread which:
38073807
In pseudocode, `$st` looks like:
38083808

38093809
```python
3810-
def canon_thread_spawn(f, c):
3810+
def canon_thread_spawn_ref(f, c):
38113811
trap_if(f is None)
38123812
if DETERMINISTIC_PROFILE:
38133813
return [-1]
@@ -3825,6 +3825,48 @@ def canon_thread_spawn(f, c):
38253825
```
38263826

38273827

3828+
### 🧵 `canon thread.spawn_indirect`
3829+
3830+
For a canonical definition:
3831+
```wat
3832+
(canon thread.spawn_indirect (type $ft) (table $t) (core func $st))
3833+
```
3834+
validation specifies:
3835+
* `$ft` must refer to a `shared` function type; initially, only the type `(func
3836+
shared (param $c i32))` is allowed (see explanation in `thread.spawn_ref`
3837+
above)
3838+
* `$t` must refer to a table containing `ft`-typed items
3839+
* `$st` is given type `(func (param $i i32) (param $c i32) (result $e
3840+
i32))`.
3841+
3842+
Calling `$st` retrieves a function `$f` of type `$ft` from table `$t`. If that
3843+
succeeds, it spawns a thread which:
3844+
- invokes `$f` with `$c`
3845+
- executes `$f` until completion or trap in a `shared` context as described by
3846+
the [shared-everything threads] proposal.
3847+
3848+
In pseudocode, `$st` looks like:
3849+
3850+
```python
3851+
def canon_thread_spawn_indirect(t, i, c):
3852+
trap_if(t[i] is None)
3853+
f = t[i]
3854+
if DETERMINISTIC_PROFILE:
3855+
return [-1]
3856+
3857+
def thread_start():
3858+
try:
3859+
f(c)
3860+
except CoreWebAssemblyException:
3861+
trap()
3862+
3863+
if spawn(thread_start):
3864+
return [0]
3865+
else:
3866+
return [-1]
3867+
```
3868+
3869+
38283870
### 🧵 `canon thread.available_parallelism`
38293871

38303872
For a canonical definition:

design/mvp/Explainer.md

+25-8
Original file line numberDiff line numberDiff line change
@@ -1438,7 +1438,8 @@ canon ::= ...
14381438
| (canon error-context.new <canonopt>* (core func <id>?))
14391439
| (canon error-context.debug-message <canonopt>* (core func <id>?))
14401440
| (canon error-context.drop (core func <id>?))
1441-
| (canon thread.spawn <typeidx> (core func <id>?)) 🧵
1441+
| (canon thread.spawn_ref <typeidx> (core func <id>?)) 🧵
1442+
| (canon thread.spawn_indirect <typeidx> <core:tableidx> (core func <id>?)) 🧵
14421443
| (canon thread.available_parallelism (core func <id>?)) 🧵
14431444
```
14441445

@@ -1945,19 +1946,34 @@ thread management. These are specified as built-ins and not core WebAssembly
19451946
instructions because browsers expect this functionality to come from existing
19461947
Web/JS APIs.
19471948

1948-
###### 🧵 `thread.spawn`
1949+
###### 🧵 `thread.spawn_ref`
19491950

19501951
| Synopsis | |
19511952
| -------------------------- | --------------------------------------------------------- |
19521953
| Approximate WIT signature | `func<FuncT>(f: FuncT, c: FuncT.params[0]) -> bool` |
19531954
| Canonical ABI signature | `[f:(ref null (func shared (param i32))) c:i32] -> [i32]` |
19541955

1955-
The `thread.spawn` built-in spawns a new thread by invoking the shared function
1956-
`f` while passing `c` to it, returning whether a thread was successfully
1957-
spawned. While it's designed to allow different types in the future, the type
1958-
of `c` is currently hard-coded to always be `i32`.
1956+
The `thread.spawn_ref` built-in spawns a new thread by invoking the shared
1957+
function `f` while passing `c` to it, returning whether a thread was
1958+
successfully spawned. While it's designed to allow different types in the
1959+
future, the type of `c` is currently hard-coded to always be `i32`.
19591960

1960-
(See also [`canon_thread_spawn`] in the Canonical ABI explainer.)
1961+
(See also [`canon_thread_spawn_ref`] in the Canonical ABI explainer.)
1962+
1963+
1964+
###### 🧵 `thread.spawn_indirect`
1965+
1966+
| Synopsis | |
1967+
| -------------------------- | ------------------------------------------------- |
1968+
| Approximate WIT signature | `func<FuncT>(i: i32, c: FuncT.params[0]) -> bool` |
1969+
| Canonical ABI signature | `[i:i32 c:i32] -> [i32]` |
1970+
1971+
The `thread.spawn_indirect` built-in spawns a new thread by retrieving the
1972+
shared function `f` from a table using index `i` (much like the `call_indirect`
1973+
core instruction). Once `f` is retrieved, this built-in operates like
1974+
`thread.spawn_ref` above, including the limitations on `f`'s parameters.
1975+
1976+
(See also [`canon_thread_spawn_indirect`] in the Canonical ABI explainer.)
19611977

19621978
###### 🧵 `thread.available_parallelism`
19631979

@@ -2806,7 +2822,8 @@ For some use-case-focused, worked examples, see:
28062822
[`canon_error_context_new`]: CanonicalABI.md#-canon-error-contextnew
28072823
[`canon_error_context_debug_message`]: CanonicalABI.md#-canon-error-contextdebug-message
28082824
[`canon_error_context_drop`]: CanonicalABI.md#-canon-error-contextdrop
2809-
[`canon_thread_spawn`]: CanonicalABI.md#-canon-theadspawn
2825+
[`canon_thread_spawn_ref`]: CanonicalABI.md#-canon-threadspawnref
2826+
[`canon_thread_spawn_indirect`]: CanonicalABI.md#-canon-threadspawnindirect
28102827
[`canon_thread_available_parallelism`]: CanonicalABI.md#-canon-threadavailable_parallelism
28112828
[`pack_async_copy_result`]: CanonicalABI.md#-canon-streamfuturereadwrite
28122829
[the `close` built-ins]: CanonicalABI.md#-canon-streamfutureclose-readablewritable

0 commit comments

Comments
 (0)