Skip to content

Commit 5a83828

Browse files
authored
threads: add component model canonical functions (#1783)
* threads: add component model canonical functions The shared-everything-threads [proposal] adds two new component model canonical functions, `thread.spawn` and `thread.hw_concurrency`. This change adds initial support for these new functions, which should match what is specified in the [canonical ABI]. All of this support should be gated behind checks that ensure the shared-everything-threads proposal has been enabled. [proposal]: https://github.com/WebAssembly/shared-everything-threads [canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#-canon-threadspawn * Rework using core types * refactor: add `SubType::func` for brevity * Create concrete ref from core type ID * review: use core type index in reencoder * review: add `intern_sub_type` helper * review: move proposal check, add missing-features tests * refactor: rename CM builtins test * refactor: pass down `WasmFeatures` * fix: re-bless * review: make `thread.*` canonicals `shared`
1 parent 47680b0 commit 5a83828

21 files changed

Lines changed: 408 additions & 64 deletions

File tree

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,18 @@ impl ComponentBuilder {
386386
inc(&mut self.core_funcs)
387387
}
388388

389+
/// Declares a new `thread.spawn` intrinsic.
390+
pub fn thread_spawn(&mut self, ty: u32) -> u32 {
391+
self.canonical_functions().thread_spawn(ty);
392+
inc(&mut self.core_funcs)
393+
}
394+
395+
/// Declares a new `thread.hw_concurrency` intrinsic.
396+
pub fn thread_hw_concurrency(&mut self) -> u32 {
397+
self.canonical_functions().thread_hw_concurrency();
398+
inc(&mut self.core_funcs)
399+
}
400+
389401
/// Adds a new custom section to this component.
390402
pub fn custom_section(&mut self, section: &CustomSection<'_>) {
391403
self.flush();

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,23 @@ impl CanonicalFunctionSection {
144144
self.num_added += 1;
145145
self
146146
}
147+
148+
/// Defines a function which will spawns a new thread by invoking a shared
149+
/// function of type `ty_index`.
150+
pub fn thread_spawn(&mut self, ty_index: u32) -> &mut Self {
151+
self.bytes.push(0x05);
152+
ty_index.encode(&mut self.bytes);
153+
self.num_added += 1;
154+
self
155+
}
156+
157+
/// Defines a function which will return the number of threads that can be
158+
/// expected to execute concurrently.
159+
pub fn thread_hw_concurrency(&mut self) -> &mut Self {
160+
self.bytes.push(0x06);
161+
self.num_added += 1;
162+
self
163+
}
147164
}
148165

149166
impl Encode for CanonicalFunctionSection {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,13 @@ pub mod component_utils {
945945
let resource = reencoder.component_type_index(resource);
946946
section.resource_rep(resource);
947947
}
948+
wasmparser::CanonicalFunction::ThreadSpawn { func_ty_index } => {
949+
let func_ty = reencoder.type_index(func_ty_index);
950+
section.thread_spawn(func_ty);
951+
}
952+
wasmparser::CanonicalFunction::ThreadHwConcurrency => {
953+
section.thread_hw_concurrency();
954+
}
948955
}
949956
Ok(())
950957
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ pub enum CanonicalFunction {
6060
/// The type index of the resource that's being accessed.
6161
resource: u32,
6262
},
63+
/// A function which spawns a new thread by invoking the shared function.
64+
ThreadSpawn {
65+
/// The index of the function to spawn.
66+
func_ty_index: u32,
67+
},
68+
/// A function which returns the number of threads that can be expected to
69+
/// execute concurrently
70+
ThreadHwConcurrency,
6371
}
6472

6573
/// A reader for the canonical section of a WebAssembly component.
@@ -101,6 +109,10 @@ impl<'a> FromReader<'a> for CanonicalFunction {
101109
0x04 => CanonicalFunction::ResourceRep {
102110
resource: reader.read()?,
103111
},
112+
0x05 => CanonicalFunction::ThreadSpawn {
113+
func_ty_index: reader.read()?,
114+
},
115+
0x06 => CanonicalFunction::ThreadHwConcurrency,
104116
x => return reader.invalid_leading_byte(x, "canonical function"),
105117
})
106118
}

crates/wasmparser/src/readers/core/types.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,18 @@ impl SubType {
480480
self.composite_type.unwrap_array()
481481
}
482482

