From 413422ec84946d73c5454a190c08da3d57efa5a7 Mon Sep 17 00:00:00 2001 From: Doron Somech Date: Thu, 18 Jun 2026 15:57:00 +0300 Subject: [PATCH 1/5] Fix StorageType::is_val_type to check the value-type variant It previously matched the `I16` variant instead of `ValType`, so it returned the wrong answer for every storage type. The method is otherwise unused inside the tree, which is why this went unnoticed. --- crates/wasmtime/src/runtime/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasmtime/src/runtime/types.rs b/crates/wasmtime/src/runtime/types.rs index 8dcdc2eeab61..ddb11ac7b2e0 100644 --- a/crates/wasmtime/src/runtime/types.rs +++ b/crates/wasmtime/src/runtime/types.rs @@ -1617,7 +1617,7 @@ impl StorageType { /// Is this a Wasm value type? #[inline] pub fn is_val_type(&self) -> bool { - matches!(self, Self::I16) + matches!(self, Self::ValType(_)) } /// Get this storage type's underlying value type, if any. From ff4590889aff5bb4e20bac58086b13891ae9272a Mon Sep 17 00:00:00 2001 From: Doron Somech Date: Thu, 18 Jun 2026 15:57:12 +0300 Subject: [PATCH 2/5] Add RecGroupBuilder embedder API for defining recursion groups Adds `RecGroupBuilder`, which lets embedders declare kind-typed labels (`PendingStructId`/`PendingArrayId`/`PendingFuncId`), use them as forward references while defining other types via a small build-time "template" family, and register the whole group at once with `build()`. This makes it possible to construct self-referential and mutually-recursive struct/array/func types directly from the embedder API, which previously required plucking such types out of a module's imports/exports. The builder lowers its members to module-canonical `WasmSubType`s (using 0-based `Module` indices for intra-group references and `Engine` indices for already-registered types) and reuses the existing rec-group registration path, so hash-consing, runtime canonicalization, supertype lists, and GC layouts all come for free. Implements the embedder API requested in #10176. --- RELEASES.md | 10 + crates/wasmtime/src/runtime.rs | 2 + crates/wasmtime/src/runtime/rec_group.rs | 997 +++++++++++++++++++ crates/wasmtime/src/runtime/type_registry.rs | 73 ++ tests/all/types.rs | 360 +++++++ 5 files changed, 1442 insertions(+) create mode 100644 crates/wasmtime/src/runtime/rec_group.rs diff --git a/RELEASES.md b/RELEASES.md index b475604de84d..9fdd9527b921 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,8 +4,18 @@ Unreleased. ### Added +* Added `RecGroupBuilder`, an embedder API for defining recursion groups of Wasm + types, including types that reference themselves or each other (e.g. linked + lists and mutually-recursive structs). + [#10176](https://github.com/bytecodealliance/wasmtime/issues/10176) + ### Changed +### Fixed + +* Fixed `StorageType::is_val_type` to report whether the storage type is a value + type; it previously returned whether it was an `i16`. + -------------------------------------------------------------------------------- Release notes for previous releases of Wasmtime can be found on the respective diff --git a/crates/wasmtime/src/runtime.rs b/crates/wasmtime/src/runtime.rs index 78eba4ad6367..84883858637b 100644 --- a/crates/wasmtime/src/runtime.rs +++ b/crates/wasmtime/src/runtime.rs @@ -53,6 +53,7 @@ pub(crate) mod memory; pub(crate) mod module; #[cfg(feature = "debug-builtins")] pub(crate) mod native_debug; +pub(crate) mod rec_group; pub(crate) mod resources; pub(crate) mod store; pub(crate) mod trampoline; @@ -97,6 +98,7 @@ pub use limits::*; pub use linker::*; pub use memory::*; pub use module::{Module, ModuleExport, ModuleFunction}; +pub use rec_group::*; pub use resources::*; #[cfg(all(feature = "async", feature = "call-hook"))] pub use store::CallHookHandler; diff --git a/crates/wasmtime/src/runtime/rec_group.rs b/crates/wasmtime/src/runtime/rec_group.rs new file mode 100644 index 000000000000..70e2dee3c2fe --- /dev/null +++ b/crates/wasmtime/src/runtime/rec_group.rs @@ -0,0 +1,997 @@ +//! Embedder API for defining recursion groups of Wasm types. +//! +//! The one-off constructors `StructType::new`, `ArrayType::new`, and +//! `FuncType::new` cannot describe types that reference themselves or each +//! other, because the constructors require all referenced types to already +//! exist. [`RecGroupBuilder`] lifts that restriction: you *declare* a type to +//! get a kind-typed label (a [`PendingStructId`], [`PendingArrayId`], or +//! [`PendingFuncId`]), use that label as a forward reference while defining +//! other types, and then *define* it later. The whole group is validated and +//! registered together when [`RecGroupBuilder::build`] is called. +//! +//! ``` +//! # use wasmtime::*; +//! # fn main() -> Result<()> { +//! let engine = Engine::default(); +//! +//! // Two mutually-recursive struct types. +//! let mut builder = RecGroupBuilder::new(&engine); +//! let s1 = builder.declare_struct(); +//! let s2 = builder.declare_struct(); +//! builder.define_struct(s1, [FieldTemplate::ref_(Mutability::Var, true, s2)]); +//! builder.define_struct(s2, [FieldTemplate::ref_(Mutability::Const, false, s1)]); +//! let group = builder.build()?; +//! +//! let s1: StructType = group.struct_(s1); +//! let s2: StructType = group.struct_(s2); +//! assert!(s1.field(0).unwrap().element_type().is_val_type()); +//! # Ok(()) +//! # } +//! ``` + +use crate::prelude::*; +use crate::type_registry::RegisteredType; +use crate::{ + ArrayType, Engine, FieldType, Finality, FuncType, HeapType, Mutability, StorageType, + StructType, ValType, +}; +use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; +use wasmtime_environ::{ + EngineOrModuleTypeIndex, EntityRef, ModuleInternedTypeIndex, WasmArrayType, + WasmCompositeInnerType, WasmCompositeType, WasmFieldType, WasmFuncType, WasmHeapType, + WasmRefType, WasmStorageType, WasmStructType, WasmSubType, WasmValType, +}; + +/// Maximum number of fields in a struct, mirroring `StructType::from_wasm_struct_type`. +const MAX_FIELDS: usize = 10_000; + +/// A process-global counter used to give each [`RecGroupBuilder`] a distinct id +/// so that labels from one builder cannot be accidentally used with another. +static NEXT_BUILDER_ID: AtomicUsize = AtomicUsize::new(0); + +fn next_builder_id() -> usize { + NEXT_BUILDER_ID.fetch_add(1, Relaxed) +} + +/// The 0-based index of a member within the rec group being built, as a +/// module-level type reference (the form `register_rec_group` expects for +/// intra-group references). +fn module_index(index: u32) -> EngineOrModuleTypeIndex { + EngineOrModuleTypeIndex::Module(ModuleInternedTypeIndex::new(index as usize)) +} + +/// A kind-typed label for a struct type being defined in a [`RecGroupBuilder`]. +/// +/// Obtained from [`RecGroupBuilder::declare_struct`] and used both to define the +/// type and to forward-reference it from other types in the same group. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct PendingStructId { + builder_id: usize, + index: u32, +} + +/// A kind-typed label for an array type being defined in a [`RecGroupBuilder`]. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct PendingArrayId { + builder_id: usize, + index: u32, +} + +/// A kind-typed label for a function type being defined in a [`RecGroupBuilder`]. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct PendingFuncId { + builder_id: usize, + index: u32, +} + +/// A heap type usable while building a recursion group. +/// +/// This mirrors [`HeapType`], with one extra variant per kind for forward +/// references to sibling types being defined in the same [`RecGroupBuilder`]. +/// +/// Already-known heap types (abstract heap types like `any`, or +/// already-registered concrete types) convert into this type via `From`/`Into`, +/// so the common case requires no ceremony. +#[derive(Clone, Debug)] +pub enum HeapTypeTemplate { + /// An abstract heap type or an already-registered concrete heap type. + Type(HeapType), + /// A forward reference to a struct defined in the same rec group. + LocalStruct(PendingStructId), + /// A forward reference to an array defined in the same rec group. + LocalArray(PendingArrayId), + /// A forward reference to a function defined in the same rec group. + LocalFunc(PendingFuncId), +} + +impl From for HeapTypeTemplate { + fn from(ty: HeapType) -> Self { + HeapTypeTemplate::Type(ty) + } +} +impl From for HeapTypeTemplate { + fn from(ty: StructType) -> Self { + HeapTypeTemplate::Type(HeapType::ConcreteStruct(ty)) + } +} +impl From for HeapTypeTemplate { + fn from(ty: ArrayType) -> Self { + HeapTypeTemplate::Type(HeapType::ConcreteArray(ty)) + } +} +impl From for HeapTypeTemplate { + fn from(ty: FuncType) -> Self { + HeapTypeTemplate::Type(HeapType::ConcreteFunc(ty)) + } +} +impl From for HeapTypeTemplate { + fn from(id: PendingStructId) -> Self { + HeapTypeTemplate::LocalStruct(id) + } +} +impl From for HeapTypeTemplate { + fn from(id: PendingArrayId) -> Self { + HeapTypeTemplate::LocalArray(id) + } +} +impl From for HeapTypeTemplate { + fn from(id: PendingFuncId) -> Self { + HeapTypeTemplate::LocalFunc(id) + } +} + +/// A value type usable while building a recursion group. +/// +/// Mirrors [`ValType`]; the only thing it can express that a [`ValType`] cannot +/// is a `ref` whose target is a sibling type being defined in the same group. +#[derive(Clone, Debug)] +pub enum ValTypeTemplate { + /// A scalar value type or an already-known concrete reference type. + Type(ValType), + /// A `ref` (nullable or not) whose target may be a forward reference. + Ref { + /// Whether the reference is nullable. + nullable: bool, + /// The referenced heap type. + heap: HeapTypeTemplate, + }, +} + +impl ValTypeTemplate { + /// Construct a `ref` value type whose target may be a sibling type in the + /// same rec group. + pub fn ref_(nullable: bool, heap: impl Into) -> Self { + ValTypeTemplate::Ref { + nullable, + heap: heap.into(), + } + } +} + +impl From for ValTypeTemplate { + fn from(ty: ValType) -> Self { + ValTypeTemplate::Type(ty) + } +} + +/// The storage type of a struct field or array element while building a +/// recursion group. +/// +/// Mirrors [`StorageType`]; the only thing it can express that a [`StorageType`] +/// cannot is a `ref` whose target is a sibling type being defined in the same +/// group. +#[derive(Clone, Debug)] +pub enum StorageTypeTemplate { + /// A packed integer storage type or an already-known value type. + Type(StorageType), + /// A `ref` (nullable or not) whose target may be a forward reference. + Ref { + /// Whether the reference is nullable. + nullable: bool, + /// The referenced heap type. + heap: HeapTypeTemplate, + }, +} + +impl From for StorageTypeTemplate { + fn from(ty: StorageType) -> Self { + StorageTypeTemplate::Type(ty) + } +} +impl From for StorageTypeTemplate { + fn from(ty: ValType) -> Self { + StorageTypeTemplate::Type(StorageType::ValType(ty)) + } +} + +/// A struct field or array element type while building a recursion group. +/// +/// Mirrors [`FieldType`]. A plain [`FieldType`] (with no forward references) +/// converts into this type via `From`/`Into`. +#[derive(Clone, Debug)] +pub struct FieldTemplate { + mutability: Mutability, + element: StorageTypeTemplate, +} + +impl FieldTemplate { + /// Construct a field template from a mutability and element storage type. + pub fn new(mutability: Mutability, element: impl Into) -> Self { + FieldTemplate { + mutability, + element: element.into(), + } + } + + /// Construct a field template whose element is a `ref` that may forward- + /// reference a sibling type in the same rec group. + pub fn ref_(mutability: Mutability, nullable: bool, heap: impl Into) -> Self { + FieldTemplate { + mutability, + element: StorageTypeTemplate::Ref { + nullable, + heap: heap.into(), + }, + } + } +} + +impl From for FieldTemplate { + fn from(ty: FieldType) -> Self { + FieldTemplate { + mutability: ty.mutability(), + element: StorageTypeTemplate::Type(ty.element_type().clone()), + } + } +} + +/// The supertype of a struct type being defined in a [`RecGroupBuilder`]. +/// +/// May be either a sibling label ([`PendingStructId`]) or an already-registered +/// [`StructType`]; both convert into this type via `From`/`Into`. +#[derive(Clone, Debug)] +pub enum StructSuperType { + /// A supertype defined as a sibling in the same rec group. + Local(PendingStructId), + /// An already-registered supertype. + Type(StructType), +} + +impl From for StructSuperType { + fn from(id: PendingStructId) -> Self { + StructSuperType::Local(id) + } +} +impl From for StructSuperType { + fn from(ty: StructType) -> Self { + StructSuperType::Type(ty) + } +} + +/// The supertype of an array type being defined in a [`RecGroupBuilder`]. +/// +/// May be either a sibling label ([`PendingArrayId`]) or an already-registered +/// [`ArrayType`]; both convert into this type via `From`/`Into`. +#[derive(Clone, Debug)] +pub enum ArraySuperType { + /// A supertype defined as a sibling in the same rec group. + Local(PendingArrayId), + /// An already-registered supertype. + Type(ArrayType), +} + +impl From for ArraySuperType { + fn from(id: PendingArrayId) -> Self { + ArraySuperType::Local(id) + } +} +impl From for ArraySuperType { + fn from(ty: ArrayType) -> Self { + ArraySuperType::Type(ty) + } +} + +/// The supertype of a function type being defined in a [`RecGroupBuilder`]. +/// +/// May be either a sibling label ([`PendingFuncId`]) or an already-registered +/// [`FuncType`]; both convert into this type via `From`/`Into`. +#[derive(Clone, Debug)] +pub enum FuncSuperType { + /// A supertype defined as a sibling in the same rec group. + Local(PendingFuncId), + /// An already-registered supertype. + Type(FuncType), +} + +impl From for FuncSuperType { + fn from(id: PendingFuncId) -> Self { + FuncSuperType::Local(id) + } +} +impl From for FuncSuperType { + fn from(ty: FuncType) -> Self { + FuncSuperType::Type(ty) + } +} + +/// One of the concrete composite types in a registered [`RecGroup`]. +#[derive(Clone, Debug)] +pub enum CompositeType { + /// A struct type. + Struct(StructType), + /// An array type. + Array(ArrayType), + /// A function type. + Func(FuncType), +} + +/// The in-progress definition of one member of a rec group. +enum MemberDef { + Struct { + finality: Finality, + supertype: Option, + fields: Vec, + }, + Array { + finality: Finality, + supertype: Option, + field: FieldTemplate, + }, + Func { + finality: Finality, + supertype: Option, + params: Vec, + results: Vec, + }, +} + +/// A builder for defining a recursion group of Wasm types, including types that +/// reference themselves or each other. +/// +/// See the [module-level documentation](crate::RecGroupBuilder) for an overview +/// and examples. +pub struct RecGroupBuilder { + engine: Engine, + builder_id: usize, + members: Vec>, +} + +impl RecGroupBuilder { + /// Create a new, empty rec group builder associated with the given engine. + pub fn new(engine: &Engine) -> Self { + RecGroupBuilder { + engine: engine.clone(), + builder_id: next_builder_id(), + members: Vec::new(), + } + } + + fn declare(&mut self) -> u32 { + let index = u32::try_from(self.members.len()).expect("too many types in a rec group"); + self.members.push(None); + index + } + + /// Declare a struct type, returning a label that can be used as a forward + /// reference before the type is defined via [`define_struct`][Self::define_struct]. + pub fn declare_struct(&mut self) -> PendingStructId { + PendingStructId { + builder_id: self.builder_id, + index: self.declare(), + } + } + + /// Declare an array type, returning a label that can be used as a forward + /// reference before the type is defined via [`define_array`][Self::define_array]. + pub fn declare_array(&mut self) -> PendingArrayId { + PendingArrayId { + builder_id: self.builder_id, + index: self.declare(), + } + } + + /// Declare a function type, returning a label that can be used as a forward + /// reference before the type is defined via [`define_func`][Self::define_func]. + pub fn declare_func(&mut self) -> PendingFuncId { + PendingFuncId { + builder_id: self.builder_id, + index: self.declare(), + } + } + + #[track_caller] + fn check_owns_struct(&self, id: PendingStructId) { + assert_eq!( + id.builder_id, self.builder_id, + "`PendingStructId` used with a different `RecGroupBuilder` than it came from" + ); + } + #[track_caller] + fn check_owns_array(&self, id: PendingArrayId) { + assert_eq!( + id.builder_id, self.builder_id, + "`PendingArrayId` used with a different `RecGroupBuilder` than it came from" + ); + } + #[track_caller] + fn check_owns_func(&self, id: PendingFuncId) { + assert_eq!( + id.builder_id, self.builder_id, + "`PendingFuncId` used with a different `RecGroupBuilder` than it came from" + ); + } + + /// Define a previously-declared struct type, with the given fields, as a + /// final type without a supertype. + pub fn define_struct( + &mut self, + id: PendingStructId, + fields: impl IntoIterator>, + ) -> &mut Self { + self.define_struct_with_finality_and_supertype( + id, + Finality::Final, + None::, + fields, + ) + } + + /// Define a previously-declared struct type with the given finality, + /// supertype, and fields. + /// + /// The supertype may be either a sibling label ([`PendingStructId`]) or an + /// already-registered [`StructType`]. + pub fn define_struct_with_finality_and_supertype( + &mut self, + id: PendingStructId, + finality: Finality, + supertype: Option>, + fields: impl IntoIterator>, + ) -> &mut Self { + self.check_owns_struct(id); + self.members[id.index as usize] = Some(MemberDef::Struct { + finality, + supertype: supertype.map(Into::into), + fields: fields.into_iter().map(Into::into).collect(), + }); + self + } + + /// Declare and define a final struct type with no supertype, returning its + /// label. + pub fn add_struct( + &mut self, + fields: impl IntoIterator>, + ) -> PendingStructId { + let id = self.declare_struct(); + self.define_struct(id, fields); + id + } + + /// Define a previously-declared array type, with the given element type, as + /// a final type without a supertype. + pub fn define_array( + &mut self, + id: PendingArrayId, + field: impl Into, + ) -> &mut Self { + self.define_array_with_finality_and_supertype( + id, + Finality::Final, + None::, + field, + ) + } + + /// Define a previously-declared array type with the given finality, + /// supertype, and element type. + pub fn define_array_with_finality_and_supertype( + &mut self, + id: PendingArrayId, + finality: Finality, + supertype: Option>, + field: impl Into, + ) -> &mut Self { + self.check_owns_array(id); + self.members[id.index as usize] = Some(MemberDef::Array { + finality, + supertype: supertype.map(Into::into), + field: field.into(), + }); + self + } + + /// Declare and define a final array type with no supertype, returning its + /// label. + pub fn add_array(&mut self, field: impl Into) -> PendingArrayId { + let id = self.declare_array(); + self.define_array(id, field); + id + } + + /// Define a previously-declared function type, with the given parameters and + /// results, as a final type without a supertype. + pub fn define_func( + &mut self, + id: PendingFuncId, + params: impl IntoIterator>, + results: impl IntoIterator>, + ) -> &mut Self { + self.define_func_with_finality_and_supertype( + id, + Finality::Final, + None::, + params, + results, + ) + } + + /// Define a previously-declared function type with the given finality, + /// supertype, parameters, and results. + pub fn define_func_with_finality_and_supertype( + &mut self, + id: PendingFuncId, + finality: Finality, + supertype: Option>, + params: impl IntoIterator>, + results: impl IntoIterator>, + ) -> &mut Self { + self.check_owns_func(id); + self.members[id.index as usize] = Some(MemberDef::Func { + finality, + supertype: supertype.map(Into::into), + params: params.into_iter().map(Into::into).collect(), + results: results.into_iter().map(Into::into).collect(), + }); + self + } + + /// Declare and define a final function type with no supertype, returning its + /// label. + pub fn add_func( + &mut self, + params: impl IntoIterator>, + results: impl IntoIterator>, + ) -> PendingFuncId { + let id = self.declare_func(); + self.define_func(id, params, results); + id + } + + /// Finish building the rec group: validate all of its types, register them + /// with the engine, and return the resulting [`RecGroup`]. + /// + /// Returns an error if any declared type was never defined, if any type + /// references a type from a different engine, if a supertype is final, if a + /// type does not structurally match its declared supertype, or if a struct + /// exceeds the implementation's field-count limit. + pub fn build(self) -> Result { + let engine = &self.engine; + + ensure!( + !self.members.is_empty(), + "a rec group must contain at least one type" + ); + + // 1. Every declared label must have been defined. + let mut defs = Vec::with_capacity(self.members.len()); + for (i, member) in self.members.iter().enumerate() { + match member { + Some(def) => defs.push(def), + None => bail!("type {i} was declared but never defined"), + } + } + + // 2. Lower each member to a `WasmSubType`, checking engine ownership and + // supertype finality as we go. + let mut sub_types = Vec::with_capacity(defs.len()); + for def in &defs { + sub_types.push(self.lower_member(def)?); + } + + // 3. Register the whole group with the engine. + let registered = engine.register_rec_group_types(sub_types.into_iter())?; + + let group = RecGroup { + builder_id: self.builder_id, + types: registered, + }; + + // 4. Now that everything is registered (so that sibling references are + // real, resolvable types), validate that each type structurally + // matches its declared supertype. On failure, `group` is dropped, + // which unregisters the types. + for (i, def) in defs.iter().enumerate() { + group.validate_supertype(i, def)?; + } + + Ok(group) + } + + /// Look up the declared finality of a sibling member by index. + fn member_finality(&self, index: u32) -> Finality { + match self.members[index as usize] + .as_ref() + .expect("all members defined by this point") + { + MemberDef::Struct { finality, .. } + | MemberDef::Array { finality, .. } + | MemberDef::Func { finality, .. } => *finality, + } + } + + fn lower_member(&self, def: &MemberDef) -> Result { + let (is_final, supertype, inner) = match def { + MemberDef::Struct { + finality, + supertype, + fields, + } => { + ensure!( + fields.len() <= MAX_FIELDS, + "attempted to define a struct type with {} fields, but that is more than the \ + maximum supported number of fields ({MAX_FIELDS})", + fields.len(), + ); + let supertype = self.lower_struct_supertype(supertype.as_ref())?; + let fields = fields + .iter() + .map(|f| self.lower_field(f)) + .collect::>>()?; + ( + finality.is_final(), + supertype, + WasmCompositeInnerType::Struct(WasmStructType { + fields: fields.into(), + }), + ) + } + MemberDef::Array { + finality, + supertype, + field, + } => { + let supertype = self.lower_array_supertype(supertype.as_ref())?; + let field = self.lower_field(field)?; + ( + finality.is_final(), + supertype, + WasmCompositeInnerType::Array(WasmArrayType(field)), + ) + } + MemberDef::Func { + finality, + supertype, + params, + results, + } => { + let supertype = self.lower_func_supertype(supertype.as_ref())?; + let params = params + .iter() + .map(|p| self.lower_val(p)) + .collect::>>()?; + let results = results + .iter() + .map(|r| self.lower_val(r)) + .collect::>>()?; + let func = WasmFuncType::new(params, results)?; + ( + finality.is_final(), + supertype, + WasmCompositeInnerType::Func(func), + ) + } + }; + + Ok(WasmSubType { + is_final, + supertype, + composite_type: WasmCompositeType { + shared: false, + inner, + }, + }) + } + + fn lower_struct_supertype( + &self, + supertype: Option<&StructSuperType>, + ) -> Result> { + Ok(match supertype { + None => None, + Some(StructSuperType::Local(id)) => { + self.check_owns_struct(*id); + ensure!( + self.member_finality(id.index).is_non_final(), + "cannot create a subtype of a final supertype" + ); + Some(module_index(id.index)) + } + Some(StructSuperType::Type(ty)) => Some(self.lower_concrete_supertype( + ty.comes_from_same_engine(&self.engine), + ty.finality(), + ty.type_index(), + )?), + }) + } + + fn lower_array_supertype( + &self, + supertype: Option<&ArraySuperType>, + ) -> Result> { + Ok(match supertype { + None => None, + Some(ArraySuperType::Local(id)) => { + self.check_owns_array(*id); + ensure!( + self.member_finality(id.index).is_non_final(), + "cannot create a subtype of a final supertype" + ); + Some(module_index(id.index)) + } + Some(ArraySuperType::Type(ty)) => Some(self.lower_concrete_supertype( + ty.comes_from_same_engine(&self.engine), + ty.finality(), + ty.type_index(), + )?), + }) + } + + fn lower_func_supertype( + &self, + supertype: Option<&FuncSuperType>, + ) -> Result> { + Ok(match supertype { + None => None, + Some(FuncSuperType::Local(id)) => { + self.check_owns_func(*id); + ensure!( + self.member_finality(id.index).is_non_final(), + "cannot create a subtype of a final supertype" + ); + Some(module_index(id.index)) + } + Some(FuncSuperType::Type(ty)) => Some(self.lower_concrete_supertype( + ty.comes_from_same_engine(&self.engine), + ty.finality(), + ty.type_index(), + )?), + }) + } + + fn lower_concrete_supertype( + &self, + same_engine: bool, + finality: Finality, + index: wasmtime_environ::VMSharedTypeIndex, + ) -> Result { + ensure!( + same_engine, + "supertype is associated with a different engine" + ); + ensure!( + finality.is_non_final(), + "cannot create a subtype of a final supertype" + ); + Ok(EngineOrModuleTypeIndex::Engine(index)) + } + + fn lower_field(&self, field: &FieldTemplate) -> Result { + Ok(WasmFieldType { + element_type: self.lower_storage(&field.element)?, + mutable: field.mutability.is_var(), + }) + } + + fn lower_storage(&self, storage: &StorageTypeTemplate) -> Result { + Ok(match storage { + StorageTypeTemplate::Type(ty) => { + ensure!( + ty.comes_from_same_engine(&self.engine), + "type is associated with a different engine" + ); + ty.to_wasm_storage_type() + } + StorageTypeTemplate::Ref { nullable, heap } => { + WasmStorageType::Val(WasmValType::Ref(WasmRefType { + nullable: *nullable, + heap_type: self.lower_heap(heap)?, + })) + } + }) + } + + fn lower_val(&self, val: &ValTypeTemplate) -> Result { + Ok(match val { + ValTypeTemplate::Type(ty) => { + ensure!( + ty.comes_from_same_engine(&self.engine), + "type is associated with a different engine" + ); + ty.to_wasm_type() + } + ValTypeTemplate::Ref { nullable, heap } => WasmValType::Ref(WasmRefType { + nullable: *nullable, + heap_type: self.lower_heap(heap)?, + }), + }) + } + + fn lower_heap(&self, heap: &HeapTypeTemplate) -> Result { + Ok(match heap { + HeapTypeTemplate::Type(ty) => { + ensure!( + ty.comes_from_same_engine(&self.engine), + "type is associated with a different engine" + ); + ty.to_wasm_type() + } + HeapTypeTemplate::LocalStruct(id) => { + self.check_owns_struct(*id); + WasmHeapType::ConcreteStruct(module_index(id.index)) + } + HeapTypeTemplate::LocalArray(id) => { + self.check_owns_array(*id); + WasmHeapType::ConcreteArray(module_index(id.index)) + } + HeapTypeTemplate::LocalFunc(id) => { + self.check_owns_func(*id); + WasmHeapType::ConcreteFunc(module_index(id.index)) + } + }) + } +} + +/// A registered recursion group of Wasm types. +/// +/// Produced by [`RecGroupBuilder::build`]. Use the kind-typed getters +/// ([`struct_`][Self::struct_], [`array`][Self::array], [`func`][Self::func]) to +/// retrieve a member by its label. +/// +/// The group's types stay registered with the engine for as long as either this +/// `RecGroup` or any type retrieved from it is alive. +#[derive(Debug)] +pub struct RecGroup { + builder_id: usize, + types: Vec, +} + +impl RecGroup { + /// The number of types in this rec group. + pub fn len(&self) -> usize { + self.types.len() + } + + /// Whether this rec group is empty. + /// + /// This is always `false`, as a rec group must contain at least one type; + /// it exists for API completeness alongside [`len`][Self::len]. + pub fn is_empty(&self) -> bool { + self.types.is_empty() + } + + /// Get the struct type for the given label. + /// + /// # Panics + /// + /// Panics if the label did not come from the [`RecGroupBuilder`] that + /// produced this rec group. + pub fn struct_(&self, id: PendingStructId) -> StructType { + assert_eq!(id.builder_id, self.builder_id, "label from another builder"); + StructType::from_registered_type(self.types[id.index as usize].clone()) + } + + /// Get the array type for the given label. + /// + /// # Panics + /// + /// Panics if the label did not come from the [`RecGroupBuilder`] that + /// produced this rec group. + pub fn array(&self, id: PendingArrayId) -> ArrayType { + assert_eq!(id.builder_id, self.builder_id, "label from another builder"); + ArrayType::from_registered_type(self.types[id.index as usize].clone()) + } + + /// Get the function type for the given label. + /// + /// # Panics + /// + /// Panics if the label did not come from the [`RecGroupBuilder`] that + /// produced this rec group. + pub fn func(&self, id: PendingFuncId) -> FuncType { + assert_eq!(id.builder_id, self.builder_id, "label from another builder"); + FuncType::from_registered_type(self.types[id.index as usize].clone()) + } + + /// Iterate over all of the types in this rec group, in definition order. + pub fn types(&self) -> impl ExactSizeIterator + '_ { + self.types.iter().map(|rt| { + let rt = rt.clone(); + if rt.is_struct() { + CompositeType::Struct(StructType::from_registered_type(rt)) + } else if rt.is_array() { + CompositeType::Array(ArrayType::from_registered_type(rt)) + } else { + debug_assert!(rt.is_func()); + CompositeType::Func(FuncType::from_registered_type(rt)) + } + }) + } + + /// Get the registered struct type at the given member index, for validation. + fn struct_at(&self, index: usize) -> StructType { + StructType::from_registered_type(self.types[index].clone()) + } + fn array_at(&self, index: usize) -> ArrayType { + ArrayType::from_registered_type(self.types[index].clone()) + } + fn func_at(&self, index: usize) -> FuncType { + FuncType::from_registered_type(self.types[index].clone()) + } + + /// Validate that the member at `index` structurally matches its declared + /// supertype, now that all sibling references are registered and resolvable. + fn validate_supertype(&self, index: usize, def: &MemberDef) -> Result<()> { + match def { + MemberDef::Struct { + supertype: Some(supertype), + .. + } => { + let sub = self.struct_at(index); + let sup = match supertype { + StructSuperType::Local(id) => self.struct_at(id.index as usize), + StructSuperType::Type(ty) => ty.clone(), + }; + ensure!( + struct_fields_match(&sub, &sup), + "struct type {index} does not match its supertype: \ + found {sub}, expected supertype {sup}", + ); + } + MemberDef::Array { + supertype: Some(supertype), + .. + } => { + let sub = self.array_at(index); + let sup = match supertype { + ArraySuperType::Local(id) => self.array_at(id.index as usize), + ArraySuperType::Type(ty) => ty.clone(), + }; + ensure!( + sub.field_type().matches(&sup.field_type()), + "array type {index} does not match its supertype: \ + found {sub}, expected supertype {sup}", + ); + } + MemberDef::Func { + supertype: Some(supertype), + .. + } => { + let sub = self.func_at(index); + let sup = match supertype { + FuncSuperType::Local(id) => self.func_at(id.index as usize), + FuncSuperType::Type(ty) => ty.clone(), + }; + // `FuncType::matches` performs structural (not nominal) matching + // for distinct types, which is exactly the subtype check. + ensure!( + sub.matches(&sup), + "function type {index} does not match its supertype: \ + found {sub}, expected supertype {sup}", + ); + } + // No supertype: nothing to validate. + MemberDef::Struct { .. } | MemberDef::Array { .. } | MemberDef::Func { .. } => {} + } + Ok(()) + } +} + +/// Does struct `sub` structurally match (i.e. subtype) struct `sup`? +/// +/// Mirrors `StructType::fields_match`: `sub` must have at least as many fields +/// as `sup`, and each of `sup`'s fields must be matched by `sub`'s. +fn struct_fields_match(sub: &StructType, sup: &StructType) -> bool { + sub.fields().len() >= sup.fields().len() + && sub.fields().zip(sup.fields()).all(|(a, b)| a.matches(&b)) +} diff --git a/crates/wasmtime/src/runtime/type_registry.rs b/crates/wasmtime/src/runtime/type_registry.rs index da4e76cdb2f5..bbaa19585639 100644 --- a/crates/wasmtime/src/runtime/type_registry.rs +++ b/crates/wasmtime/src/runtime/type_registry.rs @@ -189,6 +189,79 @@ impl Engine { trampolines, }) } + + /// Register a single, embedder-defined recursion group in this engine's + /// registry, returning one [`RegisteredType`] per member. + /// + /// The given `types` are the members of one rec group, in order. References + /// *within* the group (i.e. one member referring to another) must use + /// [`EngineOrModuleTypeIndex::Module`] whose value is the 0-based index of + /// the referenced member within `types`. References to types *outside* of + /// this rec group must already be registered in this engine and use + /// [`EngineOrModuleTypeIndex::Engine`]. + /// + /// If an identical rec group has already been registered, it is reused (the + /// returned types will share its `VMSharedTypeIndex`es). + /// + /// The returned `RegisteredType`s keep the whole rec group registered for as + /// long as any of them is alive. + pub(crate) fn register_rec_group_types( + &self, + types: impl ExactSizeIterator, + ) -> Result, OutOfMemory> { + let len = types.len(); + assert!(len >= 1, "a rec group must contain at least one type"); + + let engine = self.clone(); + let gc_runtime = engine.gc_runtime().map(|rt| &**rt); + + // Gather the parts for each member while holding the registry lock, but + // construct the `RegisteredType`s only *after* releasing it: + // `RegisteredType::from_parts` itself takes a read lock (via + // `debug_assert_contains`), so building them inside this scope would + // deadlock. This mirrors `RegisteredType::new`. + let parts = { + let registry = engine.signatures(); + let mut inner = registry.0.write(); + + // Members reference each other using 0-based module-type indices, so + // the rec group occupies `0..len`. There are no module-type + // references below `range.start`, so the inter-group canonicalization + // map is never consulted and an empty map suffices. + let map = TryPrimaryMap::::new(); + let range = ModuleInternedTypeIndex::from_bits(0) + ..ModuleInternedTypeIndex::from_bits(u32::try_from(len).unwrap()); + + let entry = inner.register_rec_group(gc_runtime, &map, range, types.map(Ok))?; + + // `register_rec_group` incremented the registration count once on our + // behalf. We are about to hand out one `RegisteredType` per member, + // each of which owns a registration and decrements it on drop, so + // account for the remaining `len - 1`. + for _ in 1..len { + entry.incref("Engine::register_rec_group_types"); + } + + entry + .0 + .shared_type_indices + .iter() + .map(|&index| { + let id = shared_type_index_to_slab_id(index); + let ty = inner.types[id].clone().unwrap(); + let layout = inner.type_to_gc_layout.get(index).and_then(|l| l.clone()); + (entry.clone(), index, ty, layout) + }) + .collect::>() + }; + + Ok(parts + .into_iter() + .map(|(entry, index, ty, layout)| { + RegisteredType::from_parts(engine.clone(), entry, index, ty, layout) + }) + .collect()) + } } impl TypeCollection { diff --git a/tests/all/types.rs b/tests/all/types.rs index 2949e6233a92..7ffb731ed276 100644 --- a/tests/all/types.rs +++ b/tests/all/types.rs @@ -707,3 +707,363 @@ fn heap_type_matches_noexn() -> Result<()> { Ok(()) } + +/// Extract the concrete struct type referenced by a `(ref ... )` field. +fn field_concrete_struct(field: &FieldType) -> StructType { + match field.element_type() { + StorageType::ValType(ValType::Ref(r)) => match r.heap_type() { + HeapType::ConcreteStruct(s) => s.clone(), + other => panic!("expected concrete struct heap type, got {other:?}"), + }, + other => panic!("expected a ref-typed field, got {other:?}"), + } +} + +#[test] +fn rec_group_self_reference() -> Result<()> { + let engine = Engine::default(); + + let mut builder = RecGroupBuilder::new(&engine); + let node = builder.declare_struct(); + builder.define_struct(node, [FieldTemplate::ref_(Mutability::Var, true, node)]); + let group = builder.build()?; + + assert_eq!(group.len(), 1); + let node = group.struct_(node); + assert_eq!(node.fields().len(), 1); + + let field = node.field(0).unwrap(); + let referenced = field_concrete_struct(&field); + assert!(StructType::eq(&referenced, &node)); + Ok(()) +} + +#[test] +fn rec_group_mutual_recursion() -> Result<()> { + let engine = Engine::default(); + + let mut builder = RecGroupBuilder::new(&engine); + let s1 = builder.declare_struct(); + let s2 = builder.declare_struct(); + builder.define_struct(s1, [FieldTemplate::ref_(Mutability::Var, true, s2)]); + builder.define_struct(s2, [FieldTemplate::ref_(Mutability::Const, false, s1)]); + let group = builder.build()?; + + assert_eq!(group.len(), 2); + let s1 = group.struct_(s1); + let s2 = group.struct_(s2); + + // s1.field0 -> (ref null s2) + let f1 = s1.field(0).unwrap(); + match f1.element_type() { + StorageType::ValType(ValType::Ref(r)) => assert!(r.is_nullable()), + other => panic!("unexpected {other:?}"), + } + assert!(StructType::eq(&field_concrete_struct(&f1), &s2)); + + // s2.field0 -> (ref s1), non-nullable + let f2 = s2.field(0).unwrap(); + match f2.element_type() { + StorageType::ValType(ValType::Ref(r)) => assert!(!r.is_nullable()), + other => panic!("unexpected {other:?}"), + } + assert!(StructType::eq(&field_concrete_struct(&f2), &s1)); + Ok(()) +} + +#[test] +fn rec_group_mixed_concrete_and_local() -> Result<()> { + let engine = Engine::default(); + + // An already-registered, one-off struct type. + let existing = StructType::new( + &engine, + [FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )], + )?; + + let mut builder = RecGroupBuilder::new(&engine); + let node = builder.declare_struct(); + builder.define_struct( + node, + [ + // forward ref to the sibling + FieldTemplate::ref_(Mutability::Var, true, node), + // ref to an already-registered type + FieldTemplate::ref_(Mutability::Const, false, existing.clone()), + // a plain scalar + FieldTemplate::new(Mutability::Var, StorageType::ValType(ValType::I64)), + ], + ); + let group = builder.build()?; + let node = group.struct_(node); + + assert_eq!(node.fields().len(), 3); + assert!(StructType::eq( + &field_concrete_struct(&node.field(0).unwrap()), + &node + )); + assert!(StructType::eq( + &field_concrete_struct(&node.field(1).unwrap()), + &existing + )); + assert!(node.field(2).unwrap().element_type().is_val_type()); + Ok(()) +} + +#[test] +fn rec_group_dedup() -> Result<()> { + let engine = Engine::default(); + + let build = || -> Result { + let mut builder = RecGroupBuilder::new(&engine); + let s1 = builder.declare_struct(); + let s2 = builder.declare_struct(); + builder.define_struct(s1, [FieldTemplate::ref_(Mutability::Var, true, s2)]); + builder.define_struct(s2, [FieldTemplate::ref_(Mutability::Const, false, s1)]); + let group = builder.build()?; + Ok(group.struct_(s1)) + }; + + let a = build()?; + let b = build()?; + // Two identical rec groups are hash-consed to the same registered types. + assert!(StructType::eq(&a, &b)); + Ok(()) +} + +#[test] +fn rec_group_subtyping_via_sibling() -> Result<()> { + let engine = Engine::default(); + + let mut builder = RecGroupBuilder::new(&engine); + let base = builder.declare_struct(); + let derived = builder.declare_struct(); + builder.define_struct_with_finality_and_supertype( + base, + Finality::NonFinal, + None::, + [FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )], + ); + builder.define_struct_with_finality_and_supertype( + derived, + Finality::Final, + Some(base), + // width subtype: keeps base's field, adds another + [ + FieldType::new(Mutability::Const, StorageType::ValType(ValType::I32)), + FieldType::new(Mutability::Const, StorageType::ValType(ValType::I64)), + ], + ); + let group = builder.build()?; + + let base = group.struct_(base); + let derived = group.struct_(derived); + assert!(derived.matches(&base)); + assert_eq!( + derived.supertype().map(|s| StructType::eq(&s, &base)), + Some(true) + ); + Ok(()) +} + +#[test] +fn rec_group_subtyping_via_concrete() -> Result<()> { + let engine = Engine::default(); + + let base = StructType::with_finality_and_supertype( + &engine, + Finality::NonFinal, + None, + [FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )], + )?; + + let mut builder = RecGroupBuilder::new(&engine); + let derived = builder.declare_struct(); + builder.define_struct_with_finality_and_supertype( + derived, + Finality::Final, + Some(base.clone()), + [FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )], + ); + let group = builder.build()?; + + assert!(group.struct_(derived).matches(&base)); + Ok(()) +} + +#[test] +fn rec_group_array_and_func_recursive() -> Result<()> { + let engine = Engine::default(); + + let mut builder = RecGroupBuilder::new(&engine); + let node = builder.declare_struct(); + let arr = builder.declare_array(); + let func = builder.declare_func(); + // struct referencing the array and func siblings + builder.define_struct( + node, + [ + FieldTemplate::ref_(Mutability::Var, true, arr), + FieldTemplate::ref_(Mutability::Var, true, func), + ], + ); + // array of (ref null node) + builder.define_array(arr, FieldTemplate::ref_(Mutability::Var, true, node)); + // func: (param (ref null node)) -> (ref null node) + builder.define_func( + func, + [ValTypeTemplate::ref_(true, node)], + [ValTypeTemplate::ref_(true, node)], + ); + let group = builder.build()?; + + assert_eq!(group.len(), 3); + let arr = group.array(arr); + let func = group.func(func); + assert_eq!(func.params().len(), 1); + assert_eq!(func.results().len(), 1); + // array element is a ref + assert!(arr.field_type().element_type().is_val_type()); + Ok(()) +} + +#[test] +fn rec_group_declared_but_undefined_errors() { + let engine = Engine::default(); + let mut builder = RecGroupBuilder::new(&engine); + let _s = builder.declare_struct(); + let err = builder.build().unwrap_err(); + assert!( + err.to_string().contains("declared but never defined"), + "unexpected error: {err}" + ); +} + +#[test] +fn rec_group_empty_errors() { + let engine = Engine::default(); + let builder = RecGroupBuilder::new(&engine); + assert!(builder.build().is_err()); +} + +#[test] +fn rec_group_final_supertype_errors() { + let engine = Engine::default(); + let mut builder = RecGroupBuilder::new(&engine); + let base = builder.declare_struct(); + let derived = builder.declare_struct(); + // base is final (default) + builder.define_struct( + base, + [FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )], + ); + builder.define_struct_with_finality_and_supertype( + derived, + Finality::Final, + Some(base), + [FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )], + ); + let err = builder.build().unwrap_err(); + assert!( + err.to_string().contains("final supertype"), + "unexpected error: {err}" + ); +} + +#[test] +fn rec_group_supertype_mismatch_errors() { + let engine = Engine::default(); + let mut builder = RecGroupBuilder::new(&engine); + let base = builder.declare_struct(); + let derived = builder.declare_struct(); + builder.define_struct_with_finality_and_supertype( + base, + Finality::NonFinal, + None::, + [FieldType::new( + Mutability::Var, + StorageType::ValType(ValType::I32), + )], + ); + // mutable fields must be exactly equal; i64 != i32 -> mismatch + builder.define_struct_with_finality_and_supertype( + derived, + Finality::Final, + Some(base), + [FieldType::new( + Mutability::Var, + StorageType::ValType(ValType::I64), + )], + ); + let err = builder.build().unwrap_err(); + assert!( + err.to_string().contains("does not match its supertype"), + "unexpected error: {err}" + ); +} + +#[test] +fn rec_group_nonfinal_root_without_supertype() -> Result<()> { + let engine = Engine::default(); + let mut builder = RecGroupBuilder::new(&engine); + let root = builder.declare_struct(); + // A non-final root with no supertype requires the turbofish on `None`. + builder.define_struct_with_finality_and_supertype( + root, + Finality::NonFinal, + None::, + [FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )], + ); + let group = builder.build()?; + assert_eq!(group.struct_(root).finality(), Finality::NonFinal); + Ok(()) +} + +#[test] +fn rec_group_types_iter() -> Result<()> { + let engine = Engine::default(); + let mut builder = RecGroupBuilder::new(&engine); + let s = builder.declare_struct(); + let a = builder.declare_array(); + builder.define_struct( + s, + [FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )], + ); + builder.define_array(a, FieldType::new(Mutability::Var, StorageType::I8)); + let group = builder.build()?; + + let kinds: Vec<_> = group + .types() + .map(|t| match t { + CompositeType::Struct(_) => "struct", + CompositeType::Array(_) => "array", + CompositeType::Func(_) => "func", + }) + .collect(); + assert_eq!(kinds, ["struct", "array"]); + Ok(()) +} From 8f2617853a1c8aa77ec46e1305f9d16ea01ed4b1 Mon Sep 17 00:00:00 2001 From: Doron Somech Date: Thu, 25 Jun 2026 12:52:57 +0300 Subject: [PATCH 3/5] Rework RecGroupBuilder API per review feedback Replace the public surface with a fluent nested-builder API: - Use a single kind-agnostic `PendingType` handle instead of separate `PendingStructId`/`PendingArrayId`/`PendingFuncId`. - Drop the public `*Template` value types. Concrete fields/params take the existing `FieldType`/`ValType` directly; forward references to in-group siblings use `forward_ref_field`/`forward_ref_element`/`forward_ref_param`/ `forward_ref_result`, which take plain `(mutability, is_nullable, target)` arguments (no sub-builders, no slice allocation). - Finality and supertypes are set via builder methods: `finality(Finality)`, `supertype()` for already-registered supertypes, and `forward_supertype(PendingType)` for in-group siblings. `build()` performs the same structural/finality validation the one-off constructors do, after registration so that forward supertypes resolve. Getters are `get_struct`/`get_array`/`get_func -> Option<..>`. --- crates/wasmtime/src/runtime/rec_group.rs | 1430 ++++++++++------------ tests/all/types.rs | 408 +++--- 2 files changed, 873 insertions(+), 965 deletions(-) diff --git a/crates/wasmtime/src/runtime/rec_group.rs b/crates/wasmtime/src/runtime/rec_group.rs index 70e2dee3c2fe..fbc0aa852ee8 100644 --- a/crates/wasmtime/src/runtime/rec_group.rs +++ b/crates/wasmtime/src/runtime/rec_group.rs @@ -4,10 +4,13 @@ //! `FuncType::new` cannot describe types that reference themselves or each //! other, because the constructors require all referenced types to already //! exist. [`RecGroupBuilder`] lifts that restriction: you *declare* a type to -//! get a kind-typed label (a [`PendingStructId`], [`PendingArrayId`], or -//! [`PendingFuncId`]), use that label as a forward reference while defining -//! other types, and then *define* it later. The whole group is validated and -//! registered together when [`RecGroupBuilder::build`] is called. +//! get a [`PendingType`] handle, use that handle as a forward reference while +//! defining other types, and the whole group is validated and registered +//! together when [`RecGroupBuilder::build`] is called. +//! +//! Already-registered types (and abstract heap types) are used directly via the +//! normal [`FieldType`]/[`ValType`] APIs; the `forward_ref_*` builder methods +//! are only for references to other types being defined in the same group. //! //! ``` //! # use wasmtime::*; @@ -16,25 +19,23 @@ //! //! // Two mutually-recursive struct types. //! let mut builder = RecGroupBuilder::new(&engine); -//! let s1 = builder.declare_struct(); -//! let s2 = builder.declare_struct(); -//! builder.define_struct(s1, [FieldTemplate::ref_(Mutability::Var, true, s2)]); -//! builder.define_struct(s2, [FieldTemplate::ref_(Mutability::Const, false, s1)]); +//! let a = builder.declare(); +//! let b = builder.declare(); +//! // forward_ref_field(mutability, is_nullable, target) +//! builder.define_struct(a).forward_ref_field(Mutability::Const, true, b); +//! builder.define_struct(b).forward_ref_field(Mutability::Const, false, a); //! let group = builder.build()?; //! -//! let s1: StructType = group.struct_(s1); -//! let s2: StructType = group.struct_(s2); -//! assert!(s1.field(0).unwrap().element_type().is_val_type()); +//! let a: StructType = group.get_struct(a).unwrap(); +//! let b: StructType = group.get_struct(b).unwrap(); +//! assert!(a.field(0).unwrap().element_type().is_val_type()); //! # Ok(()) //! # } //! ``` use crate::prelude::*; use crate::type_registry::RegisteredType; -use crate::{ - ArrayType, Engine, FieldType, Finality, FuncType, HeapType, Mutability, StorageType, - StructType, ValType, -}; +use crate::{ArrayType, Engine, FieldType, Finality, FuncType, Mutability, StructType, ValType}; use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; use wasmtime_environ::{ EngineOrModuleTypeIndex, EntityRef, ModuleInternedTypeIndex, WasmArrayType, @@ -46,274 +47,32 @@ use wasmtime_environ::{ const MAX_FIELDS: usize = 10_000; /// A process-global counter used to give each [`RecGroupBuilder`] a distinct id -/// so that labels from one builder cannot be accidentally used with another. +/// so that handles from one builder cannot be accidentally used with another. static NEXT_BUILDER_ID: AtomicUsize = AtomicUsize::new(0); fn next_builder_id() -> usize { NEXT_BUILDER_ID.fetch_add(1, Relaxed) } -/// The 0-based index of a member within the rec group being built, as a -/// module-level type reference (the form `register_rec_group` expects for -/// intra-group references). +/// The 0-based index of a member within the rec group being built, expressed as +/// the module-level type reference that `register_rec_group` expects for +/// intra-group references. fn module_index(index: u32) -> EngineOrModuleTypeIndex { EngineOrModuleTypeIndex::Module(ModuleInternedTypeIndex::new(index as usize)) } -/// A kind-typed label for a struct type being defined in a [`RecGroupBuilder`]. +/// A handle to a type being defined in a [`RecGroupBuilder`]. /// -/// Obtained from [`RecGroupBuilder::declare_struct`] and used both to define the -/// type and to forward-reference it from other types in the same group. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct PendingStructId { - builder_id: usize, - index: u32, -} - -/// A kind-typed label for an array type being defined in a [`RecGroupBuilder`]. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct PendingArrayId { - builder_id: usize, - index: u32, -} - -/// A kind-typed label for a function type being defined in a [`RecGroupBuilder`]. +/// Obtained from [`RecGroupBuilder::declare`]. It is used both to define the +/// type (via [`RecGroupBuilder::define_struct`] and friends) and to +/// forward-reference it from other types in the same group (via the +/// `forward_ref_*` builder methods). #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct PendingFuncId { +pub struct PendingType { builder_id: usize, index: u32, } -/// A heap type usable while building a recursion group. -/// -/// This mirrors [`HeapType`], with one extra variant per kind for forward -/// references to sibling types being defined in the same [`RecGroupBuilder`]. -/// -/// Already-known heap types (abstract heap types like `any`, or -/// already-registered concrete types) convert into this type via `From`/`Into`, -/// so the common case requires no ceremony. -#[derive(Clone, Debug)] -pub enum HeapTypeTemplate { - /// An abstract heap type or an already-registered concrete heap type. - Type(HeapType), - /// A forward reference to a struct defined in the same rec group. - LocalStruct(PendingStructId), - /// A forward reference to an array defined in the same rec group. - LocalArray(PendingArrayId), - /// A forward reference to a function defined in the same rec group. - LocalFunc(PendingFuncId), -} - -impl From for HeapTypeTemplate { - fn from(ty: HeapType) -> Self { - HeapTypeTemplate::Type(ty) - } -} -impl From for HeapTypeTemplate { - fn from(ty: StructType) -> Self { - HeapTypeTemplate::Type(HeapType::ConcreteStruct(ty)) - } -} -impl From for HeapTypeTemplate { - fn from(ty: ArrayType) -> Self { - HeapTypeTemplate::Type(HeapType::ConcreteArray(ty)) - } -} -impl From for HeapTypeTemplate { - fn from(ty: FuncType) -> Self { - HeapTypeTemplate::Type(HeapType::ConcreteFunc(ty)) - } -} -impl From for HeapTypeTemplate { - fn from(id: PendingStructId) -> Self { - HeapTypeTemplate::LocalStruct(id) - } -} -impl From for HeapTypeTemplate { - fn from(id: PendingArrayId) -> Self { - HeapTypeTemplate::LocalArray(id) - } -} -impl From for HeapTypeTemplate { - fn from(id: PendingFuncId) -> Self { - HeapTypeTemplate::LocalFunc(id) - } -} - -/// A value type usable while building a recursion group. -/// -/// Mirrors [`ValType`]; the only thing it can express that a [`ValType`] cannot -/// is a `ref` whose target is a sibling type being defined in the same group. -#[derive(Clone, Debug)] -pub enum ValTypeTemplate { - /// A scalar value type or an already-known concrete reference type. - Type(ValType), - /// A `ref` (nullable or not) whose target may be a forward reference. - Ref { - /// Whether the reference is nullable. - nullable: bool, - /// The referenced heap type. - heap: HeapTypeTemplate, - }, -} - -impl ValTypeTemplate { - /// Construct a `ref` value type whose target may be a sibling type in the - /// same rec group. - pub fn ref_(nullable: bool, heap: impl Into) -> Self { - ValTypeTemplate::Ref { - nullable, - heap: heap.into(), - } - } -} - -impl From for ValTypeTemplate { - fn from(ty: ValType) -> Self { - ValTypeTemplate::Type(ty) - } -} - -/// The storage type of a struct field or array element while building a -/// recursion group. -/// -/// Mirrors [`StorageType`]; the only thing it can express that a [`StorageType`] -/// cannot is a `ref` whose target is a sibling type being defined in the same -/// group. -#[derive(Clone, Debug)] -pub enum StorageTypeTemplate { - /// A packed integer storage type or an already-known value type. - Type(StorageType), - /// A `ref` (nullable or not) whose target may be a forward reference. - Ref { - /// Whether the reference is nullable. - nullable: bool, - /// The referenced heap type. - heap: HeapTypeTemplate, - }, -} - -impl From for StorageTypeTemplate { - fn from(ty: StorageType) -> Self { - StorageTypeTemplate::Type(ty) - } -} -impl From for StorageTypeTemplate { - fn from(ty: ValType) -> Self { - StorageTypeTemplate::Type(StorageType::ValType(ty)) - } -} - -/// A struct field or array element type while building a recursion group. -/// -/// Mirrors [`FieldType`]. A plain [`FieldType`] (with no forward references) -/// converts into this type via `From`/`Into`. -#[derive(Clone, Debug)] -pub struct FieldTemplate { - mutability: Mutability, - element: StorageTypeTemplate, -} - -impl FieldTemplate { - /// Construct a field template from a mutability and element storage type. - pub fn new(mutability: Mutability, element: impl Into) -> Self { - FieldTemplate { - mutability, - element: element.into(), - } - } - - /// Construct a field template whose element is a `ref` that may forward- - /// reference a sibling type in the same rec group. - pub fn ref_(mutability: Mutability, nullable: bool, heap: impl Into) -> Self { - FieldTemplate { - mutability, - element: StorageTypeTemplate::Ref { - nullable, - heap: heap.into(), - }, - } - } -} - -impl From for FieldTemplate { - fn from(ty: FieldType) -> Self { - FieldTemplate { - mutability: ty.mutability(), - element: StorageTypeTemplate::Type(ty.element_type().clone()), - } - } -} - -/// The supertype of a struct type being defined in a [`RecGroupBuilder`]. -/// -/// May be either a sibling label ([`PendingStructId`]) or an already-registered -/// [`StructType`]; both convert into this type via `From`/`Into`. -#[derive(Clone, Debug)] -pub enum StructSuperType { - /// A supertype defined as a sibling in the same rec group. - Local(PendingStructId), - /// An already-registered supertype. - Type(StructType), -} - -impl From for StructSuperType { - fn from(id: PendingStructId) -> Self { - StructSuperType::Local(id) - } -} -impl From for StructSuperType { - fn from(ty: StructType) -> Self { - StructSuperType::Type(ty) - } -} - -/// The supertype of an array type being defined in a [`RecGroupBuilder`]. -/// -/// May be either a sibling label ([`PendingArrayId`]) or an already-registered -/// [`ArrayType`]; both convert into this type via `From`/`Into`. -#[derive(Clone, Debug)] -pub enum ArraySuperType { - /// A supertype defined as a sibling in the same rec group. - Local(PendingArrayId), - /// An already-registered supertype. - Type(ArrayType), -} - -impl From for ArraySuperType { - fn from(id: PendingArrayId) -> Self { - ArraySuperType::Local(id) - } -} -impl From for ArraySuperType { - fn from(ty: ArrayType) -> Self { - ArraySuperType::Type(ty) - } -} - -/// The supertype of a function type being defined in a [`RecGroupBuilder`]. -/// -/// May be either a sibling label ([`PendingFuncId`]) or an already-registered -/// [`FuncType`]; both convert into this type via `From`/`Into`. -#[derive(Clone, Debug)] -pub enum FuncSuperType { - /// A supertype defined as a sibling in the same rec group. - Local(PendingFuncId), - /// An already-registered supertype. - Type(FuncType), -} - -impl From for FuncSuperType { - fn from(id: PendingFuncId) -> Self { - FuncSuperType::Local(id) - } -} -impl From for FuncSuperType { - fn from(ty: FuncType) -> Self { - FuncSuperType::Type(ty) - } -} - /// One of the concrete composite types in a registered [`RecGroup`]. #[derive(Clone, Debug)] pub enum CompositeType { @@ -325,23 +84,48 @@ pub enum CompositeType { Func(FuncType), } +/// A struct field or array element being defined: either an already-known +/// concrete/abstract type, or a forward reference to a sibling in this group. +enum FieldDef { + Concrete(FieldType), + Forward { + target: u32, + nullable: bool, + mutable: bool, + }, +} + +/// A function parameter or result being defined: either an already-known +/// concrete/abstract type, or a forward reference to a sibling in this group. +enum ValDef { + Concrete(ValType), + Forward { target: u32, nullable: bool }, +} + +/// A supertype being defined: either a sibling in this group or an +/// already-registered concrete type. Generic over the concrete kind. +enum SuperDef { + Forward(u32), + Known(T), +} + /// The in-progress definition of one member of a rec group. enum MemberDef { Struct { finality: Finality, - supertype: Option, - fields: Vec, + supertype: Option>, + fields: Vec, }, Array { finality: Finality, - supertype: Option, - field: FieldTemplate, + supertype: Option>, + element: Option, }, Func { finality: Finality, - supertype: Option, - params: Vec, - results: Vec, + supertype: Option>, + params: Vec, + results: Vec, }, } @@ -366,487 +150,394 @@ impl RecGroupBuilder { } } - fn declare(&mut self) -> u32 { + /// Declare a new type in this rec group, returning a handle that can be used + /// as a forward reference before the type is defined. + pub fn declare(&mut self) -> PendingType { let index = u32::try_from(self.members.len()).expect("too many types in a rec group"); self.members.push(None); - index - } - - /// Declare a struct type, returning a label that can be used as a forward - /// reference before the type is defined via [`define_struct`][Self::define_struct]. - pub fn declare_struct(&mut self) -> PendingStructId { - PendingStructId { - builder_id: self.builder_id, - index: self.declare(), - } - } - - /// Declare an array type, returning a label that can be used as a forward - /// reference before the type is defined via [`define_array`][Self::define_array]. - pub fn declare_array(&mut self) -> PendingArrayId { - PendingArrayId { + PendingType { builder_id: self.builder_id, - index: self.declare(), + index, } } - /// Declare a function type, returning a label that can be used as a forward - /// reference before the type is defined via [`define_func`][Self::define_func]. - pub fn declare_func(&mut self) -> PendingFuncId { - PendingFuncId { - builder_id: self.builder_id, - index: self.declare(), - } - } - - #[track_caller] - fn check_owns_struct(&self, id: PendingStructId) { - assert_eq!( - id.builder_id, self.builder_id, - "`PendingStructId` used with a different `RecGroupBuilder` than it came from" - ); - } #[track_caller] - fn check_owns_array(&self, id: PendingArrayId) { + fn check_owns(&self, ty: PendingType) { assert_eq!( - id.builder_id, self.builder_id, - "`PendingArrayId` used with a different `RecGroupBuilder` than it came from" - ); - } - #[track_caller] - fn check_owns_func(&self, id: PendingFuncId) { - assert_eq!( - id.builder_id, self.builder_id, - "`PendingFuncId` used with a different `RecGroupBuilder` than it came from" + ty.builder_id, self.builder_id, + "`PendingType` used with a different `RecGroupBuilder` than it came from" ); } - /// Define a previously-declared struct type, with the given fields, as a - /// final type without a supertype. - pub fn define_struct( - &mut self, - id: PendingStructId, - fields: impl IntoIterator>, - ) -> &mut Self { - self.define_struct_with_finality_and_supertype( - id, - Finality::Final, - None::, - fields, - ) - } - - /// Define a previously-declared struct type with the given finality, - /// supertype, and fields. + /// Begin defining the given handle as a struct type. /// - /// The supertype may be either a sibling label ([`PendingStructId`]) or an - /// already-registered [`StructType`]. - pub fn define_struct_with_finality_and_supertype( - &mut self, - id: PendingStructId, - finality: Finality, - supertype: Option>, - fields: impl IntoIterator>, - ) -> &mut Self { - self.check_owns_struct(id); - self.members[id.index as usize] = Some(MemberDef::Struct { - finality, - supertype: supertype.map(Into::into), - fields: fields.into_iter().map(Into::into).collect(), + /// Any previous definition of this handle is discarded. + #[track_caller] + pub fn define_struct(&mut self, ty: PendingType) -> StructTypeBuilder<'_> { + self.check_owns(ty); + self.members[ty.index as usize] = Some(MemberDef::Struct { + finality: Finality::Final, + supertype: None, + fields: Vec::new(), }); - self - } - - /// Declare and define a final struct type with no supertype, returning its - /// label. - pub fn add_struct( - &mut self, - fields: impl IntoIterator>, - ) -> PendingStructId { - let id = self.declare_struct(); - self.define_struct(id, fields); - id - } - - /// Define a previously-declared array type, with the given element type, as - /// a final type without a supertype. - pub fn define_array( - &mut self, - id: PendingArrayId, - field: impl Into, - ) -> &mut Self { - self.define_array_with_finality_and_supertype( - id, - Finality::Final, - None::, - field, - ) + StructTypeBuilder { + rec: self, + index: ty.index, + } } - /// Define a previously-declared array type with the given finality, - /// supertype, and element type. - pub fn define_array_with_finality_and_supertype( - &mut self, - id: PendingArrayId, - finality: Finality, - supertype: Option>, - field: impl Into, - ) -> &mut Self { - self.check_owns_array(id); - self.members[id.index as usize] = Some(MemberDef::Array { - finality, - supertype: supertype.map(Into::into), - field: field.into(), + /// Begin defining the given handle as an array type. + /// + /// Any previous definition of this handle is discarded. + #[track_caller] + pub fn define_array(&mut self, ty: PendingType) -> ArrayTypeBuilder<'_> { + self.check_owns(ty); + self.members[ty.index as usize] = Some(MemberDef::Array { + finality: Finality::Final, + supertype: None, + element: None, }); - self - } - - /// Declare and define a final array type with no supertype, returning its - /// label. - pub fn add_array(&mut self, field: impl Into) -> PendingArrayId { - let id = self.declare_array(); - self.define_array(id, field); - id - } - - /// Define a previously-declared function type, with the given parameters and - /// results, as a final type without a supertype. - pub fn define_func( - &mut self, - id: PendingFuncId, - params: impl IntoIterator>, - results: impl IntoIterator>, - ) -> &mut Self { - self.define_func_with_finality_and_supertype( - id, - Finality::Final, - None::, - params, - results, - ) + ArrayTypeBuilder { + rec: self, + index: ty.index, + } } - /// Define a previously-declared function type with the given finality, - /// supertype, parameters, and results. - pub fn define_func_with_finality_and_supertype( - &mut self, - id: PendingFuncId, - finality: Finality, - supertype: Option>, - params: impl IntoIterator>, - results: impl IntoIterator>, - ) -> &mut Self { - self.check_owns_func(id); - self.members[id.index as usize] = Some(MemberDef::Func { - finality, - supertype: supertype.map(Into::into), - params: params.into_iter().map(Into::into).collect(), - results: results.into_iter().map(Into::into).collect(), + /// Begin defining the given handle as a function type. + /// + /// Any previous definition of this handle is discarded. + #[track_caller] + pub fn define_func(&mut self, ty: PendingType) -> FuncTypeBuilder<'_> { + self.check_owns(ty); + self.members[ty.index as usize] = Some(MemberDef::Func { + finality: Finality::Final, + supertype: None, + params: Vec::new(), + results: Vec::new(), }); - self - } - - /// Declare and define a final function type with no supertype, returning its - /// label. - pub fn add_func( - &mut self, - params: impl IntoIterator>, - results: impl IntoIterator>, - ) -> PendingFuncId { - let id = self.declare_func(); - self.define_func(id, params, results); - id + FuncTypeBuilder { + rec: self, + index: ty.index, + } } /// Finish building the rec group: validate all of its types, register them /// with the engine, and return the resulting [`RecGroup`]. /// - /// Returns an error if any declared type was never defined, if any type - /// references a type from a different engine, if a supertype is final, if a - /// type does not structurally match its declared supertype, or if a struct - /// exceeds the implementation's field-count limit. + /// Returns an error if the group is empty, if any declared type was never + /// defined, if an array's element type was never set, if any type references + /// a type from a different engine, or if a struct exceeds the + /// implementation's field-count limit. pub fn build(self) -> Result { - let engine = &self.engine; + let RecGroupBuilder { + engine, + builder_id, + members, + } = self; ensure!( - !self.members.is_empty(), + !members.is_empty(), "a rec group must contain at least one type" ); - // 1. Every declared label must have been defined. - let mut defs = Vec::with_capacity(self.members.len()); - for (i, member) in self.members.iter().enumerate() { + for (i, member) in members.iter().enumerate() { match member { - Some(def) => defs.push(def), None => bail!("type {i} was declared but never defined"), + Some(MemberDef::Array { element: None, .. }) => { + bail!("array type {i} was declared but its element type was never set") + } + Some(_) => {} } } - // 2. Lower each member to a `WasmSubType`, checking engine ownership and - // supertype finality as we go. - let mut sub_types = Vec::with_capacity(defs.len()); - for def in &defs { - sub_types.push(self.lower_member(def)?); + let mut sub_types = Vec::with_capacity(members.len()); + for member in &members { + sub_types.push(lower_member(&engine, &members, member.as_ref().unwrap())?); } - // 3. Register the whole group with the engine. let registered = engine.register_rec_group_types(sub_types.into_iter())?; - let group = RecGroup { - builder_id: self.builder_id, + builder_id, types: registered, }; - // 4. Now that everything is registered (so that sibling references are - // real, resolvable types), validate that each type structurally - // matches its declared supertype. On failure, `group` is dropped, - // which unregisters the types. - for (i, def) in defs.iter().enumerate() { - group.validate_supertype(i, def)?; + // Validate that each type structurally matches its declared supertype, + // now that forward references resolve to registered types. On failure, + // `group` is dropped, which unregisters the types. + for (i, member) in members.iter().enumerate() { + validate_supertype(&group, i, member.as_ref().unwrap())?; } Ok(group) } +} - /// Look up the declared finality of a sibling member by index. - fn member_finality(&self, index: u32) -> Finality { - match self.members[index as usize] - .as_ref() - .expect("all members defined by this point") - { - MemberDef::Struct { finality, .. } - | MemberDef::Array { finality, .. } - | MemberDef::Func { finality, .. } => *finality, +/// Builder for a struct type within a [`RecGroupBuilder`]. +/// +/// Returned by [`RecGroupBuilder::define_struct`]. +pub struct StructTypeBuilder<'a> { + rec: &'a mut RecGroupBuilder, + index: u32, +} + +impl<'a> StructTypeBuilder<'a> { + fn fields_mut(&mut self) -> &mut Vec { + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Struct { fields, .. }) => fields, + _ => unreachable!("struct builder on a non-struct member"), } } - fn lower_member(&self, def: &MemberDef) -> Result { - let (is_final, supertype, inner) = match def { - MemberDef::Struct { - finality, - supertype, - fields, - } => { - ensure!( - fields.len() <= MAX_FIELDS, - "attempted to define a struct type with {} fields, but that is more than the \ - maximum supported number of fields ({MAX_FIELDS})", - fields.len(), - ); - let supertype = self.lower_struct_supertype(supertype.as_ref())?; - let fields = fields - .iter() - .map(|f| self.lower_field(f)) - .collect::>>()?; - ( - finality.is_final(), - supertype, - WasmCompositeInnerType::Struct(WasmStructType { - fields: fields.into(), - }), - ) - } - MemberDef::Array { - finality, - supertype, - field, - } => { - let supertype = self.lower_array_supertype(supertype.as_ref())?; - let field = self.lower_field(field)?; - ( - finality.is_final(), - supertype, - WasmCompositeInnerType::Array(WasmArrayType(field)), - ) - } - MemberDef::Func { - finality, - supertype, - params, - results, - } => { - let supertype = self.lower_func_supertype(supertype.as_ref())?; - let params = params - .iter() - .map(|p| self.lower_val(p)) - .collect::>>()?; - let results = results - .iter() - .map(|r| self.lower_val(r)) - .collect::>>()?; - let func = WasmFuncType::new(params, results)?; - ( - finality.is_final(), - supertype, - WasmCompositeInnerType::Func(func), - ) - } - }; + /// Set this struct type's finality. Defaults to [`Finality::Final`]. + pub fn finality(&mut self, finality: Finality) -> &mut Self { + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Struct { finality: f, .. }) => *f = finality, + _ => unreachable!("struct builder on a non-struct member"), + } + self + } - Ok(WasmSubType { - is_final, - supertype, - composite_type: WasmCompositeType { - shared: false, - inner, - }, - }) + /// Set this struct type's supertype to an already-registered struct type. + pub fn supertype(&mut self, supertype: StructType) -> &mut Self { + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Struct { supertype: s, .. }) => *s = Some(SuperDef::Known(supertype)), + _ => unreachable!("struct builder on a non-struct member"), + } + self } - fn lower_struct_supertype( - &self, - supertype: Option<&StructSuperType>, - ) -> Result> { - Ok(match supertype { - None => None, - Some(StructSuperType::Local(id)) => { - self.check_owns_struct(*id); - ensure!( - self.member_finality(id.index).is_non_final(), - "cannot create a subtype of a final supertype" - ); - Some(module_index(id.index)) + /// Set this struct type's supertype to another struct being defined in the + /// same rec group. + #[track_caller] + pub fn forward_supertype(&mut self, supertype: PendingType) -> &mut Self { + assert_eq!( + supertype.builder_id, self.rec.builder_id, + "`PendingType` used with a different `RecGroupBuilder` than it came from" + ); + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Struct { supertype: s, .. }) => { + *s = Some(SuperDef::Forward(supertype.index)) } - Some(StructSuperType::Type(ty)) => Some(self.lower_concrete_supertype( - ty.comes_from_same_engine(&self.engine), - ty.finality(), - ty.type_index(), - )?), - }) + _ => unreachable!("struct builder on a non-struct member"), + } + self } - fn lower_array_supertype( - &self, - supertype: Option<&ArraySuperType>, - ) -> Result> { - Ok(match supertype { - None => None, - Some(ArraySuperType::Local(id)) => { - self.check_owns_array(*id); - ensure!( - self.member_finality(id.index).is_non_final(), - "cannot create a subtype of a final supertype" - ); - Some(module_index(id.index)) - } - Some(ArraySuperType::Type(ty)) => Some(self.lower_concrete_supertype( - ty.comes_from_same_engine(&self.engine), - ty.finality(), - ty.type_index(), - )?), - }) + /// Append a field whose type is already known (a scalar, an abstract ref, or + /// a reference to an already-registered type). + pub fn field(&mut self, ty: FieldType) -> &mut Self { + self.fields_mut().push(FieldDef::Concrete(ty)); + self } - fn lower_func_supertype( - &self, - supertype: Option<&FuncSuperType>, - ) -> Result> { - Ok(match supertype { - None => None, - Some(FuncSuperType::Local(id)) => { - self.check_owns_func(*id); - ensure!( - self.member_finality(id.index).is_non_final(), - "cannot create a subtype of a final supertype" - ); - Some(module_index(id.index)) - } - Some(FuncSuperType::Type(ty)) => Some(self.lower_concrete_supertype( - ty.comes_from_same_engine(&self.engine), - ty.finality(), - ty.type_index(), - )?), - }) + /// Append a field that is a reference to another type being defined in the + /// same rec group, with the given mutability and nullability. + #[track_caller] + pub fn forward_ref_field( + &mut self, + mutability: Mutability, + is_nullable: bool, + ty: PendingType, + ) -> &mut Self { + assert_eq!( + ty.builder_id, self.rec.builder_id, + "`PendingType` used with a different `RecGroupBuilder` than it came from" + ); + self.fields_mut().push(FieldDef::Forward { + target: ty.index, + nullable: is_nullable, + mutable: mutability.is_var(), + }); + self } +} - fn lower_concrete_supertype( - &self, - same_engine: bool, - finality: Finality, - index: wasmtime_environ::VMSharedTypeIndex, - ) -> Result { - ensure!( - same_engine, - "supertype is associated with a different engine" +/// Builder for an array type within a [`RecGroupBuilder`]. +/// +/// Returned by [`RecGroupBuilder::define_array`]. An array has exactly one +/// element type, which must be set via [`element`][Self::element] or +/// [`forward_ref_element`][Self::forward_ref_element]. +pub struct ArrayTypeBuilder<'a> { + rec: &'a mut RecGroupBuilder, + index: u32, +} + +impl<'a> ArrayTypeBuilder<'a> { + fn set_element(&mut self, def: FieldDef) { + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Array { element, .. }) => *element = Some(def), + _ => unreachable!("array builder on a non-array member"), + } + } + + /// Set this array type's finality. Defaults to [`Finality::Final`]. + pub fn finality(&mut self, finality: Finality) -> &mut Self { + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Array { finality: f, .. }) => *f = finality, + _ => unreachable!("array builder on a non-array member"), + } + self + } + + /// Set this array type's supertype to an already-registered array type. + pub fn supertype(&mut self, supertype: ArrayType) -> &mut Self { + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Array { supertype: s, .. }) => *s = Some(SuperDef::Known(supertype)), + _ => unreachable!("array builder on a non-array member"), + } + self + } + + /// Set this array type's supertype to another array being defined in the + /// same rec group. + #[track_caller] + pub fn forward_supertype(&mut self, supertype: PendingType) -> &mut Self { + assert_eq!( + supertype.builder_id, self.rec.builder_id, + "`PendingType` used with a different `RecGroupBuilder` than it came from" ); - ensure!( - finality.is_non_final(), - "cannot create a subtype of a final supertype" + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Array { supertype: s, .. }) => { + *s = Some(SuperDef::Forward(supertype.index)) + } + _ => unreachable!("array builder on a non-array member"), + } + self + } + + /// Set the array's element type to an already-known type. + pub fn element(&mut self, ty: FieldType) -> &mut Self { + self.set_element(FieldDef::Concrete(ty)); + self + } + + /// Set the array's element type to a reference to another type being defined + /// in the same rec group, with the given mutability and nullability. + #[track_caller] + pub fn forward_ref_element( + &mut self, + mutability: Mutability, + is_nullable: bool, + ty: PendingType, + ) -> &mut Self { + assert_eq!( + ty.builder_id, self.rec.builder_id, + "`PendingType` used with a different `RecGroupBuilder` than it came from" ); - Ok(EngineOrModuleTypeIndex::Engine(index)) + self.set_element(FieldDef::Forward { + target: ty.index, + nullable: is_nullable, + mutable: mutability.is_var(), + }); + self } +} - fn lower_field(&self, field: &FieldTemplate) -> Result { - Ok(WasmFieldType { - element_type: self.lower_storage(&field.element)?, - mutable: field.mutability.is_var(), - }) +/// Builder for a function type within a [`RecGroupBuilder`]. +/// +/// Returned by [`RecGroupBuilder::define_func`]. +pub struct FuncTypeBuilder<'a> { + rec: &'a mut RecGroupBuilder, + index: u32, +} + +impl<'a> FuncTypeBuilder<'a> { + fn params_mut(&mut self) -> &mut Vec { + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Func { params, .. }) => params, + _ => unreachable!("func builder on a non-func member"), + } } - fn lower_storage(&self, storage: &StorageTypeTemplate) -> Result { - Ok(match storage { - StorageTypeTemplate::Type(ty) => { - ensure!( - ty.comes_from_same_engine(&self.engine), - "type is associated with a different engine" - ); - ty.to_wasm_storage_type() - } - StorageTypeTemplate::Ref { nullable, heap } => { - WasmStorageType::Val(WasmValType::Ref(WasmRefType { - nullable: *nullable, - heap_type: self.lower_heap(heap)?, - })) - } - }) + fn results_mut(&mut self) -> &mut Vec { + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Func { results, .. }) => results, + _ => unreachable!("func builder on a non-func member"), + } } - fn lower_val(&self, val: &ValTypeTemplate) -> Result { - Ok(match val { - ValTypeTemplate::Type(ty) => { - ensure!( - ty.comes_from_same_engine(&self.engine), - "type is associated with a different engine" - ); - ty.to_wasm_type() - } - ValTypeTemplate::Ref { nullable, heap } => WasmValType::Ref(WasmRefType { - nullable: *nullable, - heap_type: self.lower_heap(heap)?, - }), - }) + /// Set this function type's finality. Defaults to [`Finality::Final`]. + pub fn finality(&mut self, finality: Finality) -> &mut Self { + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Func { finality: f, .. }) => *f = finality, + _ => unreachable!("func builder on a non-func member"), + } + self } - fn lower_heap(&self, heap: &HeapTypeTemplate) -> Result { - Ok(match heap { - HeapTypeTemplate::Type(ty) => { - ensure!( - ty.comes_from_same_engine(&self.engine), - "type is associated with a different engine" - ); - ty.to_wasm_type() - } - HeapTypeTemplate::LocalStruct(id) => { - self.check_owns_struct(*id); - WasmHeapType::ConcreteStruct(module_index(id.index)) - } - HeapTypeTemplate::LocalArray(id) => { - self.check_owns_array(*id); - WasmHeapType::ConcreteArray(module_index(id.index)) - } - HeapTypeTemplate::LocalFunc(id) => { - self.check_owns_func(*id); - WasmHeapType::ConcreteFunc(module_index(id.index)) + /// Set this function type's supertype to an already-registered function type. + pub fn supertype(&mut self, supertype: FuncType) -> &mut Self { + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Func { supertype: s, .. }) => *s = Some(SuperDef::Known(supertype)), + _ => unreachable!("func builder on a non-func member"), + } + self + } + + /// Set this function type's supertype to another function being defined in + /// the same rec group. + #[track_caller] + pub fn forward_supertype(&mut self, supertype: PendingType) -> &mut Self { + self.check_owns(supertype); + match self.rec.members[self.index as usize].as_mut() { + Some(MemberDef::Func { supertype: s, .. }) => { + *s = Some(SuperDef::Forward(supertype.index)) } - }) + _ => unreachable!("func builder on a non-func member"), + } + self + } + + /// Append a parameter whose type is already known. + pub fn param(&mut self, ty: ValType) -> &mut Self { + self.params_mut().push(ValDef::Concrete(ty)); + self + } + + /// Append a result whose type is already known. + pub fn result(&mut self, ty: ValType) -> &mut Self { + self.results_mut().push(ValDef::Concrete(ty)); + self + } + + /// Append a parameter that is a reference to another type being defined in + /// the same rec group, with the given nullability. + #[track_caller] + pub fn forward_ref_param(&mut self, is_nullable: bool, ty: PendingType) -> &mut Self { + self.check_owns(ty); + self.params_mut().push(ValDef::Forward { + target: ty.index, + nullable: is_nullable, + }); + self + } + + /// Append a result that is a reference to another type being defined in the + /// same rec group, with the given nullability. + #[track_caller] + pub fn forward_ref_result(&mut self, is_nullable: bool, ty: PendingType) -> &mut Self { + self.check_owns(ty); + self.results_mut().push(ValDef::Forward { + target: ty.index, + nullable: is_nullable, + }); + self + } + + #[track_caller] + fn check_owns(&self, ty: PendingType) { + assert_eq!( + ty.builder_id, self.rec.builder_id, + "`PendingType` used with a different `RecGroupBuilder` than it came from" + ); } } /// A registered recursion group of Wasm types. /// -/// Produced by [`RecGroupBuilder::build`]. Use the kind-typed getters -/// ([`struct_`][Self::struct_], [`array`][Self::array], [`func`][Self::func]) to -/// retrieve a member by its label. +/// Produced by [`RecGroupBuilder::build`]. Use the getters +/// ([`get_struct`][Self::get_struct], [`get_array`][Self::get_array], +/// [`get_func`][Self::get_func]) to retrieve a member by its handle. /// /// The group's types stay registered with the engine for as long as either this /// `RecGroup` or any type retrieved from it is alive. @@ -862,45 +553,53 @@ impl RecGroup { self.types.len() } - /// Whether this rec group is empty. - /// - /// This is always `false`, as a rec group must contain at least one type; - /// it exists for API completeness alongside [`len`][Self::len]. + /// Whether this rec group is empty. Always `false`, since a rec group must + /// contain at least one type; provided for API completeness. pub fn is_empty(&self) -> bool { self.types.is_empty() } - /// Get the struct type for the given label. - /// - /// # Panics - /// - /// Panics if the label did not come from the [`RecGroupBuilder`] that - /// produced this rec group. - pub fn struct_(&self, id: PendingStructId) -> StructType { - assert_eq!(id.builder_id, self.builder_id, "label from another builder"); - StructType::from_registered_type(self.types[id.index as usize].clone()) + #[track_caller] + fn registered(&self, ty: PendingType) -> &RegisteredType { + assert_eq!( + ty.builder_id, self.builder_id, + "`PendingType` used with a different `RecGroup` than it came from" + ); + &self.types[ty.index as usize] } - /// Get the array type for the given label. - /// - /// # Panics - /// - /// Panics if the label did not come from the [`RecGroupBuilder`] that - /// produced this rec group. - pub fn array(&self, id: PendingArrayId) -> ArrayType { - assert_eq!(id.builder_id, self.builder_id, "label from another builder"); - ArrayType::from_registered_type(self.types[id.index as usize].clone()) + fn struct_at(&self, index: usize) -> StructType { + StructType::from_registered_type(self.types[index].clone()) + } + fn array_at(&self, index: usize) -> ArrayType { + ArrayType::from_registered_type(self.types[index].clone()) + } + fn func_at(&self, index: usize) -> FuncType { + FuncType::from_registered_type(self.types[index].clone()) } - /// Get the function type for the given label. - /// - /// # Panics - /// - /// Panics if the label did not come from the [`RecGroupBuilder`] that - /// produced this rec group. - pub fn func(&self, id: PendingFuncId) -> FuncType { - assert_eq!(id.builder_id, self.builder_id, "label from another builder"); - FuncType::from_registered_type(self.types[id.index as usize].clone()) + /// Get the struct type for the given handle, or `None` if it was defined as + /// a different kind of type. + pub fn get_struct(&self, ty: PendingType) -> Option { + let rt = self.registered(ty); + rt.is_struct() + .then(|| StructType::from_registered_type(rt.clone())) + } + + /// Get the array type for the given handle, or `None` if it was defined as a + /// different kind of type. + pub fn get_array(&self, ty: PendingType) -> Option { + let rt = self.registered(ty); + rt.is_array() + .then(|| ArrayType::from_registered_type(rt.clone())) + } + + /// Get the function type for the given handle, or `None` if it was defined + /// as a different kind of type. + pub fn get_func(&self, ty: PendingType) -> Option { + let rt = self.registered(ty); + rt.is_func() + .then(|| FuncType::from_registered_type(rt.clone())) } /// Iterate over all of the types in this rec group, in definition order. @@ -917,80 +616,257 @@ impl RecGroup { } }) } +} - /// Get the registered struct type at the given member index, for validation. - fn struct_at(&self, index: usize) -> StructType { - StructType::from_registered_type(self.types[index].clone()) - } - fn array_at(&self, index: usize) -> ArrayType { - ArrayType::from_registered_type(self.types[index].clone()) - } - fn func_at(&self, index: usize) -> FuncType { - FuncType::from_registered_type(self.types[index].clone()) +/// The `WasmHeapType` for a forward reference to the member at `target`, +/// choosing the concrete variant based on the target's kind. +fn forward_heap(members: &[Option], target: u32) -> WasmHeapType { + match members[target as usize] + .as_ref() + .expect("all members are defined before lowering") + { + MemberDef::Struct { .. } => WasmHeapType::ConcreteStruct(module_index(target)), + MemberDef::Array { .. } => WasmHeapType::ConcreteArray(module_index(target)), + MemberDef::Func { .. } => WasmHeapType::ConcreteFunc(module_index(target)), } +} - /// Validate that the member at `index` structurally matches its declared - /// supertype, now that all sibling references are registered and resolvable. - fn validate_supertype(&self, index: usize, def: &MemberDef) -> Result<()> { - match def { - MemberDef::Struct { - supertype: Some(supertype), - .. - } => { - let sub = self.struct_at(index); - let sup = match supertype { - StructSuperType::Local(id) => self.struct_at(id.index as usize), - StructSuperType::Type(ty) => ty.clone(), - }; - ensure!( - struct_fields_match(&sub, &sup), - "struct type {index} does not match its supertype: \ - found {sub}, expected supertype {sup}", - ); - } - MemberDef::Array { - supertype: Some(supertype), - .. - } => { - let sub = self.array_at(index); - let sup = match supertype { - ArraySuperType::Local(id) => self.array_at(id.index as usize), - ArraySuperType::Type(ty) => ty.clone(), - }; - ensure!( - sub.field_type().matches(&sup.field_type()), - "array type {index} does not match its supertype: \ - found {sub}, expected supertype {sup}", - ); - } - MemberDef::Func { - supertype: Some(supertype), - .. - } => { - let sub = self.func_at(index); - let sup = match supertype { - FuncSuperType::Local(id) => self.func_at(id.index as usize), - FuncSuperType::Type(ty) => ty.clone(), - }; - // `FuncType::matches` performs structural (not nominal) matching - // for distinct types, which is exactly the subtype check. - ensure!( - sub.matches(&sup), - "function type {index} does not match its supertype: \ - found {sub}, expected supertype {sup}", - ); - } - // No supertype: nothing to validate. - MemberDef::Struct { .. } | MemberDef::Array { .. } | MemberDef::Func { .. } => {} +fn lower_member( + engine: &Engine, + members: &[Option], + def: &MemberDef, +) -> Result { + let (finality, supertype, inner) = match def { + MemberDef::Struct { + finality, + supertype, + fields, + } => { + ensure!( + fields.len() <= MAX_FIELDS, + "attempted to define a struct type with {} fields, but that is more than the \ + maximum supported number of fields ({MAX_FIELDS})", + fields.len(), + ); + let supertype = match supertype { + None => None, + Some(SuperDef::Forward(t)) => Some(module_index(*t)), + Some(SuperDef::Known(ty)) => { + ensure!( + ty.comes_from_same_engine(engine), + "supertype is associated with a different engine" + ); + Some(EngineOrModuleTypeIndex::Engine(ty.type_index())) + } + }; + let fields = fields + .iter() + .map(|f| lower_field(engine, members, f)) + .collect::>>()?; + ( + *finality, + supertype, + WasmCompositeInnerType::Struct(WasmStructType { + fields: fields.into(), + }), + ) + } + MemberDef::Array { + finality, + supertype, + element, + } => { + let supertype = match supertype { + None => None, + Some(SuperDef::Forward(t)) => Some(module_index(*t)), + Some(SuperDef::Known(ty)) => { + ensure!( + ty.comes_from_same_engine(engine), + "supertype is associated with a different engine" + ); + Some(EngineOrModuleTypeIndex::Engine(ty.type_index())) + } + }; + let field = lower_field(engine, members, element.as_ref().unwrap())?; + ( + *finality, + supertype, + WasmCompositeInnerType::Array(WasmArrayType(field)), + ) + } + MemberDef::Func { + finality, + supertype, + params, + results, + } => { + let supertype = match supertype { + None => None, + Some(SuperDef::Forward(t)) => Some(module_index(*t)), + Some(SuperDef::Known(ty)) => { + ensure!( + ty.comes_from_same_engine(engine), + "supertype is associated with a different engine" + ); + Some(EngineOrModuleTypeIndex::Engine(ty.type_index())) + } + }; + let params = params + .iter() + .map(|p| lower_val(engine, members, p)) + .collect::>>()?; + let results = results + .iter() + .map(|r| lower_val(engine, members, r)) + .collect::>>()?; + ( + *finality, + supertype, + WasmCompositeInnerType::Func(WasmFuncType::new(params, results)?), + ) } - Ok(()) + }; + + Ok(WasmSubType { + is_final: finality.is_final(), + supertype, + composite_type: WasmCompositeType { + shared: false, + inner, + }, + }) +} + +fn lower_field( + engine: &Engine, + members: &[Option], + field: &FieldDef, +) -> Result { + Ok(match field { + FieldDef::Concrete(ty) => { + ensure!( + ty.comes_from_same_engine(engine), + "field type is associated with a different engine" + ); + ty.to_wasm_field_type() + } + FieldDef::Forward { + target, + nullable, + mutable, + } => WasmFieldType { + element_type: WasmStorageType::Val(WasmValType::Ref(WasmRefType { + nullable: *nullable, + heap_type: forward_heap(members, *target), + })), + mutable: *mutable, + }, + }) +} + +fn lower_val(engine: &Engine, members: &[Option], val: &ValDef) -> Result { + Ok(match val { + ValDef::Concrete(ty) => { + ensure!( + ty.comes_from_same_engine(engine), + "type is associated with a different engine" + ); + ty.to_wasm_type() + } + ValDef::Forward { target, nullable } => WasmValType::Ref(WasmRefType { + nullable: *nullable, + heap_type: forward_heap(members, *target), + }), + }) +} + +/// Validate that the member at `index` structurally matches its declared +/// supertype (if any), now that all forward references are registered. +fn validate_supertype(group: &RecGroup, index: usize, def: &MemberDef) -> Result<()> { + match def { + MemberDef::Struct { + supertype: Some(supertype), + .. + } => { + let sub = group.struct_at(index); + let sup = match supertype { + SuperDef::Forward(t) => { + let t = *t as usize; + ensure!( + group.types[t].is_struct(), + "a struct type's supertype must be a struct type" + ); + group.struct_at(t) + } + SuperDef::Known(ty) => ty.clone(), + }; + ensure!( + sup.finality().is_non_final(), + "cannot create a subtype of a final supertype" + ); + ensure!( + struct_fields_match(&sub, &sup), + "struct fields must match their supertype's fields" + ); + } + MemberDef::Array { + supertype: Some(supertype), + .. + } => { + let sub = group.array_at(index); + let sup = match supertype { + SuperDef::Forward(t) => { + let t = *t as usize; + ensure!( + group.types[t].is_array(), + "an array type's supertype must be an array type" + ); + group.array_at(t) + } + SuperDef::Known(ty) => ty.clone(), + }; + ensure!( + sup.finality().is_non_final(), + "cannot create a subtype of a final supertype" + ); + ensure!( + sub.field_type().matches(&sup.field_type()), + "array field type must match its supertype's field type" + ); + } + MemberDef::Func { + supertype: Some(supertype), + .. + } => { + let sub = group.func_at(index); + let sup = match supertype { + SuperDef::Forward(t) => { + let t = *t as usize; + ensure!( + group.types[t].is_func(), + "a function type's supertype must be a function type" + ); + group.func_at(t) + } + SuperDef::Known(ty) => ty.clone(), + }; + ensure!( + sup.finality().is_non_final(), + "cannot create a subtype of a final supertype" + ); + // `FuncType::matches` performs structural (not nominal) matching for + // distinct types, which is exactly the subtype check we want. + ensure!(sub.matches(&sup), "function type must match its supertype"); + } + // No supertype: nothing to validate. + MemberDef::Struct { .. } | MemberDef::Array { .. } | MemberDef::Func { .. } => {} } + Ok(()) } -/// Does struct `sub` structurally match (i.e. subtype) struct `sup`? -/// -/// Mirrors `StructType::fields_match`: `sub` must have at least as many fields -/// as `sup`, and each of `sup`'s fields must be matched by `sub`'s. +/// Does struct `sub` structurally match (i.e. subtype) struct `sup`? `sub` must +/// have at least as many fields as `sup`, and each of `sup`'s fields must be +/// matched by `sub`'s. fn struct_fields_match(sub: &StructType, sup: &StructType) -> bool { sub.fields().len() >= sup.fields().len() && sub.fields().zip(sup.fields()).all(|(a, b)| a.matches(&b)) diff --git a/tests/all/types.rs b/tests/all/types.rs index 7ffb731ed276..4b095cbcb87d 100644 --- a/tests/all/types.rs +++ b/tests/all/types.rs @@ -724,17 +724,19 @@ fn rec_group_self_reference() -> Result<()> { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let node = builder.declare_struct(); - builder.define_struct(node, [FieldTemplate::ref_(Mutability::Var, true, node)]); + let node = builder.declare(); + // forward_ref_field(mutability, is_nullable, target) + builder + .define_struct(node) + .forward_ref_field(Mutability::Const, true, node); let group = builder.build()?; assert_eq!(group.len(), 1); - let node = group.struct_(node); + let node = group.get_struct(node).unwrap(); assert_eq!(node.fields().len(), 1); let field = node.field(0).unwrap(); - let referenced = field_concrete_struct(&field); - assert!(StructType::eq(&referenced, &node)); + assert!(StructType::eq(&field_concrete_struct(&field), &node)); Ok(()) } @@ -743,17 +745,20 @@ fn rec_group_mutual_recursion() -> Result<()> { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let s1 = builder.declare_struct(); - let s2 = builder.declare_struct(); - builder.define_struct(s1, [FieldTemplate::ref_(Mutability::Var, true, s2)]); - builder.define_struct(s2, [FieldTemplate::ref_(Mutability::Const, false, s1)]); + let s1 = builder.declare(); + let s2 = builder.declare(); + builder + .define_struct(s1) + .forward_ref_field(Mutability::Var, true, s2); + builder + .define_struct(s2) + .forward_ref_field(Mutability::Const, false, s1); let group = builder.build()?; assert_eq!(group.len(), 2); - let s1 = group.struct_(s1); - let s2 = group.struct_(s2); + let s1 = group.get_struct(s1).unwrap(); + let s2 = group.get_struct(s2).unwrap(); - // s1.field0 -> (ref null s2) let f1 = s1.field(0).unwrap(); match f1.element_type() { StorageType::ValType(ValType::Ref(r)) => assert!(r.is_nullable()), @@ -761,7 +766,6 @@ fn rec_group_mutual_recursion() -> Result<()> { } assert!(StructType::eq(&field_concrete_struct(&f1), &s2)); - // s2.field0 -> (ref s1), non-nullable let f2 = s2.field(0).unwrap(); match f2.element_type() { StorageType::ValType(ValType::Ref(r)) => assert!(!r.is_nullable()), @@ -785,20 +789,25 @@ fn rec_group_mixed_concrete_and_local() -> Result<()> { )?; let mut builder = RecGroupBuilder::new(&engine); - let node = builder.declare_struct(); - builder.define_struct( - node, - [ - // forward ref to the sibling - FieldTemplate::ref_(Mutability::Var, true, node), - // ref to an already-registered type - FieldTemplate::ref_(Mutability::Const, false, existing.clone()), - // a plain scalar - FieldTemplate::new(Mutability::Var, StorageType::ValType(ValType::I64)), - ], - ); + let node = builder.declare(); + builder + .define_struct(node) + // forward ref to the sibling + .forward_ref_field(Mutability::Var, true, node) + // ref to an already-registered type + .field(FieldType::new( + Mutability::Const, + StorageType::ValType( + RefType::new(false, HeapType::ConcreteStruct(existing.clone())).into(), + ), + )) + // a plain scalar + .field(FieldType::new( + Mutability::Var, + StorageType::ValType(ValType::I64), + )); let group = builder.build()?; - let node = group.struct_(node); + let node = group.get_struct(node).unwrap(); assert_eq!(node.fields().len(), 3); assert!(StructType::eq( @@ -819,12 +828,15 @@ fn rec_group_dedup() -> Result<()> { let build = || -> Result { let mut builder = RecGroupBuilder::new(&engine); - let s1 = builder.declare_struct(); - let s2 = builder.declare_struct(); - builder.define_struct(s1, [FieldTemplate::ref_(Mutability::Var, true, s2)]); - builder.define_struct(s2, [FieldTemplate::ref_(Mutability::Const, false, s1)]); - let group = builder.build()?; - Ok(group.struct_(s1)) + let s1 = builder.declare(); + let s2 = builder.declare(); + builder + .define_struct(s1) + .forward_ref_field(Mutability::Const, true, s2); + builder + .define_struct(s2) + .forward_ref_field(Mutability::Const, false, s1); + Ok(builder.build()?.get_struct(s1).unwrap()) }; let a = build()?; @@ -835,152 +847,179 @@ fn rec_group_dedup() -> Result<()> { } #[test] -fn rec_group_subtyping_via_sibling() -> Result<()> { +fn rec_group_array_and_func_recursive() -> Result<()> { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let base = builder.declare_struct(); - let derived = builder.declare_struct(); - builder.define_struct_with_finality_and_supertype( - base, - Finality::NonFinal, - None::, - [FieldType::new( - Mutability::Const, - StorageType::ValType(ValType::I32), - )], - ); - builder.define_struct_with_finality_and_supertype( - derived, - Finality::Final, - Some(base), - // width subtype: keeps base's field, adds another - [ - FieldType::new(Mutability::Const, StorageType::ValType(ValType::I32)), - FieldType::new(Mutability::Const, StorageType::ValType(ValType::I64)), - ], - ); + let node = builder.declare(); + let arr = builder.declare(); + let func = builder.declare(); + + builder + .define_struct(node) + .forward_ref_field(Mutability::Var, true, arr) + .forward_ref_field(Mutability::Var, true, func); + builder + .define_array(arr) + .forward_ref_element(Mutability::Var, true, node); + builder + .define_func(func) + .forward_ref_param(true, node) + .forward_ref_result(true, node); + let group = builder.build()?; + assert_eq!(group.len(), 3); - let base = group.struct_(base); - let derived = group.struct_(derived); - assert!(derived.matches(&base)); - assert_eq!( - derived.supertype().map(|s| StructType::eq(&s, &base)), - Some(true) - ); + let arr = group.get_array(arr).unwrap(); + let func = group.get_func(func).unwrap(); + assert_eq!(func.params().len(), 1); + assert_eq!(func.results().len(), 1); + assert!(arr.field_type().element_type().is_val_type()); Ok(()) } #[test] -fn rec_group_subtyping_via_concrete() -> Result<()> { +fn rec_group_declared_but_undefined_errors() { let engine = Engine::default(); + let mut builder = RecGroupBuilder::new(&engine); + let _s = builder.declare(); + let err = builder.build().unwrap_err(); + assert!( + err.to_string().contains("declared but never defined"), + "unexpected error: {err}" + ); +} - let base = StructType::with_finality_and_supertype( - &engine, - Finality::NonFinal, - None, - [FieldType::new( - Mutability::Const, - StorageType::ValType(ValType::I32), - )], - )?; +#[test] +fn rec_group_empty_errors() { + let engine = Engine::default(); + let builder = RecGroupBuilder::new(&engine); + assert!(builder.build().is_err()); +} +#[test] +fn rec_group_array_missing_element_errors() { + let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let derived = builder.declare_struct(); - builder.define_struct_with_finality_and_supertype( - derived, - Finality::Final, - Some(base.clone()), - [FieldType::new( - Mutability::Const, - StorageType::ValType(ValType::I32), - )], + let a = builder.declare(); + builder.define_array(a); + let err = builder.build().unwrap_err(); + assert!( + err.to_string().contains("element type was never set"), + "unexpected error: {err}" ); +} + +#[test] +fn rec_group_types_iter() -> Result<()> { + let engine = Engine::default(); + let mut builder = RecGroupBuilder::new(&engine); + let s = builder.declare(); + let a = builder.declare(); + builder.define_struct(s).field(FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )); + builder + .define_array(a) + .element(FieldType::new(Mutability::Var, StorageType::I8)); let group = builder.build()?; - assert!(group.struct_(derived).matches(&base)); + let kinds: Vec<_> = group + .types() + .map(|t| match t { + CompositeType::Struct(_) => "struct", + CompositeType::Array(_) => "array", + CompositeType::Func(_) => "func", + }) + .collect(); + assert_eq!(kinds, ["struct", "array"]); Ok(()) } #[test] -fn rec_group_array_and_func_recursive() -> Result<()> { +fn rec_group_subtyping_via_forward_supertype() -> Result<()> { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let node = builder.declare_struct(); - let arr = builder.declare_array(); - let func = builder.declare_func(); - // struct referencing the array and func siblings - builder.define_struct( - node, - [ - FieldTemplate::ref_(Mutability::Var, true, arr), - FieldTemplate::ref_(Mutability::Var, true, func), - ], - ); - // array of (ref null node) - builder.define_array(arr, FieldTemplate::ref_(Mutability::Var, true, node)); - // func: (param (ref null node)) -> (ref null node) - builder.define_func( - func, - [ValTypeTemplate::ref_(true, node)], - [ValTypeTemplate::ref_(true, node)], - ); + let base = builder.declare(); + let derived = builder.declare(); + builder + .define_struct(base) + .finality(Finality::NonFinal) + .field(FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )); + builder + .define_struct(derived) + .forward_supertype(base) + // width subtype: keeps base's field, adds another + .field(FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )) + .field(FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I64), + )); let group = builder.build()?; - assert_eq!(group.len(), 3); - let arr = group.array(arr); - let func = group.func(func); - assert_eq!(func.params().len(), 1); - assert_eq!(func.results().len(), 1); - // array element is a ref - assert!(arr.field_type().element_type().is_val_type()); + let base = group.get_struct(base).unwrap(); + let derived = group.get_struct(derived).unwrap(); + assert_eq!(base.finality(), Finality::NonFinal); + assert!(derived.matches(&base)); + assert!(StructType::eq(&derived.supertype().unwrap(), &base)); Ok(()) } #[test] -fn rec_group_declared_but_undefined_errors() { +fn rec_group_subtyping_via_known_supertype() -> Result<()> { let engine = Engine::default(); + + let base = StructType::with_finality_and_supertype( + &engine, + Finality::NonFinal, + None, + [FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )], + )?; + let mut builder = RecGroupBuilder::new(&engine); - let _s = builder.declare_struct(); - let err = builder.build().unwrap_err(); - assert!( - err.to_string().contains("declared but never defined"), - "unexpected error: {err}" - ); -} + let derived = builder.declare(); + builder + .define_struct(derived) + .supertype(base.clone()) + .field(FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )); + let group = builder.build()?; -#[test] -fn rec_group_empty_errors() { - let engine = Engine::default(); - let builder = RecGroupBuilder::new(&engine); - assert!(builder.build().is_err()); + assert!(group.get_struct(derived).unwrap().matches(&base)); + Ok(()) } #[test] fn rec_group_final_supertype_errors() { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let base = builder.declare_struct(); - let derived = builder.declare_struct(); + let base = builder.declare(); + let derived = builder.declare(); // base is final (default) - builder.define_struct( - base, - [FieldType::new( - Mutability::Const, - StorageType::ValType(ValType::I32), - )], - ); - builder.define_struct_with_finality_and_supertype( - derived, - Finality::Final, - Some(base), - [FieldType::new( + builder.define_struct(base).field(FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )); + builder + .define_struct(derived) + .forward_supertype(base) + .field(FieldType::new( Mutability::Const, StorageType::ValType(ValType::I32), - )], - ); + )); let err = builder.build().unwrap_err(); assert!( err.to_string().contains("final supertype"), @@ -992,78 +1031,71 @@ fn rec_group_final_supertype_errors() { fn rec_group_supertype_mismatch_errors() { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let base = builder.declare_struct(); - let derived = builder.declare_struct(); - builder.define_struct_with_finality_and_supertype( - base, - Finality::NonFinal, - None::, - [FieldType::new( + let base = builder.declare(); + let derived = builder.declare(); + builder + .define_struct(base) + .finality(Finality::NonFinal) + // mutable field: subtype must match it exactly + .field(FieldType::new( Mutability::Var, StorageType::ValType(ValType::I32), - )], - ); - // mutable fields must be exactly equal; i64 != i32 -> mismatch - builder.define_struct_with_finality_and_supertype( - derived, - Finality::Final, - Some(base), - [FieldType::new( - Mutability::Var, - StorageType::ValType(ValType::I64), - )], - ); + )); + builder + .define_struct(derived) + .forward_supertype(base) + .field( + // i64 != i32 -> mismatch + FieldType::new(Mutability::Var, StorageType::ValType(ValType::I64)), + ); let err = builder.build().unwrap_err(); assert!( - err.to_string().contains("does not match its supertype"), + err.to_string().contains("must match their supertype"), "unexpected error: {err}" ); } #[test] -fn rec_group_nonfinal_root_without_supertype() -> Result<()> { +fn rec_group_wrong_kind_supertype_errors() { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let root = builder.declare_struct(); - // A non-final root with no supertype requires the turbofish on `None`. - builder.define_struct_with_finality_and_supertype( - root, - Finality::NonFinal, - None::, - [FieldType::new( + let base = builder.declare(); + let derived = builder.declare(); + // base is an array, but we use it as a struct's supertype + builder + .define_array(base) + .finality(Finality::NonFinal) + .element(FieldType::new(Mutability::Const, StorageType::I8)); + builder + .define_struct(derived) + .forward_supertype(base) + .field(FieldType::new( Mutability::Const, StorageType::ValType(ValType::I32), - )], + )); + let err = builder.build().unwrap_err(); + assert!( + err.to_string().contains("supertype must be a struct"), + "unexpected error: {err}" ); - let group = builder.build()?; - assert_eq!(group.struct_(root).finality(), Finality::NonFinal); - Ok(()) } #[test] -fn rec_group_types_iter() -> Result<()> { +fn rec_group_nonfinal_root_without_supertype() -> Result<()> { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let s = builder.declare_struct(); - let a = builder.declare_array(); - builder.define_struct( - s, - [FieldType::new( + let root = builder.declare(); + builder + .define_struct(root) + .finality(Finality::NonFinal) + .field(FieldType::new( Mutability::Const, StorageType::ValType(ValType::I32), - )], - ); - builder.define_array(a, FieldType::new(Mutability::Var, StorageType::I8)); + )); let group = builder.build()?; - - let kinds: Vec<_> = group - .types() - .map(|t| match t { - CompositeType::Struct(_) => "struct", - CompositeType::Array(_) => "array", - CompositeType::Func(_) => "func", - }) - .collect(); - assert_eq!(kinds, ["struct", "array"]); + assert_eq!( + group.get_struct(root).unwrap().finality(), + Finality::NonFinal + ); Ok(()) } From c5862e4d06c6cb09faf3d9534206cce06578be72 Mon Sep 17 00:00:00 2001 From: Doron Somech Date: Mon, 29 Jun 2026 19:51:34 +0300 Subject: [PATCH 4/5] Add builder API for forward-reference fields --- crates/wasmtime/src/runtime/rec_group.rs | 200 +++++++++++++++++++---- tests/all/types.rs | 45 +++-- 2 files changed, 198 insertions(+), 47 deletions(-) diff --git a/crates/wasmtime/src/runtime/rec_group.rs b/crates/wasmtime/src/runtime/rec_group.rs index fbc0aa852ee8..ded4c49afccc 100644 --- a/crates/wasmtime/src/runtime/rec_group.rs +++ b/crates/wasmtime/src/runtime/rec_group.rs @@ -21,9 +21,8 @@ //! let mut builder = RecGroupBuilder::new(&engine); //! let a = builder.declare(); //! let b = builder.declare(); -//! // forward_ref_field(mutability, is_nullable, target) -//! builder.define_struct(a).forward_ref_field(Mutability::Const, true, b); -//! builder.define_struct(b).forward_ref_field(Mutability::Const, false, a); +//! builder.define_struct(a).forward_ref_field(b).nullable(true).finish(); +//! builder.define_struct(b).forward_ref_field(a).nullable(false).finish(); //! let group = builder.build()?; //! //! let a: StructType = group.get_struct(a).unwrap(); @@ -331,24 +330,65 @@ impl<'a> StructTypeBuilder<'a> { } /// Append a field that is a reference to another type being defined in the - /// same rec group, with the given mutability and nullability. + /// same rec group. + /// + /// Returns a builder for configuring the reference; call + /// [`finish`][ForwardRefFieldBuilder::finish] to commit the field. The field + /// defaults to immutable and nullable. #[track_caller] - pub fn forward_ref_field( - &mut self, - mutability: Mutability, - is_nullable: bool, - ty: PendingType, - ) -> &mut Self { + pub fn forward_ref_field(&mut self, ty: PendingType) -> ForwardRefFieldBuilder<'_, 'a> { assert_eq!( ty.builder_id, self.rec.builder_id, "`PendingType` used with a different `RecGroupBuilder` than it came from" ); - self.fields_mut().push(FieldDef::Forward { + ForwardRefFieldBuilder { + parent: self, target: ty.index, - nullable: is_nullable, + mutability: Mutability::Const, + nullable: true, + } + } +} + +/// Builder for a struct field that forward-references another type in the same +/// rec group. Created by [`StructTypeBuilder::forward_ref_field`]. +/// +/// Configure the reference, then call [`finish`][Self::finish] to commit the +/// field and return to the struct builder. +pub struct ForwardRefFieldBuilder<'p, 'a> { + parent: &'p mut StructTypeBuilder<'a>, + target: u32, + mutability: Mutability, + nullable: bool, +} + +impl<'p, 'a> ForwardRefFieldBuilder<'p, 'a> { + /// Set the field's mutability. Defaults to [`Mutability::Const`]. + pub fn mutability(mut self, mutability: Mutability) -> Self { + self.mutability = mutability; + self + } + + /// Set whether the reference is nullable. Defaults to `true`. + pub fn nullable(mut self, is_nullable: bool) -> Self { + self.nullable = is_nullable; + self + } + + /// Commit this field and return to the struct builder. + pub fn finish(self) -> &'p mut StructTypeBuilder<'a> { + let ForwardRefFieldBuilder { + parent, + target, + mutability, + nullable, + } = self; + parent.fields_mut().push(FieldDef::Forward { + target, + nullable, mutable: mutability.is_var(), }); - self + parent } } @@ -412,24 +452,65 @@ impl<'a> ArrayTypeBuilder<'a> { } /// Set the array's element type to a reference to another type being defined - /// in the same rec group, with the given mutability and nullability. + /// in the same rec group. + /// + /// Returns a builder for configuring the reference; call + /// [`finish`][ForwardRefElementBuilder::finish] to commit the element. The + /// element defaults to immutable and nullable. #[track_caller] - pub fn forward_ref_element( - &mut self, - mutability: Mutability, - is_nullable: bool, - ty: PendingType, - ) -> &mut Self { + pub fn forward_ref_element(&mut self, ty: PendingType) -> ForwardRefElementBuilder<'_, 'a> { assert_eq!( ty.builder_id, self.rec.builder_id, "`PendingType` used with a different `RecGroupBuilder` than it came from" ); - self.set_element(FieldDef::Forward { + ForwardRefElementBuilder { + parent: self, target: ty.index, - nullable: is_nullable, + mutability: Mutability::Const, + nullable: true, + } + } +} + +/// Builder for an array element that forward-references another type in the same +/// rec group. Created by [`ArrayTypeBuilder::forward_ref_element`]. +/// +/// Configure the reference, then call [`finish`][Self::finish] to commit the +/// element and return to the array builder. +pub struct ForwardRefElementBuilder<'p, 'a> { + parent: &'p mut ArrayTypeBuilder<'a>, + target: u32, + mutability: Mutability, + nullable: bool, +} + +impl<'p, 'a> ForwardRefElementBuilder<'p, 'a> { + /// Set the element's mutability. Defaults to [`Mutability::Const`]. + pub fn mutability(mut self, mutability: Mutability) -> Self { + self.mutability = mutability; + self + } + + /// Set whether the reference is nullable. Defaults to `true`. + pub fn nullable(mut self, is_nullable: bool) -> Self { + self.nullable = is_nullable; + self + } + + /// Commit this element and return to the array builder. + pub fn finish(self) -> &'p mut ArrayTypeBuilder<'a> { + let ForwardRefElementBuilder { + parent, + target, + mutability, + nullable, + } = self; + parent.set_element(FieldDef::Forward { + target, + nullable, mutable: mutability.is_var(), }); - self + parent } } @@ -501,27 +582,37 @@ impl<'a> FuncTypeBuilder<'a> { } /// Append a parameter that is a reference to another type being defined in - /// the same rec group, with the given nullability. + /// the same rec group. + /// + /// Returns a builder for configuring the reference; call + /// [`finish`][ForwardRefFuncValBuilder::finish] to commit it. Defaults to + /// nullable. #[track_caller] - pub fn forward_ref_param(&mut self, is_nullable: bool, ty: PendingType) -> &mut Self { + pub fn forward_ref_param(&mut self, ty: PendingType) -> ForwardRefFuncValBuilder<'_, 'a> { self.check_owns(ty); - self.params_mut().push(ValDef::Forward { + ForwardRefFuncValBuilder { + parent: self, target: ty.index, - nullable: is_nullable, - }); - self + nullable: true, + is_result: false, + } } /// Append a result that is a reference to another type being defined in the - /// same rec group, with the given nullability. + /// same rec group. + /// + /// Returns a builder for configuring the reference; call + /// [`finish`][ForwardRefFuncValBuilder::finish] to commit it. Defaults to + /// nullable. #[track_caller] - pub fn forward_ref_result(&mut self, is_nullable: bool, ty: PendingType) -> &mut Self { + pub fn forward_ref_result(&mut self, ty: PendingType) -> ForwardRefFuncValBuilder<'_, 'a> { self.check_owns(ty); - self.results_mut().push(ValDef::Forward { + ForwardRefFuncValBuilder { + parent: self, target: ty.index, - nullable: is_nullable, - }); - self + nullable: true, + is_result: true, + } } #[track_caller] @@ -533,6 +624,45 @@ impl<'a> FuncTypeBuilder<'a> { } } +/// Builder for a function parameter or result that forward-references another +/// type in the same rec group. Created by +/// [`FuncTypeBuilder::forward_ref_param`] and +/// [`FuncTypeBuilder::forward_ref_result`]. +/// +/// Configure the reference, then call [`finish`][Self::finish] to commit it and +/// return to the function builder. +pub struct ForwardRefFuncValBuilder<'p, 'a> { + parent: &'p mut FuncTypeBuilder<'a>, + target: u32, + nullable: bool, + is_result: bool, +} + +impl<'p, 'a> ForwardRefFuncValBuilder<'p, 'a> { + /// Set whether the reference is nullable. Defaults to `true`. + pub fn nullable(mut self, is_nullable: bool) -> Self { + self.nullable = is_nullable; + self + } + + /// Commit this parameter/result and return to the function builder. + pub fn finish(self) -> &'p mut FuncTypeBuilder<'a> { + let ForwardRefFuncValBuilder { + parent, + target, + nullable, + is_result, + } = self; + let def = ValDef::Forward { target, nullable }; + if is_result { + parent.results_mut().push(def); + } else { + parent.params_mut().push(def); + } + parent + } +} + /// A registered recursion group of Wasm types. /// /// Produced by [`RecGroupBuilder::build`]. Use the getters diff --git a/tests/all/types.rs b/tests/all/types.rs index 4b095cbcb87d..36790e59c2f3 100644 --- a/tests/all/types.rs +++ b/tests/all/types.rs @@ -725,10 +725,11 @@ fn rec_group_self_reference() -> Result<()> { let mut builder = RecGroupBuilder::new(&engine); let node = builder.declare(); - // forward_ref_field(mutability, is_nullable, target) builder .define_struct(node) - .forward_ref_field(Mutability::Const, true, node); + .forward_ref_field(node) + .nullable(true) + .finish(); let group = builder.build()?; assert_eq!(group.len(), 1); @@ -749,10 +750,15 @@ fn rec_group_mutual_recursion() -> Result<()> { let s2 = builder.declare(); builder .define_struct(s1) - .forward_ref_field(Mutability::Var, true, s2); + .forward_ref_field(s2) + .mutability(Mutability::Var) + .nullable(true) + .finish(); builder .define_struct(s2) - .forward_ref_field(Mutability::Const, false, s1); + .forward_ref_field(s1) + .nullable(false) + .finish(); let group = builder.build()?; assert_eq!(group.len(), 2); @@ -793,7 +799,10 @@ fn rec_group_mixed_concrete_and_local() -> Result<()> { builder .define_struct(node) // forward ref to the sibling - .forward_ref_field(Mutability::Var, true, node) + .forward_ref_field(node) + .mutability(Mutability::Var) + .nullable(true) + .finish() // ref to an already-registered type .field(FieldType::new( Mutability::Const, @@ -832,10 +841,14 @@ fn rec_group_dedup() -> Result<()> { let s2 = builder.declare(); builder .define_struct(s1) - .forward_ref_field(Mutability::Const, true, s2); + .forward_ref_field(s2) + .nullable(true) + .finish(); builder .define_struct(s2) - .forward_ref_field(Mutability::Const, false, s1); + .forward_ref_field(s1) + .nullable(false) + .finish(); Ok(builder.build()?.get_struct(s1).unwrap()) }; @@ -857,15 +870,23 @@ fn rec_group_array_and_func_recursive() -> Result<()> { builder .define_struct(node) - .forward_ref_field(Mutability::Var, true, arr) - .forward_ref_field(Mutability::Var, true, func); + .forward_ref_field(arr) + .mutability(Mutability::Var) + .finish() + .forward_ref_field(func) + .mutability(Mutability::Var) + .finish(); builder .define_array(arr) - .forward_ref_element(Mutability::Var, true, node); + .forward_ref_element(node) + .mutability(Mutability::Var) + .finish(); builder .define_func(func) - .forward_ref_param(true, node) - .forward_ref_result(true, node); + .forward_ref_param(node) + .finish() + .forward_ref_result(node) + .finish(); let group = builder.build()?; assert_eq!(group.len(), 3); From 0efba3492160c5f115779f60f2a7cd99278a1406 Mon Sep 17 00:00:00 2001 From: Doron Somech Date: Wed, 1 Jul 2026 10:45:36 +0300 Subject: [PATCH 5/5] Simplify rec group builder and support empty rec groups --- crates/wasmtime/src/runtime/rec_group.rs | 880 +++++++++---------- crates/wasmtime/src/runtime/type_registry.rs | 8 +- tests/all/types.rs | 126 ++- 3 files changed, 492 insertions(+), 522 deletions(-) diff --git a/crates/wasmtime/src/runtime/rec_group.rs b/crates/wasmtime/src/runtime/rec_group.rs index ded4c49afccc..37cebb2412bf 100644 --- a/crates/wasmtime/src/runtime/rec_group.rs +++ b/crates/wasmtime/src/runtime/rec_group.rs @@ -8,10 +8,17 @@ //! defining other types, and the whole group is validated and registered //! together when [`RecGroupBuilder::build`] is called. //! +//! Each type is defined via a builder (e.g. [`RecGroupBuilder::define_struct`]) +//! and committed to the group by calling `finish` on that builder; a definition +//! that is never finished is treated as though the type was never defined. +//! //! Already-registered types (and abstract heap types) are used directly via the //! normal [`FieldType`]/[`ValType`] APIs; the `forward_ref_*` builder methods //! are only for references to other types being defined in the same group. //! +//! The order in which types are declared is significant; see +//! [`RecGroupBuilder`'s docs][RecGroupBuilder#declaration-order-is-significant]. +//! //! ``` //! # use wasmtime::*; //! # fn main() -> Result<()> { @@ -19,10 +26,10 @@ //! //! // Two mutually-recursive struct types. //! let mut builder = RecGroupBuilder::new(&engine); -//! let a = builder.declare(); -//! let b = builder.declare(); -//! builder.define_struct(a).forward_ref_field(b).nullable(true).finish(); -//! builder.define_struct(b).forward_ref_field(a).nullable(false).finish(); +//! let a = builder.declare_struct(); +//! let b = builder.declare_struct(); +//! builder.define_struct(a).forward_ref_field(b).nullable(true).finish().finish(); +//! builder.define_struct(b).forward_ref_field(a).nullable(false).finish().finish(); //! let group = builder.build()?; //! //! let a: StructType = group.get_struct(a).unwrap(); @@ -34,7 +41,9 @@ use crate::prelude::*; use crate::type_registry::RegisteredType; -use crate::{ArrayType, Engine, FieldType, Finality, FuncType, Mutability, StructType, ValType}; +use crate::{ + ArrayType, Engine, FieldType, Finality, FuncType, HeapType, Mutability, StructType, ValType, +}; use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; use wasmtime_environ::{ EngineOrModuleTypeIndex, EntityRef, ModuleInternedTypeIndex, WasmArrayType, @@ -60,72 +69,29 @@ fn module_index(index: u32) -> EngineOrModuleTypeIndex { EngineOrModuleTypeIndex::Module(ModuleInternedTypeIndex::new(index as usize)) } +/// The composite kind a member was declared as, carried by [`PendingType`] so +/// that forward references can be lowered into the correct `WasmHeapType` +/// variant before the referenced member's body is defined. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +enum MemberKind { + Struct, + Array, + Func, +} + /// A handle to a type being defined in a [`RecGroupBuilder`]. /// -/// Obtained from [`RecGroupBuilder::declare`]. It is used both to define the -/// type (via [`RecGroupBuilder::define_struct`] and friends) and to -/// forward-reference it from other types in the same group (via the -/// `forward_ref_*` builder methods). +/// Obtained from [`RecGroupBuilder::declare_struct`] and friends. It is used +/// both to define the type (via [`RecGroupBuilder::define_struct`] and friends) +/// and to forward-reference it from other types in the same group (via the +/// `forward_ref_*` builder methods). It records the kind it was declared as, so +/// that a forward reference can be lowered into the correct `WasmHeapType` +/// variant. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct PendingType { builder_id: usize, index: u32, -} - -/// One of the concrete composite types in a registered [`RecGroup`]. -#[derive(Clone, Debug)] -pub enum CompositeType { - /// A struct type. - Struct(StructType), - /// An array type. - Array(ArrayType), - /// A function type. - Func(FuncType), -} - -/// A struct field or array element being defined: either an already-known -/// concrete/abstract type, or a forward reference to a sibling in this group. -enum FieldDef { - Concrete(FieldType), - Forward { - target: u32, - nullable: bool, - mutable: bool, - }, -} - -/// A function parameter or result being defined: either an already-known -/// concrete/abstract type, or a forward reference to a sibling in this group. -enum ValDef { - Concrete(ValType), - Forward { target: u32, nullable: bool }, -} - -/// A supertype being defined: either a sibling in this group or an -/// already-registered concrete type. Generic over the concrete kind. -enum SuperDef { - Forward(u32), - Known(T), -} - -/// The in-progress definition of one member of a rec group. -enum MemberDef { - Struct { - finality: Finality, - supertype: Option>, - fields: Vec, - }, - Array { - finality: Finality, - supertype: Option>, - element: Option, - }, - Func { - finality: Finality, - supertype: Option>, - params: Vec, - results: Vec, - }, + kind: MemberKind, } /// A builder for defining a recursion group of Wasm types, including types that @@ -133,10 +99,36 @@ enum MemberDef { /// /// See the [module-level documentation](crate::RecGroupBuilder) for an overview /// and examples. +/// +/// # Declaration order is significant +/// +/// The order in which types are *declared* (via +/// [`declare_struct`][Self::declare_struct] and friends) fixes their order +/// within the rec group, and that order is semantically visible: two rec groups +/// containing the same types in a different order are *distinct* types, as are +/// their corresponding members. For example, `$f != $f'` and `$s != $s'` in the +/// following, even though each group defines one `func` and one `struct`: +/// +/// ```wat +/// (rec (type $f (func)) +/// (type $s (struct))) +/// +/// (rec (type $s' (struct)) +/// (type $f' (func))) +/// ``` +/// +/// It is the order of the `declare_*` calls that determines this, not the order +/// in which the types are subsequently defined and finished. pub struct RecGroupBuilder { engine: Engine, builder_id: usize, - members: Vec>, + /// The first error encountered while adding types to the builder (for + /// example, referencing a type from a different engine). Surfaced by + /// [`build`][Self::build]; the chaining builder methods stay infallible. + error: Option, + /// The finished definition of each member, or `None` if it was declared but + /// its builder's `finish` was never called. + members: Vec>, } impl RecGroupBuilder { @@ -145,21 +137,54 @@ impl RecGroupBuilder { RecGroupBuilder { engine: engine.clone(), builder_id: next_builder_id(), + error: None, members: Vec::new(), } } - /// Declare a new type in this rec group, returning a handle that can be used - /// as a forward reference before the type is defined. - pub fn declare(&mut self) -> PendingType { + fn declare(&mut self, kind: MemberKind) -> PendingType { let index = u32::try_from(self.members.len()).expect("too many types in a rec group"); self.members.push(None); PendingType { builder_id: self.builder_id, index, + kind, } } + /// Declare a new struct type in this rec group, returning a handle that can + /// be used as a forward reference before the type is defined. + /// + /// The order of `declare_*` calls fixes the types' order within the rec + /// group, which is semantically significant; see the [module + /// documentation](crate::RecGroupBuilder#declaration-order-is-significant) + /// for details. + pub fn declare_struct(&mut self) -> PendingType { + self.declare(MemberKind::Struct) + } + + /// Declare a new array type in this rec group, returning a handle that can + /// be used as a forward reference before the type is defined. + /// + /// The order of `declare_*` calls fixes the types' order within the rec + /// group, which is semantically significant; see the [module + /// documentation](crate::RecGroupBuilder#declaration-order-is-significant) + /// for details. + pub fn declare_array(&mut self) -> PendingType { + self.declare(MemberKind::Array) + } + + /// Declare a new function type in this rec group, returning a handle that + /// can be used as a forward reference before the type is defined. + /// + /// The order of `declare_*` calls fixes the types' order within the rec + /// group, which is semantically significant; see the [module + /// documentation](crate::RecGroupBuilder#declaration-order-is-significant) + /// for details. + pub fn declare_func(&mut self) -> PendingType { + self.declare(MemberKind::Func) + } + #[track_caller] fn check_owns(&self, ty: PendingType) { assert_eq!( @@ -168,91 +193,132 @@ impl RecGroupBuilder { ); } + /// Record an error to be surfaced by [`build`][Self::build]. Only the first + /// error is kept, so the chaining builder methods can stay infallible. + fn record_error(&mut self, error: Error) { + if self.error.is_none() { + self.error = Some(error); + } + } + + fn check_engine(&mut self, same_engine: bool, what: &str) { + if !same_engine { + self.record_error(format_err!("{what} is associated with a different engine")); + } + } + /// Begin defining the given handle as a struct type. /// - /// Any previous definition of this handle is discarded. + /// The definition is committed to the group by calling + /// [`finish`][StructTypeBuilder::finish]; committing replaces any previous + /// definition of this handle. + /// + /// # Panics + /// + /// Panics if the handle was not declared via + /// [`declare_struct`][Self::declare_struct]. #[track_caller] pub fn define_struct(&mut self, ty: PendingType) -> StructTypeBuilder<'_> { self.check_owns(ty); - self.members[ty.index as usize] = Some(MemberDef::Struct { - finality: Finality::Final, - supertype: None, - fields: Vec::new(), - }); + assert!( + matches!(ty.kind, MemberKind::Struct), + "handle was not declared as a struct type" + ); StructTypeBuilder { rec: self, index: ty.index, + finality: Finality::Final, + supertype: None, + fields: Vec::new(), } } /// Begin defining the given handle as an array type. /// - /// Any previous definition of this handle is discarded. + /// The definition is committed to the group by calling + /// [`finish`][ArrayTypeBuilder::finish]; committing replaces any previous + /// definition of this handle. + /// + /// # Panics + /// + /// Panics if the handle was not declared via + /// [`declare_array`][Self::declare_array]. #[track_caller] pub fn define_array(&mut self, ty: PendingType) -> ArrayTypeBuilder<'_> { self.check_owns(ty); - self.members[ty.index as usize] = Some(MemberDef::Array { - finality: Finality::Final, - supertype: None, - element: None, - }); + assert!( + matches!(ty.kind, MemberKind::Array), + "handle was not declared as an array type" + ); ArrayTypeBuilder { rec: self, index: ty.index, + finality: Finality::Final, + supertype: None, + element: None, } } /// Begin defining the given handle as a function type. /// - /// Any previous definition of this handle is discarded. + /// The definition is committed to the group by calling + /// [`finish`][FuncTypeBuilder::finish]; committing replaces any previous + /// definition of this handle. + /// + /// # Panics + /// + /// Panics if the handle was not declared via + /// [`declare_func`][Self::declare_func]. #[track_caller] pub fn define_func(&mut self, ty: PendingType) -> FuncTypeBuilder<'_> { self.check_owns(ty); - self.members[ty.index as usize] = Some(MemberDef::Func { + assert!( + matches!(ty.kind, MemberKind::Func), + "handle was not declared as a function type" + ); + FuncTypeBuilder { + rec: self, + index: ty.index, finality: Finality::Final, supertype: None, params: Vec::new(), results: Vec::new(), - }); - FuncTypeBuilder { - rec: self, - index: ty.index, } } /// Finish building the rec group: validate all of its types, register them /// with the engine, and return the resulting [`RecGroup`]. /// - /// Returns an error if the group is empty, if any declared type was never - /// defined, if an array's element type was never set, if any type references - /// a type from a different engine, or if a struct exceeds the - /// implementation's field-count limit. + /// An empty group is allowed and produces an empty [`RecGroup`]. + /// + /// Returns an error if any declared type was never defined (i.e. its + /// builder's `finish` was never called), if any type references a type from + /// a different engine, or if a struct exceeds the implementation's + /// field-count limit. pub fn build(self) -> Result { let RecGroupBuilder { engine, builder_id, + error, members, } = self; - ensure!( - !members.is_empty(), - "a rec group must contain at least one type" - ); + if let Some(error) = error { + return Err(error); + } - for (i, member) in members.iter().enumerate() { + let mut sub_types = Vec::with_capacity(members.len()); + for (i, member) in members.into_iter().enumerate() { match member { + Some(sub_type) => sub_types.push(sub_type), None => bail!("type {i} was declared but never defined"), - Some(MemberDef::Array { element: None, .. }) => { - bail!("array type {i} was declared but its element type was never set") - } - Some(_) => {} } } - let mut sub_types = Vec::with_capacity(members.len()); - for member in &members { - sub_types.push(lower_member(&engine, &members, member.as_ref().unwrap())?); - } + // Keep each member's declared supertype so we can structurally validate + // it after registration, once forward references resolve to registered + // types. (`register_rec_group_types` does not itself check subtyping.) + let supertypes: Vec<_> = sub_types.iter().map(|s| s.supertype).collect(); let registered = engine.register_rec_group_types(sub_types.into_iter())?; let group = RecGroup { @@ -260,11 +326,9 @@ impl RecGroupBuilder { types: registered, }; - // Validate that each type structurally matches its declared supertype, - // now that forward references resolve to registered types. On failure, - // `group` is dropped, which unregisters the types. - for (i, member) in members.iter().enumerate() { - validate_supertype(&group, i, member.as_ref().unwrap())?; + // On validation failure, `group` is dropped, which unregisters the types. + for (i, supertype) in supertypes.into_iter().enumerate() { + validate_supertype(&engine, &group, i, supertype)?; } Ok(group) @@ -273,35 +337,28 @@ impl RecGroupBuilder { /// Builder for a struct type within a [`RecGroupBuilder`]. /// -/// Returned by [`RecGroupBuilder::define_struct`]. +/// Returned by [`RecGroupBuilder::define_struct`]. Call [`finish`][Self::finish] +/// to commit the type to the group. pub struct StructTypeBuilder<'a> { rec: &'a mut RecGroupBuilder, index: u32, + finality: Finality, + supertype: Option, + fields: Vec, } impl<'a> StructTypeBuilder<'a> { - fn fields_mut(&mut self) -> &mut Vec { - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Struct { fields, .. }) => fields, - _ => unreachable!("struct builder on a non-struct member"), - } - } - /// Set this struct type's finality. Defaults to [`Finality::Final`]. pub fn finality(&mut self, finality: Finality) -> &mut Self { - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Struct { finality: f, .. }) => *f = finality, - _ => unreachable!("struct builder on a non-struct member"), - } + self.finality = finality; self } /// Set this struct type's supertype to an already-registered struct type. pub fn supertype(&mut self, supertype: StructType) -> &mut Self { - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Struct { supertype: s, .. }) => *s = Some(SuperDef::Known(supertype)), - _ => unreachable!("struct builder on a non-struct member"), - } + let same = supertype.comes_from_same_engine(&self.rec.engine); + self.rec.check_engine(same, "supertype"); + self.supertype = Some(EngineOrModuleTypeIndex::Engine(supertype.type_index())); self } @@ -309,23 +366,17 @@ impl<'a> StructTypeBuilder<'a> { /// same rec group. #[track_caller] pub fn forward_supertype(&mut self, supertype: PendingType) -> &mut Self { - assert_eq!( - supertype.builder_id, self.rec.builder_id, - "`PendingType` used with a different `RecGroupBuilder` than it came from" - ); - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Struct { supertype: s, .. }) => { - *s = Some(SuperDef::Forward(supertype.index)) - } - _ => unreachable!("struct builder on a non-struct member"), - } + self.rec.check_owns(supertype); + self.supertype = Some(module_index(supertype.index)); self } /// Append a field whose type is already known (a scalar, an abstract ref, or /// a reference to an already-registered type). pub fn field(&mut self, ty: FieldType) -> &mut Self { - self.fields_mut().push(FieldDef::Concrete(ty)); + let same = ty.comes_from_same_engine(&self.rec.engine); + self.rec.check_engine(same, "field type"); + self.fields.push(ty.to_wasm_field_type()); self } @@ -337,17 +388,39 @@ impl<'a> StructTypeBuilder<'a> { /// defaults to immutable and nullable. #[track_caller] pub fn forward_ref_field(&mut self, ty: PendingType) -> ForwardRefFieldBuilder<'_, 'a> { - assert_eq!( - ty.builder_id, self.rec.builder_id, - "`PendingType` used with a different `RecGroupBuilder` than it came from" - ); + self.rec.check_owns(ty); ForwardRefFieldBuilder { parent: self, - target: ty.index, + target: ty, mutability: Mutability::Const, nullable: true, } } + + /// Commit this struct definition to the rec group. + pub fn finish(&mut self) { + let index = self.index as usize; + let fields = core::mem::take(&mut self.fields); + if fields.len() > MAX_FIELDS { + self.rec.record_error(format_err!( + "attempted to define a struct type with {} fields, but that is more than the \ + maximum supported number of fields ({MAX_FIELDS})", + fields.len(), + )); + return; + } + let sub_type = WasmSubType { + is_final: self.finality.is_final(), + supertype: self.supertype, + composite_type: WasmCompositeType { + shared: false, + inner: WasmCompositeInnerType::Struct(WasmStructType { + fields: fields.into(), + }), + }, + }; + self.rec.members[index] = Some(sub_type); + } } /// Builder for a struct field that forward-references another type in the same @@ -357,7 +430,7 @@ impl<'a> StructTypeBuilder<'a> { /// field and return to the struct builder. pub struct ForwardRefFieldBuilder<'p, 'a> { parent: &'p mut StructTypeBuilder<'a>, - target: u32, + target: PendingType, mutability: Mutability, nullable: bool, } @@ -383,11 +456,9 @@ impl<'p, 'a> ForwardRefFieldBuilder<'p, 'a> { mutability, nullable, } = self; - parent.fields_mut().push(FieldDef::Forward { - target, - nullable, - mutable: mutability.is_var(), - }); + parent + .fields + .push(forward_field(target, nullable, mutability.is_var())); parent } } @@ -396,35 +467,28 @@ impl<'p, 'a> ForwardRefFieldBuilder<'p, 'a> { /// /// Returned by [`RecGroupBuilder::define_array`]. An array has exactly one /// element type, which must be set via [`element`][Self::element] or -/// [`forward_ref_element`][Self::forward_ref_element]. +/// [`forward_ref_element`][Self::forward_ref_element]. Call +/// [`finish`][Self::finish] to commit the type to the group. pub struct ArrayTypeBuilder<'a> { rec: &'a mut RecGroupBuilder, index: u32, + finality: Finality, + supertype: Option, + element: Option, } impl<'a> ArrayTypeBuilder<'a> { - fn set_element(&mut self, def: FieldDef) { - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Array { element, .. }) => *element = Some(def), - _ => unreachable!("array builder on a non-array member"), - } - } - /// Set this array type's finality. Defaults to [`Finality::Final`]. pub fn finality(&mut self, finality: Finality) -> &mut Self { - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Array { finality: f, .. }) => *f = finality, - _ => unreachable!("array builder on a non-array member"), - } + self.finality = finality; self } /// Set this array type's supertype to an already-registered array type. pub fn supertype(&mut self, supertype: ArrayType) -> &mut Self { - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Array { supertype: s, .. }) => *s = Some(SuperDef::Known(supertype)), - _ => unreachable!("array builder on a non-array member"), - } + let same = supertype.comes_from_same_engine(&self.rec.engine); + self.rec.check_engine(same, "supertype"); + self.supertype = Some(EngineOrModuleTypeIndex::Engine(supertype.type_index())); self } @@ -432,22 +496,16 @@ impl<'a> ArrayTypeBuilder<'a> { /// same rec group. #[track_caller] pub fn forward_supertype(&mut self, supertype: PendingType) -> &mut Self { - assert_eq!( - supertype.builder_id, self.rec.builder_id, - "`PendingType` used with a different `RecGroupBuilder` than it came from" - ); - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Array { supertype: s, .. }) => { - *s = Some(SuperDef::Forward(supertype.index)) - } - _ => unreachable!("array builder on a non-array member"), - } + self.rec.check_owns(supertype); + self.supertype = Some(module_index(supertype.index)); self } /// Set the array's element type to an already-known type. pub fn element(&mut self, ty: FieldType) -> &mut Self { - self.set_element(FieldDef::Concrete(ty)); + let same = ty.comes_from_same_engine(&self.rec.engine); + self.rec.check_engine(same, "element type"); + self.element = Some(ty.to_wasm_field_type()); self } @@ -459,17 +517,34 @@ impl<'a> ArrayTypeBuilder<'a> { /// element defaults to immutable and nullable. #[track_caller] pub fn forward_ref_element(&mut self, ty: PendingType) -> ForwardRefElementBuilder<'_, 'a> { - assert_eq!( - ty.builder_id, self.rec.builder_id, - "`PendingType` used with a different `RecGroupBuilder` than it came from" - ); + self.rec.check_owns(ty); ForwardRefElementBuilder { parent: self, - target: ty.index, + target: ty, mutability: Mutability::Const, nullable: true, } } + + /// Commit this array definition to the rec group. + pub fn finish(&mut self) { + let index = self.index as usize; + let Some(element) = self.element else { + self.rec.record_error(format_err!( + "array type {index} was declared but its element type was never set" + )); + return; + }; + let sub_type = WasmSubType { + is_final: self.finality.is_final(), + supertype: self.supertype, + composite_type: WasmCompositeType { + shared: false, + inner: WasmCompositeInnerType::Array(WasmArrayType(element)), + }, + }; + self.rec.members[index] = Some(sub_type); + } } /// Builder for an array element that forward-references another type in the same @@ -479,7 +554,7 @@ impl<'a> ArrayTypeBuilder<'a> { /// element and return to the array builder. pub struct ForwardRefElementBuilder<'p, 'a> { parent: &'p mut ArrayTypeBuilder<'a>, - target: u32, + target: PendingType, mutability: Mutability, nullable: bool, } @@ -505,53 +580,36 @@ impl<'p, 'a> ForwardRefElementBuilder<'p, 'a> { mutability, nullable, } = self; - parent.set_element(FieldDef::Forward { - target, - nullable, - mutable: mutability.is_var(), - }); + parent.element = Some(forward_field(target, nullable, mutability.is_var())); parent } } /// Builder for a function type within a [`RecGroupBuilder`]. /// -/// Returned by [`RecGroupBuilder::define_func`]. +/// Returned by [`RecGroupBuilder::define_func`]. Call [`finish`][Self::finish] +/// to commit the type to the group. pub struct FuncTypeBuilder<'a> { rec: &'a mut RecGroupBuilder, index: u32, + finality: Finality, + supertype: Option, + params: Vec, + results: Vec, } impl<'a> FuncTypeBuilder<'a> { - fn params_mut(&mut self) -> &mut Vec { - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Func { params, .. }) => params, - _ => unreachable!("func builder on a non-func member"), - } - } - - fn results_mut(&mut self) -> &mut Vec { - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Func { results, .. }) => results, - _ => unreachable!("func builder on a non-func member"), - } - } - /// Set this function type's finality. Defaults to [`Finality::Final`]. pub fn finality(&mut self, finality: Finality) -> &mut Self { - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Func { finality: f, .. }) => *f = finality, - _ => unreachable!("func builder on a non-func member"), - } + self.finality = finality; self } /// Set this function type's supertype to an already-registered function type. pub fn supertype(&mut self, supertype: FuncType) -> &mut Self { - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Func { supertype: s, .. }) => *s = Some(SuperDef::Known(supertype)), - _ => unreachable!("func builder on a non-func member"), - } + let same = supertype.comes_from_same_engine(&self.rec.engine); + self.rec.check_engine(same, "supertype"); + self.supertype = Some(EngineOrModuleTypeIndex::Engine(supertype.type_index())); self } @@ -559,25 +617,24 @@ impl<'a> FuncTypeBuilder<'a> { /// the same rec group. #[track_caller] pub fn forward_supertype(&mut self, supertype: PendingType) -> &mut Self { - self.check_owns(supertype); - match self.rec.members[self.index as usize].as_mut() { - Some(MemberDef::Func { supertype: s, .. }) => { - *s = Some(SuperDef::Forward(supertype.index)) - } - _ => unreachable!("func builder on a non-func member"), - } + self.rec.check_owns(supertype); + self.supertype = Some(module_index(supertype.index)); self } /// Append a parameter whose type is already known. pub fn param(&mut self, ty: ValType) -> &mut Self { - self.params_mut().push(ValDef::Concrete(ty)); + let same = ty.comes_from_same_engine(&self.rec.engine); + self.rec.check_engine(same, "type"); + self.params.push(ty.to_wasm_type()); self } /// Append a result whose type is already known. pub fn result(&mut self, ty: ValType) -> &mut Self { - self.results_mut().push(ValDef::Concrete(ty)); + let same = ty.comes_from_same_engine(&self.rec.engine); + self.rec.check_engine(same, "type"); + self.results.push(ty.to_wasm_type()); self } @@ -589,10 +646,10 @@ impl<'a> FuncTypeBuilder<'a> { /// nullable. #[track_caller] pub fn forward_ref_param(&mut self, ty: PendingType) -> ForwardRefFuncValBuilder<'_, 'a> { - self.check_owns(ty); + self.rec.check_owns(ty); ForwardRefFuncValBuilder { parent: self, - target: ty.index, + target: ty, nullable: true, is_result: false, } @@ -606,21 +663,36 @@ impl<'a> FuncTypeBuilder<'a> { /// nullable. #[track_caller] pub fn forward_ref_result(&mut self, ty: PendingType) -> ForwardRefFuncValBuilder<'_, 'a> { - self.check_owns(ty); + self.rec.check_owns(ty); ForwardRefFuncValBuilder { parent: self, - target: ty.index, + target: ty, nullable: true, is_result: true, } } - #[track_caller] - fn check_owns(&self, ty: PendingType) { - assert_eq!( - ty.builder_id, self.rec.builder_id, - "`PendingType` used with a different `RecGroupBuilder` than it came from" - ); + /// Commit this function definition to the rec group. + pub fn finish(&mut self) { + let index = self.index as usize; + let params = core::mem::take(&mut self.params); + let results = core::mem::take(&mut self.results); + let func = match WasmFuncType::new(params, results) { + Ok(func) => func, + Err(e) => { + self.rec.record_error(e.into()); + return; + } + }; + let sub_type = WasmSubType { + is_final: self.finality.is_final(), + supertype: self.supertype, + composite_type: WasmCompositeType { + shared: false, + inner: WasmCompositeInnerType::Func(func), + }, + }; + self.rec.members[index] = Some(sub_type); } } @@ -633,7 +705,7 @@ impl<'a> FuncTypeBuilder<'a> { /// return to the function builder. pub struct ForwardRefFuncValBuilder<'p, 'a> { parent: &'p mut FuncTypeBuilder<'a>, - target: u32, + target: PendingType, nullable: bool, is_result: bool, } @@ -653,11 +725,14 @@ impl<'p, 'a> ForwardRefFuncValBuilder<'p, 'a> { nullable, is_result, } = self; - let def = ValDef::Forward { target, nullable }; + let val = WasmValType::Ref(WasmRefType { + nullable, + heap_type: forward_heap(target), + }); if is_result { - parent.results_mut().push(def); + parent.results.push(val); } else { - parent.params_mut().push(def); + parent.params.push(val); } parent } @@ -683,8 +758,8 @@ impl RecGroup { self.types.len() } - /// Whether this rec group is empty. Always `false`, since a rec group must - /// contain at least one type; provided for API completeness. + /// Whether this rec group is empty, i.e. was built without declaring any + /// types. pub fn is_empty(&self) -> bool { self.types.is_empty() } @@ -732,264 +807,123 @@ impl RecGroup { .then(|| FuncType::from_registered_type(rt.clone())) } - /// Iterate over all of the types in this rec group, in definition order. - pub fn types(&self) -> impl ExactSizeIterator + '_ { + /// Iterate over all of the types in this rec group, in definition order, + /// each as a concrete [`HeapType`]. + pub fn types(&self) -> impl ExactSizeIterator + '_ { self.types.iter().map(|rt| { let rt = rt.clone(); if rt.is_struct() { - CompositeType::Struct(StructType::from_registered_type(rt)) + HeapType::ConcreteStruct(StructType::from_registered_type(rt)) } else if rt.is_array() { - CompositeType::Array(ArrayType::from_registered_type(rt)) + HeapType::ConcreteArray(ArrayType::from_registered_type(rt)) } else { debug_assert!(rt.is_func()); - CompositeType::Func(FuncType::from_registered_type(rt)) + HeapType::ConcreteFunc(FuncType::from_registered_type(rt)) } }) } } -/// The `WasmHeapType` for a forward reference to the member at `target`, -/// choosing the concrete variant based on the target's kind. -fn forward_heap(members: &[Option], target: u32) -> WasmHeapType { - match members[target as usize] - .as_ref() - .expect("all members are defined before lowering") - { - MemberDef::Struct { .. } => WasmHeapType::ConcreteStruct(module_index(target)), - MemberDef::Array { .. } => WasmHeapType::ConcreteArray(module_index(target)), - MemberDef::Func { .. } => WasmHeapType::ConcreteFunc(module_index(target)), +/// The `WasmHeapType` for a forward reference to the `target` member, choosing +/// the concrete variant based on the target's declared kind. +fn forward_heap(target: PendingType) -> WasmHeapType { + match target.kind { + MemberKind::Struct => WasmHeapType::ConcreteStruct(module_index(target.index)), + MemberKind::Array => WasmHeapType::ConcreteArray(module_index(target.index)), + MemberKind::Func => WasmHeapType::ConcreteFunc(module_index(target.index)), } } -fn lower_member( - engine: &Engine, - members: &[Option], - def: &MemberDef, -) -> Result { - let (finality, supertype, inner) = match def { - MemberDef::Struct { - finality, - supertype, - fields, - } => { - ensure!( - fields.len() <= MAX_FIELDS, - "attempted to define a struct type with {} fields, but that is more than the \ - maximum supported number of fields ({MAX_FIELDS})", - fields.len(), - ); - let supertype = match supertype { - None => None, - Some(SuperDef::Forward(t)) => Some(module_index(*t)), - Some(SuperDef::Known(ty)) => { - ensure!( - ty.comes_from_same_engine(engine), - "supertype is associated with a different engine" - ); - Some(EngineOrModuleTypeIndex::Engine(ty.type_index())) - } - }; - let fields = fields - .iter() - .map(|f| lower_field(engine, members, f)) - .collect::>>()?; - ( - *finality, - supertype, - WasmCompositeInnerType::Struct(WasmStructType { - fields: fields.into(), - }), - ) - } - MemberDef::Array { - finality, - supertype, - element, - } => { - let supertype = match supertype { - None => None, - Some(SuperDef::Forward(t)) => Some(module_index(*t)), - Some(SuperDef::Known(ty)) => { - ensure!( - ty.comes_from_same_engine(engine), - "supertype is associated with a different engine" - ); - Some(EngineOrModuleTypeIndex::Engine(ty.type_index())) - } - }; - let field = lower_field(engine, members, element.as_ref().unwrap())?; - ( - *finality, - supertype, - WasmCompositeInnerType::Array(WasmArrayType(field)), - ) - } - MemberDef::Func { - finality, - supertype, - params, - results, - } => { - let supertype = match supertype { - None => None, - Some(SuperDef::Forward(t)) => Some(module_index(*t)), - Some(SuperDef::Known(ty)) => { - ensure!( - ty.comes_from_same_engine(engine), - "supertype is associated with a different engine" - ); - Some(EngineOrModuleTypeIndex::Engine(ty.type_index())) - } - }; - let params = params - .iter() - .map(|p| lower_val(engine, members, p)) - .collect::>>()?; - let results = results - .iter() - .map(|r| lower_val(engine, members, r)) - .collect::>>()?; - ( - *finality, - supertype, - WasmCompositeInnerType::Func(WasmFuncType::new(params, results)?), - ) - } - }; - - Ok(WasmSubType { - is_final: finality.is_final(), - supertype, - composite_type: WasmCompositeType { - shared: false, - inner, - }, - }) -} - -fn lower_field( - engine: &Engine, - members: &[Option], - field: &FieldDef, -) -> Result { - Ok(match field { - FieldDef::Concrete(ty) => { - ensure!( - ty.comes_from_same_engine(engine), - "field type is associated with a different engine" - ); - ty.to_wasm_field_type() - } - FieldDef::Forward { - target, +/// The `WasmFieldType` for a struct field or array element that forward-references +/// the `target` member. +fn forward_field(target: PendingType, nullable: bool, mutable: bool) -> WasmFieldType { + WasmFieldType { + element_type: WasmStorageType::Val(WasmValType::Ref(WasmRefType { nullable, - mutable, - } => WasmFieldType { - element_type: WasmStorageType::Val(WasmValType::Ref(WasmRefType { - nullable: *nullable, - heap_type: forward_heap(members, *target), - })), - mutable: *mutable, - }, - }) -} - -fn lower_val(engine: &Engine, members: &[Option], val: &ValDef) -> Result { - Ok(match val { - ValDef::Concrete(ty) => { - ensure!( - ty.comes_from_same_engine(engine), - "type is associated with a different engine" - ); - ty.to_wasm_type() - } - ValDef::Forward { target, nullable } => WasmValType::Ref(WasmRefType { - nullable: *nullable, - heap_type: forward_heap(members, *target), - }), - }) + heap_type: forward_heap(target), + })), + mutable, + } } /// Validate that the member at `index` structurally matches its declared -/// supertype (if any), now that all forward references are registered. -fn validate_supertype(group: &RecGroup, index: usize, def: &MemberDef) -> Result<()> { - match def { - MemberDef::Struct { - supertype: Some(supertype), - .. - } => { - let sub = group.struct_at(index); - let sup = match supertype { - SuperDef::Forward(t) => { - let t = *t as usize; - ensure!( - group.types[t].is_struct(), - "a struct type's supertype must be a struct type" - ); - group.struct_at(t) - } - SuperDef::Known(ty) => ty.clone(), - }; - ensure!( - sup.finality().is_non_final(), - "cannot create a subtype of a final supertype" - ); - ensure!( - struct_fields_match(&sub, &sup), - "struct fields must match their supertype's fields" - ); - } - MemberDef::Array { - supertype: Some(supertype), - .. - } => { - let sub = group.array_at(index); - let sup = match supertype { - SuperDef::Forward(t) => { - let t = *t as usize; - ensure!( - group.types[t].is_array(), - "an array type's supertype must be an array type" - ); - group.array_at(t) - } - SuperDef::Known(ty) => ty.clone(), - }; - ensure!( - sup.finality().is_non_final(), - "cannot create a subtype of a final supertype" - ); - ensure!( - sub.field_type().matches(&sup.field_type()), - "array field type must match its supertype's field type" - ); - } - MemberDef::Func { - supertype: Some(supertype), - .. - } => { - let sub = group.func_at(index); - let sup = match supertype { - SuperDef::Forward(t) => { - let t = *t as usize; - ensure!( - group.types[t].is_func(), - "a function type's supertype must be a function type" - ); - group.func_at(t) - } - SuperDef::Known(ty) => ty.clone(), - }; - ensure!( - sup.finality().is_non_final(), - "cannot create a subtype of a final supertype" - ); - // `FuncType::matches` performs structural (not nominal) matching for - // distinct types, which is exactly the subtype check we want. - ensure!(sub.matches(&sup), "function type must match its supertype"); - } - // No supertype: nothing to validate. - MemberDef::Struct { .. } | MemberDef::Array { .. } | MemberDef::Func { .. } => {} +/// `supertype` (if any), now that all forward references are registered. +fn validate_supertype( + engine: &Engine, + group: &RecGroup, + index: usize, + supertype: Option, +) -> Result<()> { + let Some(supertype) = supertype else { + return Ok(()); + }; + let rt = &group.types[index]; + if rt.is_struct() { + let sub = group.struct_at(index); + let sup = match supertype { + EngineOrModuleTypeIndex::Module(t) => { + let t = t.index(); + ensure!( + group.types[t].is_struct(), + "a struct type's supertype must be a struct type" + ); + group.struct_at(t) + } + EngineOrModuleTypeIndex::Engine(idx) => StructType::from_shared_type_index(engine, idx), + EngineOrModuleTypeIndex::RecGroup(_) => unreachable!(), + }; + ensure!( + sup.finality().is_non_final(), + "cannot create a subtype of a final supertype" + ); + ensure!( + struct_fields_match(&sub, &sup), + "struct fields must match their supertype's fields" + ); + } else if rt.is_array() { + let sub = group.array_at(index); + let sup = match supertype { + EngineOrModuleTypeIndex::Module(t) => { + let t = t.index(); + ensure!( + group.types[t].is_array(), + "an array type's supertype must be an array type" + ); + group.array_at(t) + } + EngineOrModuleTypeIndex::Engine(idx) => ArrayType::from_shared_type_index(engine, idx), + EngineOrModuleTypeIndex::RecGroup(_) => unreachable!(), + }; + ensure!( + sup.finality().is_non_final(), + "cannot create a subtype of a final supertype" + ); + ensure!( + sub.field_type().matches(&sup.field_type()), + "array field type must match its supertype's field type" + ); + } else { + debug_assert!(rt.is_func()); + let sub = group.func_at(index); + let sup = match supertype { + EngineOrModuleTypeIndex::Module(t) => { + let t = t.index(); + ensure!( + group.types[t].is_func(), + "a function type's supertype must be a function type" + ); + group.func_at(t) + } + EngineOrModuleTypeIndex::Engine(idx) => FuncType::from_shared_type_index(engine, idx), + EngineOrModuleTypeIndex::RecGroup(_) => unreachable!(), + }; + ensure!( + sup.finality().is_non_final(), + "cannot create a subtype of a final supertype" + ); + // `FuncType::matches` performs structural (not nominal) matching for + // distinct types, which is exactly the subtype check we want. + ensure!(sub.matches(&sup), "function type must match its supertype"); } Ok(()) } diff --git a/crates/wasmtime/src/runtime/type_registry.rs b/crates/wasmtime/src/runtime/type_registry.rs index bbaa19585639..242f1be43c50 100644 --- a/crates/wasmtime/src/runtime/type_registry.rs +++ b/crates/wasmtime/src/runtime/type_registry.rs @@ -210,7 +210,13 @@ impl Engine { types: impl ExactSizeIterator, ) -> Result, OutOfMemory> { let len = types.len(); - assert!(len >= 1, "a rec group must contain at least one type"); + + // An empty rec group (`(rec)`) is valid but contributes no types: there + // is nothing to register and no entry to reference, so return early + // rather than creating a leaked, referenceless registry entry. + if len == 0 { + return Ok(Vec::new()); + } let engine = self.clone(); let gc_runtime = engine.gc_runtime().map(|rt| &**rt); diff --git a/tests/all/types.rs b/tests/all/types.rs index 36790e59c2f3..8c5722fe6f4e 100644 --- a/tests/all/types.rs +++ b/tests/all/types.rs @@ -724,11 +724,12 @@ fn rec_group_self_reference() -> Result<()> { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let node = builder.declare(); + let node = builder.declare_struct(); builder .define_struct(node) .forward_ref_field(node) .nullable(true) + .finish() .finish(); let group = builder.build()?; @@ -746,18 +747,20 @@ fn rec_group_mutual_recursion() -> Result<()> { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let s1 = builder.declare(); - let s2 = builder.declare(); + let s1 = builder.declare_struct(); + let s2 = builder.declare_struct(); builder .define_struct(s1) .forward_ref_field(s2) .mutability(Mutability::Var) .nullable(true) + .finish() .finish(); builder .define_struct(s2) .forward_ref_field(s1) .nullable(false) + .finish() .finish(); let group = builder.build()?; @@ -795,7 +798,7 @@ fn rec_group_mixed_concrete_and_local() -> Result<()> { )?; let mut builder = RecGroupBuilder::new(&engine); - let node = builder.declare(); + let node = builder.declare_struct(); builder .define_struct(node) // forward ref to the sibling @@ -814,7 +817,8 @@ fn rec_group_mixed_concrete_and_local() -> Result<()> { .field(FieldType::new( Mutability::Var, StorageType::ValType(ValType::I64), - )); + )) + .finish(); let group = builder.build()?; let node = group.get_struct(node).unwrap(); @@ -837,17 +841,19 @@ fn rec_group_dedup() -> Result<()> { let build = || -> Result { let mut builder = RecGroupBuilder::new(&engine); - let s1 = builder.declare(); - let s2 = builder.declare(); + let s1 = builder.declare_struct(); + let s2 = builder.declare_struct(); builder .define_struct(s1) .forward_ref_field(s2) .nullable(true) + .finish() .finish(); builder .define_struct(s2) .forward_ref_field(s1) .nullable(false) + .finish() .finish(); Ok(builder.build()?.get_struct(s1).unwrap()) }; @@ -864,9 +870,9 @@ fn rec_group_array_and_func_recursive() -> Result<()> { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let node = builder.declare(); - let arr = builder.declare(); - let func = builder.declare(); + let node = builder.declare_struct(); + let arr = builder.declare_array(); + let func = builder.declare_func(); builder .define_struct(node) @@ -875,17 +881,20 @@ fn rec_group_array_and_func_recursive() -> Result<()> { .finish() .forward_ref_field(func) .mutability(Mutability::Var) + .finish() .finish(); builder .define_array(arr) .forward_ref_element(node) .mutability(Mutability::Var) + .finish() .finish(); builder .define_func(func) .forward_ref_param(node) .finish() .forward_ref_result(node) + .finish() .finish(); let group = builder.build()?; @@ -903,7 +912,7 @@ fn rec_group_array_and_func_recursive() -> Result<()> { fn rec_group_declared_but_undefined_errors() { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let _s = builder.declare(); + let _s = builder.declare_struct(); let err = builder.build().unwrap_err(); assert!( err.to_string().contains("declared but never defined"), @@ -912,18 +921,22 @@ fn rec_group_declared_but_undefined_errors() { } #[test] -fn rec_group_empty_errors() { +fn rec_group_empty_ok() -> Result<()> { + // An empty rec group (`(rec)`) is valid and builds successfully. let engine = Engine::default(); let builder = RecGroupBuilder::new(&engine); - assert!(builder.build().is_err()); + let group = builder.build()?; + assert_eq!(group.len(), 0); + assert!(group.is_empty()); + Ok(()) } #[test] fn rec_group_array_missing_element_errors() { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let a = builder.declare(); - builder.define_array(a); + let a = builder.declare_array(); + builder.define_array(a).finish(); let err = builder.build().unwrap_err(); assert!( err.to_string().contains("element type was never set"), @@ -935,23 +948,28 @@ fn rec_group_array_missing_element_errors() { fn rec_group_types_iter() -> Result<()> { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let s = builder.declare(); - let a = builder.declare(); - builder.define_struct(s).field(FieldType::new( - Mutability::Const, - StorageType::ValType(ValType::I32), - )); + let s = builder.declare_struct(); + let a = builder.declare_array(); + builder + .define_struct(s) + .field(FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )) + .finish(); builder .define_array(a) - .element(FieldType::new(Mutability::Var, StorageType::I8)); + .element(FieldType::new(Mutability::Var, StorageType::I8)) + .finish(); let group = builder.build()?; let kinds: Vec<_> = group .types() .map(|t| match t { - CompositeType::Struct(_) => "struct", - CompositeType::Array(_) => "array", - CompositeType::Func(_) => "func", + HeapType::ConcreteStruct(_) => "struct", + HeapType::ConcreteArray(_) => "array", + HeapType::ConcreteFunc(_) => "func", + other => panic!("unexpected heap type: {other:?}"), }) .collect(); assert_eq!(kinds, ["struct", "array"]); @@ -963,15 +981,16 @@ fn rec_group_subtyping_via_forward_supertype() -> Result<()> { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let base = builder.declare(); - let derived = builder.declare(); + let base = builder.declare_struct(); + let derived = builder.declare_struct(); builder .define_struct(base) .finality(Finality::NonFinal) .field(FieldType::new( Mutability::Const, StorageType::ValType(ValType::I32), - )); + )) + .finish(); builder .define_struct(derived) .forward_supertype(base) @@ -983,7 +1002,8 @@ fn rec_group_subtyping_via_forward_supertype() -> Result<()> { .field(FieldType::new( Mutability::Const, StorageType::ValType(ValType::I64), - )); + )) + .finish(); let group = builder.build()?; let base = group.get_struct(base).unwrap(); @@ -1009,14 +1029,15 @@ fn rec_group_subtyping_via_known_supertype() -> Result<()> { )?; let mut builder = RecGroupBuilder::new(&engine); - let derived = builder.declare(); + let derived = builder.declare_struct(); builder .define_struct(derived) .supertype(base.clone()) .field(FieldType::new( Mutability::Const, StorageType::ValType(ValType::I32), - )); + )) + .finish(); let group = builder.build()?; assert!(group.get_struct(derived).unwrap().matches(&base)); @@ -1027,20 +1048,24 @@ fn rec_group_subtyping_via_known_supertype() -> Result<()> { fn rec_group_final_supertype_errors() { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let base = builder.declare(); - let derived = builder.declare(); + let base = builder.declare_struct(); + let derived = builder.declare_struct(); // base is final (default) - builder.define_struct(base).field(FieldType::new( - Mutability::Const, - StorageType::ValType(ValType::I32), - )); + builder + .define_struct(base) + .field(FieldType::new( + Mutability::Const, + StorageType::ValType(ValType::I32), + )) + .finish(); builder .define_struct(derived) .forward_supertype(base) .field(FieldType::new( Mutability::Const, StorageType::ValType(ValType::I32), - )); + )) + .finish(); let err = builder.build().unwrap_err(); assert!( err.to_string().contains("final supertype"), @@ -1052,8 +1077,8 @@ fn rec_group_final_supertype_errors() { fn rec_group_supertype_mismatch_errors() { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let base = builder.declare(); - let derived = builder.declare(); + let base = builder.declare_struct(); + let derived = builder.declare_struct(); builder .define_struct(base) .finality(Finality::NonFinal) @@ -1061,14 +1086,16 @@ fn rec_group_supertype_mismatch_errors() { .field(FieldType::new( Mutability::Var, StorageType::ValType(ValType::I32), - )); + )) + .finish(); builder .define_struct(derived) .forward_supertype(base) .field( // i64 != i32 -> mismatch FieldType::new(Mutability::Var, StorageType::ValType(ValType::I64)), - ); + ) + .finish(); let err = builder.build().unwrap_err(); assert!( err.to_string().contains("must match their supertype"), @@ -1080,20 +1107,22 @@ fn rec_group_supertype_mismatch_errors() { fn rec_group_wrong_kind_supertype_errors() { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let base = builder.declare(); - let derived = builder.declare(); + let base = builder.declare_array(); + let derived = builder.declare_struct(); // base is an array, but we use it as a struct's supertype builder .define_array(base) .finality(Finality::NonFinal) - .element(FieldType::new(Mutability::Const, StorageType::I8)); + .element(FieldType::new(Mutability::Const, StorageType::I8)) + .finish(); builder .define_struct(derived) .forward_supertype(base) .field(FieldType::new( Mutability::Const, StorageType::ValType(ValType::I32), - )); + )) + .finish(); let err = builder.build().unwrap_err(); assert!( err.to_string().contains("supertype must be a struct"), @@ -1105,14 +1134,15 @@ fn rec_group_wrong_kind_supertype_errors() { fn rec_group_nonfinal_root_without_supertype() -> Result<()> { let engine = Engine::default(); let mut builder = RecGroupBuilder::new(&engine); - let root = builder.declare(); + let root = builder.declare_struct(); builder .define_struct(root) .finality(Finality::NonFinal) .field(FieldType::new( Mutability::Const, StorageType::ValType(ValType::I32), - )); + )) + .finish(); let group = builder.build()?; assert_eq!( group.get_struct(root).unwrap().finality(),