diff --git a/design/mvp/Async.md b/design/mvp/Async.md index f80fb4ca..ff33b48a 100644 --- a/design/mvp/Async.md +++ b/design/mvp/Async.md @@ -78,12 +78,11 @@ these languages' concurrency features are already bound (making the Component Model "just another OS" from the language toolchains' perspective). Moreover, this async ABI does not require components to use preemptive -multi-threading ([`thread.spawn`]) in order to achieve concurrency. Instead, -concurrency can be achieved by cooperatively switching between different -logical tasks running on a single thread. This switching may require the use of -[fibers] or a [CPS transform], but may also be avoided entirely when a -component's producer toolchain is engineered to always return to an -[event loop]. +multi-threading ([`thread.spawn*`]) in order to achieve concurrency. Instead, +concurrency can be achieved by cooperatively switching between different logical +tasks running on a single thread. This switching may require the use of [fibers] +or a [CPS transform], but may also be avoided entirely when a component's +producer toolchain is engineered to always return to an [event loop]. To avoid partitioning the world along sync/async lines as mentioned in the Goals section, the Component Model allows *every* component-level function type @@ -672,11 +671,11 @@ by declarative instantiation and `start` above. ## Interaction with multi-threading -For now, the integration between multi-threading (via [`thread.spawn`]) and -native async is limited. In particular, because all [lift and lower -definitions] produce non-`shared` functions, any threads spawned by a component -via `thread.spawn` will not be able to directly call imports (synchronously -*or* asynchronously) and will thus have to use Core WebAssembly `atomics.*` +For now, the integration between multi-threading (via [`thread.spawn*`]) and +native async is limited. In particular, because all [lift and lower definitions] +produce non-`shared` functions, any threads spawned by a component via +`thread.spawn*` will not be able to directly call imports (synchronously *or* +asynchronously) and will thus have to use Core WebAssembly `atomics.*` instructions to switch back to a non-`shared` function running on the "main" thread (i.e., whichever thread was used to call the component's exports). @@ -693,8 +692,8 @@ composition story described above could naturally be extended to a sync+async+shared composition story, continuing to avoid the "what color is your function" problem (where `shared` is the [color]). -Even without any use of `thread.new`, native async provides an opportunity to -achieve some automatic parallelism "for free". In particular, due to the +Even without any use of [`thread.spawn*`], native async provides an opportunity +to achieve some automatic parallelism "for free". In particular, due to the shared-nothing nature of components, each component instance could be given a separate thread on which to interleave all tasks executing in that instance. Thus, in a cross-component call from `C1` to `C2`, `C2`'s task can run in a @@ -750,7 +749,7 @@ comes after: [`yield`]: Explainer.md#-yield [`waitable-set.wait`]: Explainer.md#-waitable-setwait [`waitable-set.poll`]: Explainer.md#-waitable-setpoll -[`thread.spawn`]: Explainer.md#-threadspawn +[`thread.spawn*`]: Explainer.md#-threadspawn_ref [ESM-integration]: Explainer.md#ESM-integration [Canonical ABI Explainer]: CanonicalABI.md diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index 915afcc3..6501b450 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -286,10 +286,8 @@ canon ::= 0x00 0x00 f: opts: ft: => (canon lift | 0x01 0x00 f: opts: => (canon lower f opts (core func)) | 0x02 rt: => (canon resource.new rt (core func)) | 0x03 rt: => (canon resource.drop rt (core func)) - | 0x07 rt: => (canon resource.drop rt async (core func)) ๐Ÿ”€ + | 0x07 rt: => (canon resource.drop rt async (core func)) ๐Ÿ”€ | 0x04 rt: => (canon resource.rep rt (core func)) - | 0x05 ft: => (canon thread.spawn ft (core func)) ๐Ÿงต - | 0x06 => (canon thread.available_parallelism (core func)) ๐Ÿงต | 0x08 => (canon backpressure.set (core func)) ๐Ÿ”€ | 0x09 rs: opts: => (canon task.return rs opts (core func)) ๐Ÿ”€ | 0x0a 0x7f i: => (canon context.get i32 i (core func)) ๐Ÿ”€ @@ -318,6 +316,9 @@ canon ::= 0x00 0x00 f: opts: ft: => (canon lift | 0x21 async?:? m: => (canon waitable-set.poll async? (memory m) (core func)) ๐Ÿ”€ | 0x22 => (canon waitable-set.drop (core func)) ๐Ÿ”€ | 0x23 => (canon waitable.join (core func)) ๐Ÿ”€ + | 0x40 ft: => (canon thread.spawn_ref ft (core func)) ๐Ÿงต + | 0x41 ft: tbl: => (canon thread.spawn_indirect ft tbl (core func)) ๐Ÿงต + | 0x42 => (canon thread.available_parallelism (core func)) ๐Ÿงต async? ::= 0x00 => | 0x01 => async opts ::= opt*:vec() => opt* diff --git a/design/mvp/CanonicalABI.md b/design/mvp/CanonicalABI.md index 27bd1f95..0d1291f6 100644 --- a/design/mvp/CanonicalABI.md +++ b/design/mvp/CanonicalABI.md @@ -54,7 +54,8 @@ being specified here. * [`canon error-context.new`](#-canon-error-contextnew) ๐Ÿ”€ * [`canon error-context.debug-message`](#-canon-error-contextdebug-message) ๐Ÿ”€ * [`canon error-context.drop`](#-canon-error-contextdrop) ๐Ÿ”€ - * [`canon thread.spawn`](#-canon-threadspawn) ๐Ÿงต + * [`canon thread.spawn_ref`](#-canon-threadspawn_ref) ๐Ÿงต + * [`canon thread.spawn_indirect`](#-canon-threadspawn_indirect) ๐Ÿงต * [`canon thread.available_parallelism`](#-canon-threadavailable_parallelism) ๐Ÿงต ## Supporting definitions @@ -3773,37 +3774,82 @@ async def canon_error_context_drop(task, i): ``` -### ๐Ÿงต `canon thread.spawn` +### ๐Ÿงต `canon thread.spawn_ref` For a canonical definition: ```wat -(canon thread.spawn (type $ft) (core func $st)) +(canon thread.spawn_ref $ft (core func $spawn_ref)) ``` validation specifies: -* `$ft` must refer to a `shared` function type; initially, only the type `(func - shared (param $c i32))` is allowed (see explanation below) -* `$st` is given type `(func (param $f (ref null $ft)) (param $c i32) (result $e - i32))`. +* `$ft` must refer to a `shared` function type; initially, only the type + `(shared (func (param $c i32)))` is allowed (see explanation below) +* `$spawn_ref` is given type `(func (param $f (ref null $ft)) (param $c i32) + (result $e i32))`. > Note: ideally, a thread could be spawned with [arbitrary thread parameters]. > Currently, that would require additional work in the toolchain to support so, -> for simplicity, the current proposal simply fixes a single `i32` parameter type. -> However, `thread.spawn` could be extended to allow arbitrary thread parameters -> in the future, once it's concretely beneficial to the toolchain. +> for simplicity, the current proposal simply fixes a single `i32` parameter +> type. However, `thread.spawn_ref` could be extended to allow arbitrary thread +> parameters in the future, once it's concretely beneficial to the toolchain. > The inclusion of `$ft` ensures backwards compatibility for when arbitrary > parameters are allowed. -Calling `$st` checks that the reference `$f` is not null. Then, it spawns a +Calling `$spawn_ref` checks that the reference `$f` is not null. Then, it spawns +a thread which: + - invokes `$f` with `$c` + - executes `$f` until completion or trap in a `shared` context as described by + the [shared-everything threads] proposal. + +In pseudocode, `$spawn_ref` looks like: + +```python +def canon_thread_spawn_ref(f, c): + trap_if(f is None) + if DETERMINISTIC_PROFILE: + return [-1] + + def thread_start(): + try: + f(c) + except CoreWebAssemblyException: + trap() + + if spawn(thread_start): + return [0] + else: + return [-1] +``` + + +### ๐Ÿงต `canon thread.spawn_indirect` + +For a canonical definition: +```wat +(canon thread.spawn_indirect $ft $tbl (core func $spawn_indirect)) +``` +validation specifies: +* `$ft` must refer to a `shared` function type; initially, only the type + `(shared (func (param $c i32)))` is allowed (see explanation in + `thread.spawn_ref` above) +* `$tbl` must refer to a table with type `(table (ref null (shared func)) + shared)` +* `$spawn_indirect` is given type `(func (param $i i32) (param $c i32) (result + $e i32))`. + +Calling `$spawn_indirect` retrieves a reference to function `$f` from table +`$tbl` and checks that `$f` is of type `$ft`. If that succeeds, it spawns a thread which: - invokes `$f` with `$c` - executes `$f` until completion or trap in a `shared` context as described by the [shared-everything threads] proposal. -In pseudocode, `$st` looks like: +In pseudocode, `$spawn_indirect` looks like: ```python -def canon_thread_spawn(f, c): +def canon_thread_spawn_indirect(ft, tbl, i, c): + f = tbl[i] trap_if(f is None) + trap_if(f.type != ft) if DETERMINISTIC_PROFILE: return [-1] diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index b34146b8..b344cdfc 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -1438,7 +1438,8 @@ canon ::= ... | (canon error-context.new * (core func ?)) | (canon error-context.debug-message * (core func ?)) | (canon error-context.drop (core func ?)) - | (canon thread.spawn (core func ?)) ๐Ÿงต + | (canon thread.spawn_ref (core func ?)) ๐Ÿงต + | (canon thread.spawn_indirect (core func ?)) ๐Ÿงต | (canon thread.available_parallelism (core func ?)) ๐Ÿงต ``` @@ -1945,19 +1946,35 @@ thread management. These are specified as built-ins and not core WebAssembly instructions because browsers expect this functionality to come from existing Web/JS APIs. -###### ๐Ÿงต `thread.spawn` +###### ๐Ÿงต `thread.spawn_ref` -| Synopsis | | -| -------------------------- | --------------------------------------------------------- | -| Approximate WIT signature | `func(f: FuncT, c: FuncT.params[0]) -> bool` | -| Canonical ABI signature | `[f:(ref null (func shared (param i32))) c:i32] -> [i32]` | +| Synopsis | | +| -------------------------- | ---------------------------------------------------------- | +| Approximate WIT signature | `func(f: FuncT, c: FuncT.params[0]) -> bool` | +| Canonical ABI signature | `[f:(ref null (shared (func (param i32))) c:i32] -> [i32]` | -The `thread.spawn` built-in spawns a new thread by invoking the shared function -`f` while passing `c` to it, returning whether a thread was successfully -spawned. While it's designed to allow different types in the future, the type -of `c` is currently hard-coded to always be `i32`. +The `thread.spawn_ref` built-in spawns a new thread by invoking the shared +function `f` while passing `c` to it, returning whether a thread was +successfully spawned. While it's designed to allow different types in the +future, the type of `c` is currently hard-coded to always be `i32`. -(See also [`canon_thread_spawn`] in the Canonical ABI explainer.) +(See also [`canon_thread_spawn_ref`] in the Canonical ABI explainer.) + + +###### ๐Ÿงต `thread.spawn_indirect` + +| Synopsis | | +| -------------------------- | ------------------------------------------------- | +| Approximate WIT signature | `func(i: u32, c: FuncT.params[0]) -> bool` | +| Canonical ABI signature | `[i:i32 c:i32] -> [i32]` | + +The `thread.spawn_indirect` built-in spawns a new thread by retrieving the +shared function `f` from a table using index `i` and traps if the type of `f` is +not equal to `FuncT` (much like the `call_indirect` core instruction). Once `f` +is retrieved, this built-in operates like `thread.spawn_ref` above, including +the limitations on `f`'s parameters. + +(See also [`canon_thread_spawn_indirect`] in the Canonical ABI explainer.) ###### ๐Ÿงต `thread.available_parallelism` @@ -1966,14 +1983,15 @@ of `c` is currently hard-coded to always be `i32`. | Approximate WIT signature | `func() -> u32` | | Canonical ABI signature | `[] -> [i32]` | -The `thread.available_parallelism` built-in returns the number of threads that can be -expected to execute in parallel. +The `thread.available_parallelism` built-in returns the number of threads that +can be expected to execute in parallel. The concept of "available parallelism" corresponds is sometimes referred to as "hardware concurrency", such as in [`navigator.hardwareConcurrency`] in JavaScript. -(See also [`canon_thread_available_parallelism`] in the Canonical ABI explainer.) +(See also [`canon_thread_available_parallelism`] in the Canonical ABI +explainer.) ### ๐Ÿช™ Value Definitions @@ -2806,7 +2824,8 @@ For some use-case-focused, worked examples, see: [`canon_error_context_new`]: CanonicalABI.md#-canon-error-contextnew [`canon_error_context_debug_message`]: CanonicalABI.md#-canon-error-contextdebug-message [`canon_error_context_drop`]: CanonicalABI.md#-canon-error-contextdrop -[`canon_thread_spawn`]: CanonicalABI.md#-canon-theadspawn +[`canon_thread_spawn_ref`]: CanonicalABI.md#-canon-threadspawn_ref +[`canon_thread_spawn_indirect`]: CanonicalABI.md#-canon-threadspawn_indirect [`canon_thread_available_parallelism`]: CanonicalABI.md#-canon-threadavailable_parallelism [`pack_async_copy_result`]: CanonicalABI.md#-canon-streamfuturereadwrite [the `close` built-ins]: CanonicalABI.md#-canon-streamfutureclose-readablewritable