Skip to content

Commit a69f22e

Browse files
committed
Add thread.spawn_indirect
This change codifies the conclusions we arrived to in [WebAssembly#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`. [WebAssembly#89]: WebAssembly/shared-everything-threads#89
1 parent 7aedcbe commit a69f22e

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
@@ -630,11 +629,11 @@ these values is defined by the Canonical ABI.
630629

631630
## Interaction with multi-threading
632631

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

@@ -651,8 +650,8 @@ composition story described above could naturally be extended to a
651650
sync+async+shared composition story, continuing to avoid the "what color is
652651
your function" problem (where `shared` is the [color]).
653652

654-
Even without any use of `thread.new`, native async provides an opportunity to
655-
achieve some automatic parallelism "for free". In particular, due to the
653+
Even without any use of [`thread.spawn*`], native async provides an opportunity
654+
to achieve some automatic parallelism "for free". In particular, due to the
656655
shared-nothing nature of components, each component instance could be given a
657656
separate thread on which to interleave all tasks executing in that instance.
658657
Thus, in a cross-component call from `C1` to `C2`, `C2`'s task can run in a
@@ -720,7 +719,7 @@ comes after:
720719
[`yield`]: Explainer.md#-yield
721720
[`waitable-set.wait`]: Explainer.md#-waitable-setwait
722721
[`waitable-set.poll`]: Explainer.md#-waitable-setpoll
723-
[`thread.spawn`]: Explainer.md#-threadspawn
722+
[`thread.spawn*`]: Explainer.md#-threadspawnref
724723
[ESM-integration]: Explainer.md#ESM-integration
725724

726725
[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
@@ -3761,11 +3761,11 @@ async def canon_error_context_drop(task, i):
37613761
```
37623762

37633763

3764-
### 🧵 `canon thread.spawn`
3764+
### 🧵 `canon thread.spawn_ref`
37653765

37663766
For a canonical definition:
37673767
```wat
3768-
(canon thread.spawn (type $ft) (core func $st))
3768+
(canon thread.spawn_ref (type $ft) (core func $st))
37693769
```
37703770
validation specifies:
37713771
* `$ft` must refer to a `shared` function type; initially, only the type `(func
@@ -3790,7 +3790,7 @@ thread which:
37903790
In pseudocode, `$st` looks like:
37913791

37923792
```python
3793-
def canon_thread_spawn(f, c):
3793+
def canon_thread_spawn_ref(f, c):
37943794
trap_if(f is None)
37953795
if DETERMINISTIC_PROFILE:
37963796
return [-1]
@@ -3808,6 +3808,48 @@ def canon_thread_spawn(f, c):
38083808
```
38093809

38103810

3811+
### 🧵 `canon thread.spawn_indirect`
3812+
3813+
For a canonical definition:
3814+
```wat
3815+
(canon thread.spawn_indirect (type $ft) (table $t) (core func $st))
3816+
```
3817+
validation specifies:
3818+
* `$ft` must refer to a `shared` function type; initially, only the type `(func
3819+
shared (param $c i32))` is allowed (see explanation in `thread.spawn_ref`
3820+
above)
3821+
* `$t` must refer to a table containing `ft`-typed items
3822+
* `$st` is given type `(func (param $i i32) (param $c i32) (result $e
3823+
i32))`.
3824+
3825+
Calling `$st` retrieves a function `$f` of type `$ft` from table `$t`. If that
3826+
succeeds, it spawns a thread which:
3827+
- invokes `$f` with `$c`
3828+
- executes `$f` until completion or trap in a `shared` context as described by
3829+
the [shared-everything threads] proposal.
3830+
3831+
In pseudocode, `$st` looks like:
3832+
3833+
```python
3834+
def canon_thread_spawn_indirect(t, i, c):
3835+
trap_if(t[i] is None)
3836+
f = t[i]
3837+
if DETERMINISTIC_PROFILE:
3838+
return [-1]
3839+
3840+
def thread_start():
3841+
try:
3842+
f(c)
3843+
except CoreWebAssemblyException:
3844+
trap()
3845+
3846+
if spawn(thread_start):
3847+
return [0]
3848+
else:
3849+
return [-1]
3850+
```
3851+
3852+
38113853
### 🧵 `canon thread.available_parallelism`
38123854

38133855
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

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

1949-
###### 🧵 `thread.spawn`
1950+
###### 🧵 `thread.spawn_ref`
19501951

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

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

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

19631979
###### 🧵 `thread.available_parallelism`
19641980

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

0 commit comments

Comments
 (0)