Skip to content

Commit a94d0a1

Browse files
committed
threads: add thread.spawn_indirect
As discussed in [bytecodealliance#89], this adds support for a new intrinsic, `thread.spawn_indirect`. This new operation would allow spawning a shared function stored in a table via a table index. This leaves some future work undone: - `thread.spawn` could/should be renamed to `thread.spawn_ref` - `thread.spawn_indirect` could/should take the encoding byte from `thread.hw_concurrency`--swap `0x07` for `0x06` - importantly, `thread.spawn_indirect` should gain a field indicating which type to expect in the indirect table; due to current limitations in `wasm-tools`, the locations to check once this is possible are marked with `TODO: spawn indirect types`. [bytecodealliance#89]: WebAssembly/shared-everything-threads#89
1 parent d4939a4 commit a94d0a1

File tree

16 files changed

+179
-27
lines changed

16 files changed

+179
-27
lines changed

crates/wasm-encoder/src/component/canonicals.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ impl CanonicalFunctionSection {
158158
self
159159
}
160160

161-
/// Defines a function which will spawns a new thread by invoking a shared
161+
/// Defines a function which will spawn a new thread by invoking a shared
162162
/// function of type `ty_index`.
163163
pub fn thread_spawn(&mut self, ty_index: u32) -> &mut Self {
164164
self.bytes.push(0x05);
@@ -167,6 +167,15 @@ impl CanonicalFunctionSection {
167167
self
168168
}
169169

170+
/// Defines a function which will spawn a new thread by invoking a shared
171+
/// function indirectly through a `funcref` table.
172+
pub fn thread_spawn_indirect(&mut self, table_index: u32) -> &mut Self {
173+
self.bytes.push(0x24);
174+
table_index.encode(&mut self.bytes);
175+
self.num_added += 1;
176+
self
177+
}
178+
170179
/// Defines a function which will return the number of threads that can be
171180
/// expected to execute concurrently.
172181
pub fn thread_available_parallelism(&mut self) -> &mut Self {

crates/wasm-encoder/src/reencode/component.rs

+4
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,10 @@ pub mod component_utils {
957957
let func_ty = reencoder.type_index(func_ty_index);
958958
section.thread_spawn(func_ty);
959959
}
960+
wasmparser::CanonicalFunction::ThreadSpawnIndirect { table_index } => {
961+
let table_index = reencoder.table_index(table_index);
962+
section.thread_spawn_indirect(table_index);
963+
}
960964
wasmparser::CanonicalFunction::ThreadAvailableParallelism => {
961965
section.thread_available_parallelism();
962966
}

crates/wasmparser/src/readers/component/canonicals.rs

+16
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,19 @@ pub enum CanonicalFunction {
7575
/// The index of the function to spawn.
7676
func_ty_index: u32,
7777
},
78+
/// A function which spawns a new thread by invoking the shared function
79+
/// passed as an index into a `funcref` table.
80+
///
81+
/// Eventually, this will include a core `type_index` field to specify what
82+
/// type of functions the table will contain. Currently, however,
83+
/// `wasm-tools` makes it difficult to pass along the core type information
84+
/// downstream, so we fix the type of the table temporarily to `funcref` and
85+
/// check at runtime that the entry has type `[i32] -> []`. (TODO: spawn
86+
/// indirect types).
87+
ThreadSpawnIndirect {
88+
/// The index of the table to use for the indirect spawn.
89+
table_index: u32,
90+
},
7891
/// A function which returns the number of threads that can be expected to
7992
/// execute concurrently
8093
ThreadAvailableParallelism,
@@ -281,6 +294,9 @@ impl<'a> FromReader<'a> for CanonicalFunction {
281294
func_ty_index: reader.read()?,
282295
},
283296
0x06 => CanonicalFunction::ThreadAvailableParallelism,
297+
0x24 => CanonicalFunction::ThreadSpawnIndirect {
298+
table_index: reader.read()?,
299+
},
284300
0x08 => CanonicalFunction::BackpressureSet,
285301
0x09 => CanonicalFunction::TaskReturn {
286302
result: crate::read_resultlist(reader)?,

crates/wasmparser/src/validator/component.rs

+40
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,9 @@ impl ComponentState {
990990
CanonicalFunction::ThreadSpawn { func_ty_index } => {
991991
self.thread_spawn(func_ty_index, types, offset, features)
992992
}
993+
CanonicalFunction::ThreadSpawnIndirect { table_index } => {
994+
self.thread_spawn_indirect(table_index, types, offset, features)
995+
}
993996
CanonicalFunction::ThreadAvailableParallelism => {
994997
self.thread_available_parallelism(types, offset, features)
995998
}
@@ -1975,6 +1978,43 @@ impl ComponentState {
19751978
Ok(())
19761979
}
19771980

1981+
fn thread_spawn_indirect(
1982+
&mut self,
1983+
table_index: u32,
1984+
types: &mut TypeAlloc,
1985+
offset: usize,
1986+
features: &WasmFeatures,
1987+
) -> Result<()> {
1988+
if !features.shared_everything_threads() {
1989+
bail!(
1990+
offset,
1991+
"`thread.spawn_indirect` requires the shared-everything-threads proposal"
1992+
)
1993+
}
1994+
1995+
// Check this much like `call_indirect` (see
1996+
// `OperatorValidatorTemp::check_call_indirect_ty`), but loosen the
1997+
// table type restrictions for now to just a `funcref`. Once the
1998+
// `spawn_indirect` gains a type immediate, this should look that type
1999+
// up and verify that it is (a) shared and (b) matches the table type
2000+
// (TODO: spawn indirect types).
2001+
let table = self.table_at(table_index, offset)?;
2002+
let expected_ty = RefType::FUNCREF
2003+
.shared()
2004+
.expect("a funcref can always be shared");
2005+
if table.element_type != expected_ty {
2006+
bail!(offset, "expected a table of shared `funcref`");
2007+
}
2008+
2009+
// Insert the core function.
2010+
let func_ty = FuncType::new([ValType::I32, ValType::I32], [ValType::I32]);
2011+
let core_ty = SubType::func(func_ty, true);
2012+
let id = types.intern_sub_type(core_ty, offset);
2013+
self.core_funcs.push(id);
2014+
2015+
Ok(())
2016+
}
2017+
19782018
fn thread_available_parallelism(
19792019
&mut self,
19802020
types: &mut TypeAlloc,

crates/wasmprinter/src/component.rs

+13-15
Original file line numberDiff line numberDiff line change
@@ -895,23 +895,21 @@ impl Printer<'_, '_> {
895895
CanonicalFunction::ThreadSpawn {
896896
func_ty_index: func_index,
897897
} => {
898-
self.start_group("core func ")?;
899-
self.print_name(&state.core.func_names, state.core.funcs)?;
900-
self.result.write_str(" ")?;
901-
self.start_group("canon thread.spawn ")?;
902-
self.print_idx(&state.core.type_names, func_index)?;
903-
self.end_group()?;
904-
self.end_group()?;
905-
state.core.funcs += 1;
898+
self.print_intrinsic(state, "canon thread.spawn ", &|me, state| {
899+
me.print_idx(&state.core.type_names, func_index)
900+
})?;
901+
}
902+
CanonicalFunction::ThreadSpawnIndirect { table_index } => {
903+
self.print_intrinsic(state, "canon thread.spawn_indirect ", &|me, state| {
904+
me.start_group("table ")?;
905+
me.print_idx(&state.core.table_names, table_index)?;
906+
me.end_group()
907+
})?;
906908
}
907909
CanonicalFunction::ThreadAvailableParallelism => {
908-
self.start_group("core func ")?;
909-
self.print_name(&state.core.func_names, state.core.funcs)?;
910-
self.result.write_str(" ")?;
911-
self.start_group("canon thread.available_parallelism")?;
912-
self.end_group()?;
913-
self.end_group()?;
914-
state.core.funcs += 1;
910+
self.print_intrinsic(state, "canon thread.available_parallelism", &|_, _| {
911+
Ok(())
912+
})?;
915913
}
916914
CanonicalFunction::BackpressureSet => {
917915
self.print_intrinsic(state, "canon backpressure.set", &|_, _| Ok(()))?;

crates/wast/src/component/binary.rs

+4
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,10 @@ impl<'a> Encoder<'a> {
357357
self.core_func_names.push(name);
358358
self.funcs.thread_spawn(info.ty.into());
359359
}
360+
CoreFuncKind::ThreadSpawnIndirect(info) => {
361+
self.core_func_names.push(name);
362+
self.funcs.thread_spawn_indirect(info.table.idx.into());
363+
}
360364
CoreFuncKind::ThreadAvailableParallelism(_info) => {
361365
self.core_func_names.push(name);
362366
self.funcs.thread_available_parallelism();

crates/wast/src/component/func.rs

+27
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub enum CoreFuncKind<'a> {
5252
ResourceDrop(CanonResourceDrop<'a>),
5353
ResourceRep(CanonResourceRep<'a>),
5454
ThreadSpawn(CanonThreadSpawn<'a>),
55+
ThreadSpawnIndirect(CanonThreadSpawnIndirect<'a>),
5556
ThreadAvailableParallelism(CanonThreadAvailableParallelism),
5657
BackpressureSet,
5758
TaskReturn(CanonTaskReturn<'a>),
@@ -111,6 +112,8 @@ impl<'a> CoreFuncKind<'a> {
111112
Ok(CoreFuncKind::ResourceRep(parser.parse()?))
112113
} else if l.peek::<kw::thread_spawn>()? {
113114
Ok(CoreFuncKind::ThreadSpawn(parser.parse()?))
115+
} else if l.peek::<kw::thread_spawn_indirect>()? {
116+
Ok(CoreFuncKind::ThreadSpawnIndirect(parser.parse()?))
114117
} else if l.peek::<kw::thread_available_parallelism>()? {
115118
Ok(CoreFuncKind::ThreadAvailableParallelism(parser.parse()?))
116119
} else if l.peek::<kw::backpressure_set>()? {
@@ -488,6 +491,30 @@ impl<'a> Parse<'a> for CanonThreadSpawn<'a> {
488491
}
489492
}
490493

494+
/// Information relating to the `thread.spawn_indirect` intrinsic.
495+
///
496+
/// This should look identical to that of `CallIndirect`. The only difference is
497+
/// that, temporarily, the `ty` field (`pub ty: TypeUse<'a, FunctionType<'a>>`)
498+
/// is fixed to a shared `funcref`, instead of allowing programs to specify the
499+
/// type. This is due to `wasm-tools` limitations making it difficult to pass
500+
/// core type indexes downstream (TODO: spawn indirect types).
501+
#[derive(Debug)]
502+
pub struct CanonThreadSpawnIndirect<'a> {
503+
/// The table that this spawn is going to be indexing.
504+
pub table: CoreItemRef<'a, kw::table>,
505+
}
506+
507+
impl<'a> Parse<'a> for CanonThreadSpawnIndirect<'a> {
508+
fn parse(parser: Parser<'a>) -> Result<Self> {
509+
parser.parse::<kw::thread_spawn_indirect>()?;
510+
let table = parser.parens(|parser| {
511+
let span = parser.parse::<kw::table>()?.0;
512+
parse_trailing_item_ref(kw::table(span), parser)
513+
})?;
514+
Ok(Self { table })
515+
}
516+
}
517+
491518
/// Information relating to the `thread.spawn` intrinsic.
492519
#[derive(Debug)]
493520
pub struct CanonThreadAvailableParallelism;

crates/wast/src/component/resolve.rs

+8
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,13 @@ impl<'a> Resolver<'a> {
390390
CoreFuncKind::ThreadSpawn(info) => {
391391
self.resolve_ns(&mut info.ty, Ns::CoreType)?;
392392
}
393+
CoreFuncKind::ThreadSpawnIndirect(info) => {
394+
self.core_item_ref(&mut info.table)?;
395+
// Eventually this should resolve the specific type associated
396+
// with this canonical function, e.g.,
397+
// `self.resolve_type_use(&mut info.ty)?;` (TODO: spawn indirect
398+
// types).
399+
}
393400
CoreFuncKind::ThreadAvailableParallelism(_)
394401
| CoreFuncKind::BackpressureSet
395402
| CoreFuncKind::Yield(_)
@@ -1094,6 +1101,7 @@ component_item!(kw::module, CoreModule);
10941101

10951102
core_item!(kw::func, CoreFunc);
10961103
core_item!(kw::memory, CoreMemory);
1104+
core_item!(kw::table, CoreTable);
10971105
core_item!(kw::r#type, CoreType);
10981106
core_item!(kw::r#instance, CoreInstance);
10991107

crates/wast/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ pub mod kw {
555555
custom_keyword!(import_info = "import-info");
556556
custom_keyword!(thread);
557557
custom_keyword!(thread_spawn = "thread.spawn");
558+
custom_keyword!(thread_spawn_indirect = "thread.spawn_indirect");
558559
custom_keyword!(thread_available_parallelism = "thread.available_parallelism");
559560
custom_keyword!(backpressure_set = "backpressure.set");
560561
custom_keyword!(task_return = "task.return");

src/bin/wasm-tools/dump.rs

+1
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ impl<'a> Dump<'a> {
420420
| CanonicalFunction::ResourceDropAsync { .. }
421421
| CanonicalFunction::ResourceRep { .. }
422422
| CanonicalFunction::ThreadSpawn { .. }
423+
| CanonicalFunction::ThreadSpawnIndirect { .. }
423424
| CanonicalFunction::ThreadAvailableParallelism
424425
| CanonicalFunction::BackpressureSet
425426
| CanonicalFunction::TaskReturn { .. }

tests/cli/missing-features/component-model/shared-everything-threads-builtins.wast

+8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
)
99
"`thread.spawn` requires the shared-everything-threads proposal")
1010

11+
(assert_invalid
12+
(component
13+
(core module $libc (table (export "start-table") 1 funcref))
14+
(core instance $libc (instantiate $libc))
15+
(core func $spawn_indirect (canon thread.spawn_indirect (table $libc "start-table")))
16+
)
17+
"`thread.spawn_indirect` requires the shared-everything-threads proposal")
18+
1119
(assert_invalid
1220
(component
1321
(core func $parallelism (canon thread.available_parallelism))

tests/cli/shared-everything-threads/builtins.wast

+12
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,35 @@
55
(component
66
(core type $start (shared (func (param $context i32))))
77
(core func $spawn (canon thread.spawn $start))
8+
9+
(core module $libc (table (export "start-table") 1 (ref null (shared func))))
10+
(core instance $libc (instantiate $libc))
11+
(core func $spawn_indirect (canon thread.spawn_indirect (table $libc "start-table")))
12+
813
(core func $parallelism (canon thread.available_parallelism))
914
)
1015

1116
(component
1217
(core type $start (shared (func (param $context i32))))
1318
(core func $spawn (canon thread.spawn $start))
19+
20+
(core module $libc (table (export "start-table") 1 (ref null (shared func))))
21+
(core instance $libc (instantiate $libc))
22+
(core func $spawn_indirect (canon thread.spawn_indirect (table $libc "start-table")))
23+
1424
(core func $parallelism (canon thread.available_parallelism))
1525

1626
(core module $m
1727
(type $st (shared (func (param $context i32))))
1828
(import "" "spawn" (func (param (ref null $st)) (param i32) (result i32)))
29+
(import "" "spawn_indirect" (func (param i32) (param i32) (result i32)))
1930
(import "" "parallelism" (func (result i32)))
2031
)
2132

2233
(core instance (instantiate $m
2334
(with "" (instance
2435
(export "spawn" (func $spawn))
36+
(export "spawn_indirect" (func $spawn_indirect))
2537
(export "parallelism" (func $parallelism))
2638
))
2739
))

tests/snapshots/cli/missing-features/component-model/shared-everything-threads-builtins.wast.json

+7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313
"line": 12,
1414
"filename": "shared-everything-threads-builtins.1.wasm",
1515
"module_type": "binary",
16+
"text": "`thread.spawn_indirect` requires the shared-everything-threads proposal"
17+
},
18+
{
19+
"type": "assert_invalid",
20+
"line": 20,
21+
"filename": "shared-everything-threads-builtins.2.wasm",
22+
"module_type": "binary",
1623
"text": "`thread.available_parallelism` requires the shared-everything-threads proposal"
1724
}
1825
]

tests/snapshots/cli/shared-everything-threads/builtins.wast.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@
99
},
1010
{
1111
"type": "module",
12-
"line": 11,
12+
"line": 16,
1313
"filename": "builtins.1.wasm",
1414
"module_type": "binary"
1515
},
1616
{
1717
"type": "assert_invalid",
18-
"line": 31,
18+
"line": 43,
1919
"filename": "builtins.2.wasm",
2020
"module_type": "binary",
2121
"text": "spawn type must be shared"
2222
},
2323
{
2424
"type": "assert_invalid",
25-
"line": 39,
25+
"line": 51,
2626
"filename": "builtins.3.wasm",
2727
"module_type": "binary",
2828
"text": "spawn function must take a single `i32` argument (currently)"
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
(component
22
(core type $start (;0;) (shared (func (param i32))))
33
(core func $spawn (;0;) (canon thread.spawn $start))
4-
(core func $parallelism (;1;) (canon thread.available_parallelism))
4+
(core module $libc (;0;)
5+
(table (;0;) 1 (ref null (shared func)))
6+
(export "start-table" (table 0))
7+
)
8+
(core instance $libc (;0;) (instantiate $libc))
9+
(alias core export $libc "start-table" (core table (;0;)))
10+
(core func $spawn_indirect (;1;) (canon thread.spawn_indirect (table 0)))
11+
(core func $parallelism (;2;) (canon thread.available_parallelism))
512
)
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
(component
22
(core type $start (;0;) (shared (func (param i32))))
33
(core func $spawn (;0;) (canon thread.spawn $start))
4-
(core func $parallelism (;1;) (canon thread.available_parallelism))
5-
(core module $m (;0;)
4+
(core module $libc (;0;)
5+
(table (;0;) 1 (ref null (shared func)))
6+
(export "start-table" (table 0))
7+
)
8+
(core instance $libc (;0;) (instantiate $libc))
9+
(alias core export $libc "start-table" (core table (;0;)))
10+
(core func $spawn_indirect (;1;) (canon thread.spawn_indirect (table 0)))
11+
(core func $parallelism (;2;) (canon thread.available_parallelism))
12+
(core module $m (;1;)
613
(type $st (;0;) (shared (func (param i32))))
714
(type (;1;) (func (param (ref null $st) i32) (result i32)))
8-
(type (;2;) (func (result i32)))
15+
(type (;2;) (func (param i32 i32) (result i32)))
16+
(type (;3;) (func (result i32)))
917
(import "" "spawn" (func (;0;) (type 1)))
10-
(import "" "parallelism" (func (;1;) (type 2)))
18+
(import "" "spawn_indirect" (func (;1;) (type 2)))
19+
(import "" "parallelism" (func (;2;) (type 3)))
1120
)
12-
(core instance (;0;)
21+
(core instance (;1;)
1322
(export "spawn" (func $spawn))
23+
(export "spawn_indirect" (func $spawn_indirect))
1424
(export "parallelism" (func $parallelism))
1525
)
16-
(core instance (;1;) (instantiate $m
17-
(with "" (instance 0))
26+
(core instance (;2;) (instantiate $m
27+
(with "" (instance 1))
1828
)
1929
)
2030
)

0 commit comments

Comments
 (0)