483+
/// Construct a function `SubType`.
484+
pub fn func(signature: FuncType, shared: bool) -> Self {
485+
Self {
486+
is_final: true,
487+
supertype_idx: None,
488+
composite_type: CompositeType {
489+
inner: CompositeInnerType::Func(signature),
490+
shared,
491+
},
492+
}
493+
}
494+
483495
/// Unwrap an `FuncType` or panic.
484496
///
485497
/// Does not check finality or whether there is a supertype.

crates/wasmparser/src/validator.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1217,7 +1217,7 @@ impl Validator {
12171217
current.funcs.reserve(count as usize);
12181218
Ok(())
12191219
},
1220-
|components, types, _, func, offset| {
1220+
|components, types, features, func, offset| {
12211221
let current = components.last_mut().unwrap();
12221222
match func {
12231223
crate::CanonicalFunction::Lift {
@@ -1244,6 +1244,12 @@ impl Validator {
12441244
crate::CanonicalFunction::ResourceRep { resource } => {
12451245
current.resource_rep(resource, types, offset)
12461246
}
1247+
crate::CanonicalFunction::ThreadSpawn { func_ty_index } => {
1248+
current.thread_spawn(func_ty_index, types, offset, features)
1249+
}
1250+
crate::CanonicalFunction::ThreadHwConcurrency => {
1251+
current.thread_hw_concurrency(types, offset, features)
1252+
}
12471253
}
12481254
},
12491255
)

crates/wasmparser/src/validator/component.rs

Lines changed: 94 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ use super::{
1010
ModuleType, RecordType, Remapping, ResourceId, TypeAlloc, TypeList, VariantCase,
1111
},
1212
};
13+
use crate::collections::index_map::Entry;
14+
use crate::limits::*;
1315
use crate::prelude::*;
16+
use crate::types::{
17+
ComponentAnyTypeId, ComponentCoreModuleTypeId, ComponentCoreTypeId, ComponentDefinedType,
18+
ComponentEntityType, Context, CoreInstanceTypeKind, LoweringInfo, Remap, SubtypeCx, TupleType,
19+
TypeInfo, VariantType,
20+
};
1421
use crate::validator::names::{ComponentName, ComponentNameKind, KebabStr, KebabString};
15-
use crate::{collections::index_map::Entry, CompositeInnerType};
1622
use crate::{
17-
limits::*,
18-
types::{
19-
ComponentAnyTypeId, ComponentCoreModuleTypeId, ComponentCoreTypeId, ComponentDefinedType,
20-
ComponentEntityType, Context, CoreInstanceTypeKind, LoweringInfo, Remap, SubtypeCx,
21-
TupleType, TypeInfo, VariantType,
22-
},
2323
BinaryReaderError, CanonicalOption, ComponentExportName, ComponentExternalKind,
24-
ComponentOuterAliasKind, ComponentTypeRef, CompositeType, ExternalKind, FuncType, GlobalType,
25-
InstantiationArgKind, MemoryType, RecGroup, Result, SubType, TableType, TypeBounds, ValType,
26-
WasmFeatures,
24+
ComponentOuterAliasKind, ComponentTypeRef, CompositeInnerType, ExternalKind, FuncType,
25+
GlobalType, InstantiationArgKind, MemoryType, PackedIndex, RefType, Result, SubType, TableType,
26+
TypeBounds, ValType, WasmFeatures,
2727
};
2828
use core::mem;
2929

@@ -127,7 +127,7 @@ pub(crate) struct ComponentState {
127127
/// itself.
128128
///
129129
/// The `Option<ValType>` in this mapping is whether or not the underlying
130-
/// reprsentation of the resource is known to this component. Immediately
130+
/// representation of the resource is known to this component. Immediately
131131
/// defined resources, for example, will have `Some(I32)` here. Resources
132132
/// that come from transitively defined components, for example, will have
133133
/// `None`. In the type context all entries here are `None`.
@@ -1001,19 +1001,8 @@ impl ComponentState {
10011001

10021002
self.check_options(None, &info, &options, types, offset)?;
10031003

1004-
let composite_type = CompositeType {
1005-
inner: CompositeInnerType::Func(info.into_func_type()),
1006-
shared: false,
1007-
};
1008-
let lowered_ty = SubType {
1009-
is_final: true,
1010-
supertype_idx: None,
1011-
composite_type,
1012-
};
1013-
1014-
let (_is_new, group_id) =
1015-
types.intern_canonical_rec_group(RecGroup::implicit(offset, lowered_ty));
1016-
let id = types[group_id].start;
1004+
let lowered_ty = SubType::func(info.into_func_type(), false);
1005+
let id = types.intern_sub_type(lowered_ty, offset);
10171006
self.core_funcs.push(id);
10181007

10191008
Ok(())
@@ -1026,18 +1015,9 @@ impl ComponentState {
10261015
offset: usize,
10271016
) -> Result<()> {
10281017
let rep = self.check_local_resource(resource, types, offset)?;
1029-
let composite_type = CompositeType {
1030-
inner: CompositeInnerType::Func(FuncType::new([rep], [ValType::I32])),
1031-
shared: false,
1032-
};
1033-
let core_ty = SubType {
1034-
is_final: true,
1035-
supertype_idx: None,
1036-
composite_type,
1037-
};
1038-
let (_is_new, group_id) =
1039-
types.intern_canonical_rec_group(RecGroup::implicit(offset, core_ty));
1040-
let id = types[group_id].start;
1018+
let func_ty = FuncType::new([rep], [ValType::I32]);
1019+
let core_ty = SubType::func(func_ty, false);
1020+
let id = types.intern_sub_type(core_ty, offset);
10411021
self.core_funcs.push(id);
10421022
Ok(())
10431023
}
@@ -1049,18 +1029,9 @@ impl ComponentState {
10491029
offset: usize,
10501030
) -> Result<()> {
10511031
self.resource_at(resource, types, offset)?;
1052-
let composite_type = CompositeType {
1053-
inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [])),
1054-
shared: false,
1055-
};
1056-
let core_ty = SubType {
1057-
is_final: true,
1058-
supertype_idx: None,
1059-
composite_type,
1060-
};
1061-
let (_is_new, group_id) =
1062-
types.intern_canonical_rec_group(RecGroup::implicit(offset, core_ty));
1063-
let id = types[group_id].start;
1032+
let func_ty = FuncType::new([ValType::I32], []);
1033+
let core_ty = SubType::func(func_ty, false);
1034+
let id = types.intern_sub_type(core_ty, offset);
10641035
self.core_funcs.push(id);
10651036
Ok(())
10661037
}
@@ -1072,18 +1043,9 @@ impl ComponentState {
10721043
offset: usize,
10731044
) -> Result<()> {
10741045
let rep = self.check_local_resource(resource, types, offset)?;
1075-
let composite_type = CompositeType {
1076-
inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [rep])),
1077-
shared: false,
1078-
};
1079-
let core_ty = SubType {
1080-
is_final: true,
1081-
supertype_idx: None,
1082-
composite_type,
1083-
};
1084-
let (_is_new, group_id) =
1085-
types.intern_canonical_rec_group(RecGroup::implicit(offset, core_ty));
1086-
let id = types[group_id].start;
1046+
let func_ty = FuncType::new([ValType::I32], [rep]);
1047+
let core_ty = SubType::func(func_ty, false);
1048+
let id = types.intern_sub_type(core_ty, offset);
10871049
self.core_funcs.push(id);
10881050
Ok(())
10891051
}
@@ -1112,6 +1074,78 @@ impl ComponentState {
11121074
bail!(offset, "type index {} is not a resource type", idx)
11131075
}
11141076

1077+
pub fn thread_spawn(
1078+
&mut self,
1079+
func_ty_index: u32,
1080+
types: &mut TypeAlloc,
1081+
offset: usize,
1082+
features: &WasmFeatures,
1083+
) -> Result<()> {
1084+
if !features.shared_everything_threads() {
1085+
bail!(
1086+
offset,
1087+
"`thread.spawn` requires the shared-everything-threads proposal"
1088+
)
1089+
}
1090+
1091+
// Validate the type accepted by `thread.spawn`.
1092+
let core_type_id = match self.core_type_at(func_ty_index, offset)? {
1093+
ComponentCoreTypeId::Sub(c) => c,
1094+
ComponentCoreTypeId::Module(_) => bail!(offset, "expected a core function type"),
1095+
};
1096+
let sub_ty = &types[core_type_id];
1097+
if !sub_ty.composite_type.shared {
1098+
bail!(offset, "spawn type must be shared");
1099+
}
1100+
match &sub_ty.composite_type.inner {
1101+
CompositeInnerType::Func(func_ty) => {
1102+
if func_ty.params() != [ValType::I32] {
1103+
bail!(
1104+
offset,
1105+
"spawn function must take a single `i32` argument (currently)"
1106+
);
1107+
}
1108+
if func_ty.results() != [] {
1109+
bail!(offset, "spawn function must not return any values");
1110+
}
1111+
}
1112+
_ => bail!(offset, "spawn type must be a function"),
1113+
}
1114+
1115+
// Insert the core function.
1116+
let packed_index = PackedIndex::from_id(core_type_id).ok_or_else(|| {
1117+
format_err!(offset, "implementation limit: too many types in `TypeList`")
1118+
})?;
1119+
let start_func_ref = RefType::concrete(true, packed_index);
1120+
let func_ty = FuncType::new([ValType::Ref(start_func_ref), ValType::I32], [ValType::I32]);
1121+
let core_ty = SubType::func(func_ty, true);
1122+
let id = types.intern_sub_type(core_ty, offset);
1123+
self.core_funcs.push(id);
1124+
1125+
Ok(())
1126+
}
1127+
1128+
pub fn thread_hw_concurrency(
1129+
&mut self,
1130+
types: &mut TypeAlloc,
1131+
offset: usize,
1132+
features: &WasmFeatures,
1133+
) -> Result<()> {
1134+
if !features.shared_everything_threads() {
1135+
bail!(
1136+
offset,
1137+
"`thread.hw_concurrency` requires the shared-everything-threads proposal"
1138+
)
1139+
}
1140+
1141+
let func_ty = FuncType::new([], [ValType::I32]);
1142+
let core_ty = SubType::func(func_ty, true);
1143+
let id = types.intern_sub_type(core_ty, offset);
1144+
self.core_funcs.push(id);
1145+
1146+
Ok(())
1147+
}
1148+
11151149
pub fn add_component(&mut self, component: ComponentType, types: &mut TypeAlloc) -> Result<()> {
11161150
let id = types.push_ty(component);
11171151
self.components.push(id);

crates/wasmparser/src/validator/types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2627,6 +2627,14 @@ impl TypeList {
26272627
return (true, rec_group_id);
26282628
}
26292629

2630+
/// Helper for interning a sub type as a rec group; see
2631+
/// [`Self::intern_canonical_rec_group`].
2632+
pub fn intern_sub_type(&mut self, sub_ty: SubType, offset: usize) -> CoreTypeId {
2633+
let (_is_new, group_id) =
2634+
self.intern_canonical_rec_group(RecGroup::implicit(offset, sub_ty));
2635+
self[group_id].start
2636+
}
2637+
26302638
/// Get the `CoreTypeId` for a local index into a rec group.
26312639
pub fn rec_group_local_id(
26322640
&self,

crates/wasmprinter/src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,6 +2377,27 @@ impl Printer<'_, '_> {
23772377
self.end_group()?;
23782378
state.core.funcs += 1;
23792379
}
2380+
CanonicalFunction::ThreadSpawn {
2381+
func_ty_index: func_index,
2382+
} => {
2383+
self.start_group("core func ")?;
2384+
self.print_name(&state.core.func_names, state.core.funcs)?;
2385+
self.result.write_str(" ")?;
2386+
self.start_group("canon thread.spawn ")?;
2387+
self.print_idx(&state.core.type_names, func_index)?;
2388+
self.end_group()?;
2389+
self.end_group()?;
2390+
state.core.funcs += 1;
2391+
}
2392+
CanonicalFunction::ThreadHwConcurrency => {
2393+
self.start_group("core func ")?;
2394+
self.print_name(&state.core.func_names, state.core.funcs)?;
2395+
self.result.write_str(" ")?;
2396+
self.start_group("canon thread.hw_concurrency")?;
2397+
self.end_group()?;
2398+
self.end_group()?;
2399+
state.core.funcs += 1;
2400+
}
23802401
}
23812402
}
23822403

crates/wast/src/component/binary.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,14 @@ impl<'a> Encoder<'a> {
347347
self.core_func_names.push(name);
348348
self.funcs.resource_rep(info.ty.into());
349349
}
350+
CanonicalFuncKind::ThreadSpawn(info) => {
351+
self.core_func_names.push(name);
352+
self.funcs.thread_spawn(info.ty.into());
353+
}
354+
CanonicalFuncKind::ThreadHwConcurrency(_info) => {
355+
self.core_func_names.push(name);
356+
self.funcs.thread_hw_concurrency();
357+
}
350358
}
351359

352360
self.flush(Some(self.funcs.id()));

0 commit comments

Comments
 (0)