diff --git a/crates/environ/src/compile/module_types.rs b/crates/environ/src/compile/module_types.rs index 082b3cab23f7..85b67b937c79 100644 --- a/crates/environ/src/compile/module_types.rs +++ b/crates/environ/src/compile/module_types.rs @@ -368,6 +368,11 @@ impl ModuleTypesBuilder { self.types } + /// Get the types defined so far by this builder. + pub fn types(&self) -> &ModuleTypes { + &self.types + } + /// Get the elements within an already-defined rec group. pub fn rec_group_elements( &self, diff --git a/crates/environ/src/fact.rs b/crates/environ/src/fact.rs index 02c6518e0be2..49fcd5f7540d 100644 --- a/crates/environ/src/fact.rs +++ b/crates/environ/src/fact.rs @@ -216,6 +216,11 @@ impl DataModel { DataModel::LinearMemory(opts) => opts, } } + + #[must_use] + fn is_gc(&self) -> bool { + matches!(self, Self::Gc { .. }) + } } /// This type is split out of `AdapterOptions` and is specifically used to @@ -337,7 +342,7 @@ impl<'a> Module<'a> { // Import the core wasm function which was lifted using its appropriate // signature since the exported function this adapter generates will // call the lifted function. - let signature = self.types.signature(&lift); + let signature = self.types.signature(&mut self.core_types, &lift.options); let ty = self .core_types .function(&signature.params, &signature.results); @@ -985,7 +990,23 @@ pub enum Import { } impl Options { - fn flat_types<'a>( + /// The number of core values that `ty` lowers to under this data model. + /// + /// Panics under the linear memory data model if `ty` is spilled to memory + /// rather than passed as core values. + pub(super) fn flat_count(&self, ty: &InterfaceType, types: &ComponentTypesBuilder) -> usize { + match self.data_model { + DataModel::Gc {} => 1, + DataModel::LinearMemory(_) => types.flat_types(ty).unwrap().len(), + } + } + + /// The scalar core value types that `ty` lowers to under this data model. + /// + /// `None` means `FlatType` cannot name the lowering, not that there is + /// none: under linear memory `ty` spilled to memory, and under GC it + /// lowers to a reference, which only `gc_flat_types` can recover. + fn scalar_flat_types<'a>( &self, ty: &InterfaceType, types: &'a ComponentTypesBuilder, @@ -1014,14 +1035,17 @@ impl Options { | InterfaceType::Enum(_) | InterfaceType::Flags(_) => Some(flat.memory32), - // These lower to a GC `struct`/`array` reference or to - // `externref`, neither of which FACT can translate yet. + // A `tuple` lowers to a `(ref null (struct ...))`, which + // `FlatType` cannot describe; `flatten_types` handles it. + InterfaceType::Tuple(_) => None, + + // These also lower to a reference, but FACT cannot translate + // them yet. InterfaceType::String | InterfaceType::Record(_) | InterfaceType::Variant(_) | InterfaceType::List(_) | InterfaceType::Map(_) - | InterfaceType::Tuple(_) | InterfaceType::Option(_) | InterfaceType::Result(_) | InterfaceType::Own(_) @@ -1146,7 +1170,7 @@ impl HelperType { fn push_flat(&self, dst: &mut Vec, types: &ComponentTypesBuilder) { match self.loc { HelperLocation::Stack => { - for ty in self.opts.flat_types(&self.ty, types).unwrap() { + for ty in self.opts.scalar_flat_types(&self.ty, types).unwrap() { dst.push((*ty).into()); } } diff --git a/crates/environ/src/fact/core_types.rs b/crates/environ/src/fact/core_types.rs index 6613f7b44a1b..eeb343692a09 100644 --- a/crates/environ/src/fact/core_types.rs +++ b/crates/environ/src/fact/core_types.rs @@ -1,26 +1,303 @@ use crate::prelude::*; +use crate::{ + EngineOrModuleTypeIndex, ModuleInternedRecGroupIndex, ModuleInternedTypeIndex, ModuleTypes, + TypeTrace, WasmCompositeInnerType, WasmFieldType, WasmHeapType, WasmRefType, WasmStorageType, + WasmSubType, WasmValType, +}; +use core::ops::Range; use std::collections::HashMap; -use wasm_encoder::{TypeSection, ValType}; +use std::convert::Infallible; +use wasm_encoder::{ + AbstractHeapType, ArrayType, CompositeInnerType, CompositeType, ContType, FieldType, FuncType, + HeapType, RefType, StorageType, StructType, SubType, TypeSection, ValType, +}; + +/// The rec group currently being encoded, if any, and the range it spans. +type RecGroupCtx = Option<(ModuleInternedRecGroupIndex, Range)>; /// A simple representation of the type section which automatically intern's /// types and ensures they're only defined once. #[derive(Default)] pub struct CoreTypes { pub section: TypeSection, + + /// The next core type index to hand out. Not `section.len()`, which counts + /// rec groups rather than types. + next_ty: u32, + intern: HashMap<(Vec, Vec), u32>, + + /// Cloned rec groups, mapped to the adapter-local index of each member, in + /// order. + rec_groups: HashMap>, + + /// The inverse of `rec_groups`. + module_types: HashMap, } impl CoreTypes { pub fn function(&mut self, params: &[ValType], results: &[ValType]) -> u32 { - *self - .intern - .entry((params.to_vec(), results.to_vec())) - .or_insert_with(|| { - let idx = self.section.len(); - self.section - .ty() - .function(params.iter().copied(), results.iter().copied()); - idx - }) + if let Some(idx) = self.intern.get(&(params.to_vec(), results.to_vec())) { + return *idx; + } + + let idx = self.push_ty(); + self.section + .ty() + .function(params.iter().copied(), results.iter().copied()); + self.intern.insert((params.to_vec(), results.to_vec()), idx); + idx + } + + fn push_ty(&mut self) -> u32 { + let idx = self.next_ty; + self.next_ty += 1; + idx + } + + /// Clone `group` into this adapter module, returning the adapter-local + /// index of each member, in order. + pub fn rec_group(&mut self, types: &ModuleTypes, group: ModuleInternedRecGroupIndex) -> &[u32] { + let mut stack = vec![(group, false)]; + while let Some((group, cloned_deps)) = stack.pop() { + if self.rec_groups.contains_key(&group) { + continue; + } + + let range = types.rec_group_range(group); + + if !cloned_deps { + stack.push((group, true)); + let rec_groups = &self.rec_groups; + for ty in types.rec_group_elements(group) { + let Ok(()) = types[ty].trace::<_, Infallible>(&mut |idx| { + if let EngineOrModuleTypeIndex::Module(idx) = idx { + if !range.contains(&idx) { + let dep = types.rec_group_of(idx); + debug_assert!(dep < group); + if !rec_groups.contains_key(&dep) { + stack.push((dep, false)); + } + } + } + Ok(()) + }); + } + continue; + } + + // Assign indices before encoding, so references between members of + // this group resolve. + let indices = types + .rec_group_elements(group) + .map(|_| self.push_ty()) + .collect::>(); + for (idx, ty) in indices.iter().zip(types.rec_group_elements(group)) { + self.module_types.insert(*idx, ty); + } + self.rec_groups.insert(group, indices); + + let ctx = Some((group, range)); + let encoded = types + .rec_group_elements(group) + .map(|ty| self.sub_type(types, &types[ty], &ctx)) + .collect::>(); + self.section.ty().rec(encoded); + } + + &self.rec_groups[&group] + } + + /// The adapter-local index of `ty`, if its group has been cloned. + fn interned_type_index(&self, types: &ModuleTypes, ty: ModuleInternedTypeIndex) -> Option { + let group = types.rec_group_of(ty); + let indices = self.rec_groups.get(&group)?; + Some(indices[group_position(&types.rec_group_range(group), ty)]) + } + + /// The component type that adapter-local type `index` was cloned from. + pub fn module_type_of(&self, index: u32) -> ModuleInternedTypeIndex { + self.module_types[&index] + } + + fn sub_type(&self, types: &ModuleTypes, ty: &WasmSubType, ctx: &RecGroupCtx) -> SubType { + let inner = match &ty.composite_type.inner { + WasmCompositeInnerType::Struct(s) => CompositeInnerType::Struct(StructType { + fields: s + .fields + .iter() + .map(|f| self.field_type(types, f, ctx)) + .collect(), + }), + WasmCompositeInnerType::Array(a) => { + CompositeInnerType::Array(ArrayType(self.field_type(types, &a.0, ctx))) + } + WasmCompositeInnerType::Func(f) => CompositeInnerType::Func(FuncType::new( + f.params().iter().map(|t| self.val_type(types, t, ctx)), + f.results().iter().map(|t| self.val_type(types, t, ctx)), + )), + WasmCompositeInnerType::Cont(c) => CompositeInnerType::Cont(ContType(self.type_index( + types, + EngineOrModuleTypeIndex::Module(c.unwrap_module_type_index()), + ctx, + ))), + + WasmCompositeInnerType::Exn(_) => { + unreachable!("`exn` types are Wasmtime internal; no core Wasm encoding") + } + }; + + SubType { + is_final: ty.is_final, + supertype_idxs: ty + .supertype + .map(|s| self.type_index(types, s, ctx)) + .into_iter() + .collect(), + composite_type: CompositeType { + inner, + shared: ty.composite_type.shared, + descriptor: None, + describes: None, + }, + } + } + + fn field_type(&self, types: &ModuleTypes, ty: &WasmFieldType, ctx: &RecGroupCtx) -> FieldType { + FieldType { + element_type: match &ty.element_type { + WasmStorageType::I8 => StorageType::I8, + WasmStorageType::I16 => StorageType::I16, + WasmStorageType::Val(v) => StorageType::Val(self.val_type(types, v, ctx)), + }, + mutable: ty.mutable, + } + } + + fn val_type(&self, types: &ModuleTypes, ty: &WasmValType, ctx: &RecGroupCtx) -> ValType { + match ty { + WasmValType::I32 => ValType::I32, + WasmValType::I64 => ValType::I64, + WasmValType::F32 => ValType::F32, + WasmValType::F64 => ValType::F64, + WasmValType::V128 => ValType::V128, + WasmValType::Ref(r) => ValType::Ref(self.ref_type(types, r, ctx)), + } + } + + pub(super) fn ref_type( + &self, + types: &ModuleTypes, + ty: &WasmRefType, + ctx: &RecGroupCtx, + ) -> RefType { + let abstract_ = |ty| HeapType::Abstract { shared: false, ty }; + let heap_type = match ty.heap_type { + WasmHeapType::Extern => abstract_(AbstractHeapType::Extern), + WasmHeapType::NoExtern => abstract_(AbstractHeapType::NoExtern), + WasmHeapType::Func => abstract_(AbstractHeapType::Func), + WasmHeapType::NoFunc => abstract_(AbstractHeapType::NoFunc), + WasmHeapType::Any => abstract_(AbstractHeapType::Any), + WasmHeapType::Eq => abstract_(AbstractHeapType::Eq), + WasmHeapType::I31 => abstract_(AbstractHeapType::I31), + WasmHeapType::Array => abstract_(AbstractHeapType::Array), + WasmHeapType::Struct => abstract_(AbstractHeapType::Struct), + WasmHeapType::None => abstract_(AbstractHeapType::None), + WasmHeapType::Exn => abstract_(AbstractHeapType::Exn), + WasmHeapType::NoExn => abstract_(AbstractHeapType::NoExn), + WasmHeapType::Cont => abstract_(AbstractHeapType::Cont), + WasmHeapType::NoCont => abstract_(AbstractHeapType::NoCont), + + WasmHeapType::ConcreteStruct(i) + | WasmHeapType::ConcreteArray(i) + | WasmHeapType::ConcreteFunc(i) + | WasmHeapType::ConcreteCont(i) => HeapType::Concrete(self.type_index(types, i, ctx)), + + WasmHeapType::ConcreteExn(_) => { + unreachable!("`exn` types are Wasmtime-internal; no core Wasm encoding") + } + }; + RefType { + nullable: ty.nullable, + heap_type, + } + } + + /// Translate a type reference from the component's index space into this + /// adapter module's. + fn type_index( + &self, + types: &ModuleTypes, + index: EngineOrModuleTypeIndex, + ctx: &RecGroupCtx, + ) -> u32 { + let index = match index { + EngineOrModuleTypeIndex::Module(i) => i, + EngineOrModuleTypeIndex::Engine(_) | EngineOrModuleTypeIndex::RecGroup(_) => { + panic!("adapter modules only reference module-interned types") + } + }; + if let Some((group, range)) = ctx { + if range.contains(&index) { + return self.rec_groups[group][group_position(range, index)]; + } + } + self.interned_type_index(types, index) + .expect("referenced rec groups are declared before the groups that reference them") + } +} + +fn group_position(range: &Range, ty: ModuleInternedTypeIndex) -> usize { + debug_assert!(range.contains(&ty)); + usize::try_from(ty.as_u32() - range.start.as_u32()).unwrap() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{WasmCompositeType, WasmStructType}; + + fn struct_referencing(field: Option) -> WasmSubType { + let element_type = match field { + Some(idx) => WasmStorageType::Val(WasmValType::Ref(WasmRefType { + nullable: true, + heap_type: WasmHeapType::ConcreteStruct(EngineOrModuleTypeIndex::Module(idx)), + })), + None => WasmStorageType::I8, + }; + WasmSubType { + is_final: true, + supertype: None, + composite_type: WasmCompositeType { + inner: WasmCompositeInnerType::Struct(WasmStructType { + fields: Box::new([WasmFieldType { + element_type, + mutable: true, + }]), + }), + shared: false, + }, + } + } + + #[test] + #[cfg_attr(miri, ignore)] + fn deep_rec_group_chain_does_not_overflow_the_stack() { + const LEN: usize = 100_000; + + let mut types = ModuleTypes::default(); + let mut prev = None; + for _ in 0..LEN { + let idx = types.push(struct_referencing(prev)); + types.push_rec_group(idx..types.next_ty()); + prev = Some(idx); + } + + let last = types.rec_group_of(prev.unwrap()); + let mut core_types = CoreTypes::default(); + assert_eq!(core_types.rec_group(&types, last).len(), 1); + + // The whole chain is pulled in, since each group references the last. + assert_eq!(core_types.next_ty as usize, LEN); } } diff --git a/crates/environ/src/fact/signature.rs b/crates/environ/src/fact/signature.rs index a6e7439c158a..d91c0322a9ba 100644 --- a/crates/environ/src/fact/signature.rs +++ b/crates/environ/src/fact/signature.rs @@ -3,12 +3,30 @@ use crate::component::{ ComponentTypesBuilder, InterfaceType, MAX_FLAT_ASYNC_PARAMS, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, }; -use crate::fact::{AdapterOptions, Options}; +use crate::fact::core_types::CoreTypes; +use crate::fact::{AdapterOptions, DataModel, Options}; use crate::{WasmValType, prelude::*}; use wasm_encoder::ValType; use super::LinearMemoryOptions; +/// Which half of a function's signature a list of component types is. +/// +/// Under the GC data model the flattening comes from the declared +/// `core-type`, so we need to know which part to look at. +#[derive(Copy, Clone)] +pub enum SignaturePart { + /// Flattening parameters. + Params, + + /// Flattening results. + Results, + + /// `Neither` is for when we aren't using translating to/from a `core-type` + /// signature, such as with helper functions; see also `gc_flat_types`. + Neither, +} + /// Metadata about a core wasm signature which is created for a component model /// signature. #[derive(Debug)] @@ -26,25 +44,45 @@ impl ComponentTypesBuilder { /// This is used to generate the core wasm signatures for functions that are /// imported (matching whatever was `canon lift`'d) and functions that are /// exported (matching the generated function from `canon lower`). - pub(super) fn signature(&self, options: &AdapterOptions) -> Signature { - let f = &self.module_types_builder()[options.options.core_type] + pub(super) fn signature(&self, core_types: &mut CoreTypes, options: &Options) -> Signature { + let f = &self.module_types_builder()[options.core_type] .composite_type .inner .unwrap_func(); Signature { - params: f.params().iter().map(|ty| self.val_type(ty)).collect(), - results: f.results().iter().map(|ty| self.val_type(ty)).collect(), + params: f + .params() + .iter() + .map(|ty| self.val_type(core_types, ty)) + .collect(), + results: f + .results() + .iter() + .map(|ty| self.val_type(core_types, ty)) + .collect(), } } - fn val_type(&self, ty: &WasmValType) -> ValType { + fn val_type(&self, core_types: &mut CoreTypes, ty: &WasmValType) -> ValType { match ty { WasmValType::I32 => ValType::I32, WasmValType::I64 => ValType::I64, WasmValType::F32 => ValType::F32, WasmValType::F64 => ValType::F64, WasmValType::V128 => ValType::V128, - WasmValType::Ref(_) => todo!("CM+GC"), + + // The adapter must name the same type that the caller and callee + // use, which means cloning the rec group it was defined in into + // this adapter module and rewriting the reference to point at the + // clone. + WasmValType::Ref(r) => { + let types = self.module_types_builder().types(); + if let Some(ty) = r.heap_type.as_concrete_type_index() { + let ty = ty.unwrap_module_type_index(); + core_types.rec_group(types, types.rec_group_of(ty)); + } + ValType::Ref(core_types.ref_type(types, r, &None)) + } } } @@ -60,6 +98,7 @@ impl ComponentTypesBuilder { /// them directly to the callee with no additional effort. pub(super) fn async_start_signature( &self, + core_types: &mut CoreTypes, lower: &AdapterOptions, lift: &AdapterOptions, ) -> Signature { @@ -71,7 +110,9 @@ impl ComponentTypesBuilder { MAX_FLAT_PARAMS }; let params = match self.flatten_types( + core_types, &lower.options, + SignaturePart::Params, max_flat_params, self[lower_ty.params].types.iter().copied(), ) { @@ -82,7 +123,9 @@ impl ComponentTypesBuilder { let lift_ty = &self[lift.ty]; let lift_ptr_ty = lift.options.data_model.unwrap_memory().ptr(); let results = match self.flatten_types( + core_types, &lift.options, + SignaturePart::Params, // Both sync- and async-lifted functions accept up to this many core // parameters via the stack. The host will call the `async-start` // function (possibly after a backpressure delay), which will @@ -102,6 +145,7 @@ impl ComponentTypesBuilder { pub(super) fn flatten_lowering_types( &self, + core_types: &mut CoreTypes, options: &Options, tys: impl IntoIterator, ) -> Option> { @@ -109,16 +153,19 @@ impl ComponentTypesBuilder { // nothing is actually passed, but otherwise if anything is returned // it's always through memory. let max = if options.async_ { 0 } else { MAX_FLAT_RESULTS }; - self.flatten_types(options, max, tys) + self.flatten_types(core_types, options, SignaturePart::Results, max, tys) } pub(super) fn flatten_lifting_types( &self, + core_types: &mut CoreTypes, options: &Options, tys: impl IntoIterator, ) -> Option> { self.flatten_types( + core_types, options, + SignaturePart::Results, if options.async_ { // Async functions return results by calling `task.return`, // which accepts up to `MAX_FLAT_PARAMS` parameters via the @@ -144,14 +191,17 @@ impl ComponentTypesBuilder { /// callee task fully completes (which may happen much later). pub(super) fn async_return_signature( &self, + core_types: &mut CoreTypes, lower: &AdapterOptions, lift: &AdapterOptions, ) -> Signature { let lift_ty = &self[lift.ty]; let lift_ptr_ty = lift.options.data_model.unwrap_memory().ptr(); - let mut params = match self - .flatten_lifting_types(&lift.options, self[lift_ty.results].types.iter().copied()) - { + let mut params = match self.flatten_lifting_types( + core_types, + &lift.options, + self[lift_ty.results].types.iter().copied(), + ) { Some(list) => list, None => { vec![lift_ptr_ty] @@ -171,7 +221,9 @@ impl ComponentTypesBuilder { Vec::new() } else { match self.flatten_types( + core_types, &lower.options, + SignaturePart::Results, MAX_FLAT_RESULTS, lower_result_tys.types.iter().copied(), ) { @@ -195,20 +247,82 @@ impl ComponentTypesBuilder { /// list. pub(super) fn flatten_types( &self, + core_types: &mut CoreTypes, opts: &Options, + which: SignaturePart, max: usize, tys: impl IntoIterator, ) -> Option> { - let mut dst = Vec::new(); - for ty in tys { - for ty in opts.flat_types(&ty, self)? { - if dst.len() == max { - return None; + let tys = tys.into_iter().collect::>(); + + match (&opts.data_model, which) { + // GC never spills, so `max` does not apply and this cannot fail. + (DataModel::Gc {}, SignaturePart::Params | SignaturePart::Results) => { + Some(self.gc_flat_types(core_types, opts, which, tys.len())) + } + (_, _) => { + let mut dst = Vec::new(); + for ty in tys { + for ty in self.flat_val_types(opts, &ty)? { + if dst.len() >= max { + return None; + } + dst.push(ty); + } } - dst.push((*ty).into()); + Some(dst) } } - Some(dst) + } + + /// The core value types that `ty` flattens to under `opts`. + /// + /// Works one type at a time, so it only reports what `FlatType` can + /// describe; see `Options::scalar_flat_types`. + fn flat_val_types(&self, opts: &Options, ty: &InterfaceType) -> Option> { + match opts.scalar_flat_types(ty, self) { + Some(flat) => Some(flat.iter().map(|ty| (*ty).into()).collect()), + + None => match opts.data_model { + // `flatten_types` routes GC parameter and result lists to + // `gc_flat_types`, so only a `SignaturePart::Neither` helper + // lands here, which no GC-supported type reaches today. + DataModel::Gc {} => { + todo!("CM+GC: helper function for a reference-lowered type") + } + DataModel::LinearMemory(_) => None, + }, + } + } + + /// Flatten a parameter or result list under the GC data model. + /// + /// The `core-type` canonical option declares the core signature up front, + /// and validation enforces that it maps 1:1 to the component signature, so + /// the declared types are the flattening. This is the only way to flatten + /// a type that lowers to a reference, which `FlatType` cannot describe. + /// Nothing spills to memory under this data model, so this cannot fail. + /// + /// See https://github.com/WebAssembly/component-model/issues/525. + fn gc_flat_types( + &self, + core_types: &mut CoreTypes, + opts: &Options, + which: SignaturePart, + len: usize, + ) -> Vec { + let sig = self.signature(core_types, opts); + let flat = match which { + SignaturePart::Params => sig.params, + SignaturePart::Results => sig.results, + SignaturePart::Neither => unreachable!("not a parameter or result list"), + }; + assert_eq!( + flat.len(), + len, + "the declared `core-type` must map 1:1 to the component signature", + ); + flat } pub(super) fn align(&self, opts: &LinearMemoryOptions, ty: &InterfaceType) -> u32 { diff --git a/crates/environ/src/fact/trampoline.rs b/crates/environ/src/fact/trampoline.rs index 85a0800173d8..1ba7681b4996 100644 --- a/crates/environ/src/fact/trampoline.rs +++ b/crates/environ/src/fact/trampoline.rs @@ -24,18 +24,22 @@ use crate::component::{ TypeRecordIndex, TypeResourceTableIndex, TypeResultIndex, TypeStreamTableIndex, TypeTupleIndex, TypeVariantIndex, VariantInfo, }; -use crate::fact::signature::Signature; +use crate::fact::signature::{Signature, SignaturePart}; use crate::fact::transcode::Transcoder; use crate::fact::{ AdapterData, Body, Function, FunctionId, Helper, HelperLocation, HelperType, LinearMemoryOptions, Module, Options, }; use crate::prelude::*; -use crate::{FuncIndex, GlobalIndex, IndexType, NUM_COMPONENT_CONTEXT_SLOTS, Trap}; +use crate::{ + FuncIndex, GlobalIndex, IndexType, NUM_COMPONENT_CONTEXT_SLOTS, Trap, WasmStorageType, +}; use std::collections::HashMap; use std::mem; use std::ops::Range; -use wasm_encoder::{BlockType, Catch, Encode, Instruction, Instruction::*, MemArg, ValType}; +use wasm_encoder::{ + BlockType, Catch, Encode, HeapType, Instruction, Instruction::*, MemArg, RefType, ValType, +}; use wasmtime_component_util::{DiscriminantSize, FlagsSize}; use super::DataModel; @@ -83,8 +87,12 @@ pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) { module: &'b mut Module<'a>, adapter: &AdapterData, ) -> (Compiler<'a, 'b>, Signature, Signature) { - let lower_sig = module.types.signature(&adapter.lower); - let lift_sig = module.types.signature(&adapter.lift); + let lower_sig = module + .types + .signature(&mut module.core_types, &adapter.lower.options); + let lift_sig = module + .types + .signature(&mut module.core_types, &adapter.lift.options); let ty = module .core_types .function(&lower_sig.params, &lower_sig.results); @@ -132,9 +140,11 @@ pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) { // This allows the host to delay copying the parameters until the callee // signals readiness by clearing its backpressure flag. let async_start_adapter = |module: &mut Module| { - let sig = module - .types - .async_start_signature(&adapter.lower, &adapter.lift); + let sig = module.types.async_start_signature( + &mut module.core_types, + &adapter.lower, + &adapter.lift, + ); let ty = module.core_types.function(&sig.params, &sig.results); let result = module.funcs.push(Function::new( Some(format!("[async-start]{}", adapter.name)), @@ -156,9 +166,11 @@ pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) { // callee to caller when that intrinsic is called rather than when the // callee task fully completes (which may happen much later). let async_return_adapter = |module: &mut Module| { - let sig = module - .types - .async_return_signature(&adapter.lower, &adapter.lift); + let sig = module.types.async_return_signature( + &mut module.core_types, + &adapter.lower, + &adapter.lift, + ); let ty = module.core_types.function(&sig.params, &sig.results); let result = module.funcs.push(Function::new( Some(format!("[async-return]{}", adapter.name)), @@ -183,6 +195,13 @@ pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) { assert!(module.tunables.concurrency_support); + // The host side holds parameters and results across suspension points, + // where GC values would need rooting, and bakes in the linear memory + // flat param/result limits, which a GC-lowered function cannot spill past. + if adapter.lower.options.data_model.is_gc() || adapter.lift.options.data_model.is_gc() { + todo!("CM+GC: the component-model-async ABI does not support the GC data model"); + } + // All other cases are mediated by the host: the caller and/or callee use // the async ABI, or the callee has an async function type (in which case // it's subject to backpressure and exclusive-entry checks even if both @@ -228,7 +247,13 @@ pub(super) fn compile_helper(module: &mut Module<'_>, result: FunctionId, helper HelperLocation::Stack => { src_flat = module .types - .flatten_types(&helper.src.opts, usize::MAX, [helper.src.ty]) + .flatten_types( + &mut module.core_types, + &helper.src.opts, + SignaturePart::Neither, + usize::MAX, + [helper.src.ty], + ) .unwrap() .iter() .enumerate() @@ -259,7 +284,13 @@ pub(super) fn compile_helper(module: &mut Module<'_>, result: FunctionId, helper HelperLocation::Stack => { dst_flat = module .types - .flatten_types(&helper.dst.opts, usize::MAX, [helper.dst.ty]) + .flatten_types( + &mut module.core_types, + &helper.dst.opts, + SignaturePart::Neither, + usize::MAX, + [helper.dst.ty], + ) .unwrap(); Destination::Stack(&dst_flat, &helper.dst.opts) } @@ -310,7 +341,6 @@ enum Source<'a> { /// This value is stored in a GC struct field described by the `GcStruct` /// structure. - #[allow(dead_code, reason = "CM+GC is still WIP")] Struct(GcStruct<'a>), /// This value is stored in a GC array element described by the `GcArray` @@ -333,7 +363,11 @@ enum Destination<'a> { /// This value is to be placed in a GC struct field described by the /// `GcStruct` structure. - #[allow(dead_code, reason = "CM+GC is still WIP")] + /// + /// The value is left on the operand stack for the enclosing `struct.new`, + /// so `translate_*` emits no store. This is distinct from `Stack` because + /// `stack_set` cannot describe a field: it panics on references, and a + /// packed `i8`/`i16` field has no `ValType`. Struct(GcStruct<'a>), /// This value is to be placed in a GC array element described by the @@ -374,7 +408,13 @@ impl<'a> Memory<'a> { /// Representation of where a value is coming from or going to in a GC struct. struct GcStruct<'a> { opts: &'a Options, - // TODO: more fields to come in the future. + /// The local holding the struct reference, or `None` for a destination + /// whose `struct.new` has not run yet. + local: Option, + /// The adapter-local type index of the struct type being accessed. + ty: u32, + /// The field of the struct being accessed. + field: u32, } /// Representation of where a value is coming from or going to in a GC array. @@ -496,7 +536,7 @@ impl<'a, 'b> Compiler<'a, 'b> { &adapter.name, &lower_sig.params, match adapter.lift.options.data_model { - DataModel::Gc {} => todo!("CM+GC"), + DataModel::Gc {} => unreachable!("GC + async is rejected in `compile`"), DataModel::LinearMemory(LinearMemoryOptions { memory, .. }) => memory.map(|m| m.0), }, ); @@ -525,19 +565,19 @@ impl<'a, 'b> Compiler<'a, 'b> { // depending on `prepare_sync` let result_types = &self.types[self.types[adapter.lower.ty].results].types; if prepare_sync { - self.instruction(I32Const( - i32::try_from( - self.types - .flatten_types( - &adapter.lower.options, - usize::MAX, - result_types.iter().copied(), - ) - .map(|v| v.len()) - .unwrap_or(usize::try_from(i32::MAX).unwrap()), + // A spill to linear memory is signalled with this sentinel. + let flat_results = self + .types + .flatten_types( + &mut self.module.core_types, + &adapter.lower.options, + SignaturePart::Results, + usize::MAX, + result_types.iter().copied(), ) - .unwrap(), - )); + .map(|v| v.len()) + .unwrap_or(usize::try_from(i32::MAX).unwrap()); + self.instruction(I32Const(i32::try_from(flat_results).unwrap())); } else { if result_types.len() > 0 { self.instruction(I32Const(PREPARE_ASYNC_WITH_RESULT.cast_signed())); @@ -839,16 +879,24 @@ impl<'a, 'b> Compiler<'a, 'b> { DataModel::LinearMemory(_) if adapter.lower.options.async_ => MAX_FLAT_ASYNC_PARAMS, DataModel::LinearMemory(_) => MAX_FLAT_PARAMS, }; - let src_flat = - self.types - .flatten_types(lower_opts, max_flat_params, src_tys.iter().copied()); + let src_flat = self.types.flatten_types( + &mut self.module.core_types, + lower_opts, + SignaturePart::Params, + max_flat_params, + src_tys.iter().copied(), + ); let max_dst_flat_params = match lift_opts.data_model { DataModel::Gc {} => usize::MAX, DataModel::LinearMemory(_) => MAX_FLAT_PARAMS, }; - let dst_flat = - self.types - .flatten_types(lift_opts, max_dst_flat_params, dst_tys.iter().copied()); + let dst_flat = self.types.flatten_types( + &mut self.module.core_types, + lift_opts, + SignaturePart::Params, + max_dst_flat_params, + dst_tys.iter().copied(), + ); let src = if let Some(flat) = &src_flat { Source::Stack(Stack { @@ -921,12 +969,16 @@ impl<'a, 'b> Compiler<'a, 'b> { let lift_opts = &adapter.lift.options; let lower_opts = &adapter.lower.options; - let src_flat = self - .types - .flatten_lifting_types(lift_opts, src_tys.iter().copied()); - let dst_flat = self - .types - .flatten_lowering_types(lower_opts, dst_tys.iter().copied()); + let src_flat = self.types.flatten_lifting_types( + &mut self.module.core_types, + lift_opts, + src_tys.iter().copied(), + ); + let dst_flat = self.types.flatten_lowering_types( + &mut self.module.core_types, + lower_opts, + dst_tys.iter().copied(), + ); let src = if src_flat.is_some() { Source::Stack(Stack { @@ -1153,7 +1205,7 @@ impl<'a, 'b> Compiler<'a, 'b> { Source::Stack(stack) => { for (i, ty) in stack .opts - .flat_types(src_ty, self.types) + .scalar_flat_types(src_ty, self.types) .unwrap() .iter() .enumerate() @@ -1213,7 +1265,13 @@ impl<'a, 'b> Compiler<'a, 'b> { if let Destination::Stack(tys, opts) = dst { let flat = self .types - .flatten_types(opts, usize::MAX, [*dst_ty]) + .flatten_types( + &mut self.module.core_types, + opts, + SignaturePart::Neither, + usize::MAX, + [*dst_ty], + ) .unwrap(); assert_eq!(flat.len(), tys.len()); let locals = flat @@ -1251,14 +1309,16 @@ impl<'a, 'b> Compiler<'a, 'b> { match src { Source::Memory(mem) => self.i32_load8u(mem), Source::Stack(stack) => self.stack_get(stack, ValType::I32), - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => self.struct_get_u(s), + Source::Array(_) => todo!("CM+GC"), } self.instruction(Select); match dst { Destination::Memory(mem) => self.i32_store8(mem), Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } } @@ -1279,7 +1339,11 @@ impl<'a, 'b> Compiler<'a, 'b> { Source::Stack(stack) => { self.stack_get(stack, ValType::I32); } - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => { + self.struct_get_u(s); + needs_mask = mask != 0xff; + } + Source::Array(_) => todo!("CM+GC"), } if needs_mask { self.instruction(I32Const(i32::from(mask))); @@ -1288,7 +1352,8 @@ impl<'a, 'b> Compiler<'a, 'b> { match dst { Destination::Memory(mem) => self.i32_store8(mem), Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } } @@ -1302,12 +1367,14 @@ impl<'a, 'b> Compiler<'a, 'b> { self.stack_get(stack, ValType::I32); self.instruction(I32Extend8S); } - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => self.struct_get_s(s), + Source::Array(_) => todo!("CM+GC"), } match dst { Destination::Memory(mem) => self.i32_store8(mem), Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } } @@ -1328,7 +1395,11 @@ impl<'a, 'b> Compiler<'a, 'b> { Source::Stack(stack) => { self.stack_get(stack, ValType::I32); } - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => { + self.struct_get_u(s); + needs_mask = mask != 0xffff; + } + Source::Array(_) => todo!("CM+GC"), } if needs_mask { self.instruction(I32Const(i32::from(mask))); @@ -1337,7 +1408,8 @@ impl<'a, 'b> Compiler<'a, 'b> { match dst { Destination::Memory(mem) => self.i32_store16(mem), Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } } @@ -1351,12 +1423,14 @@ impl<'a, 'b> Compiler<'a, 'b> { self.stack_get(stack, ValType::I32); self.instruction(I32Extend16S); } - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => self.struct_get_s(s), + Source::Array(_) => todo!("CM+GC"), } match dst { Destination::Memory(mem) => self.i32_store16(mem), Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } } @@ -1371,7 +1445,8 @@ impl<'a, 'b> Compiler<'a, 'b> { match src { Source::Memory(mem) => self.i32_load(mem), Source::Stack(stack) => self.stack_get(stack, ValType::I32), - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => self.struct_get(s), + Source::Array(_) => todo!("CM+GC"), } if mask != 0xffffffff { self.instruction(I32Const(mask as i32)); @@ -1380,7 +1455,8 @@ impl<'a, 'b> Compiler<'a, 'b> { match dst { Destination::Memory(mem) => self.i32_store(mem), Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } } @@ -1391,12 +1467,14 @@ impl<'a, 'b> Compiler<'a, 'b> { match src { Source::Memory(mem) => self.i32_load(mem), Source::Stack(stack) => self.stack_get(stack, ValType::I32), - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => self.struct_get(s), + Source::Array(_) => todo!("CM+GC"), } match dst { Destination::Memory(mem) => self.i32_store(mem), Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } } @@ -1407,12 +1485,14 @@ impl<'a, 'b> Compiler<'a, 'b> { match src { Source::Memory(mem) => self.i64_load(mem), Source::Stack(stack) => self.stack_get(stack, ValType::I64), - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => self.struct_get(s), + Source::Array(_) => todo!("CM+GC"), } match dst { Destination::Memory(mem) => self.i64_store(mem), Destination::Stack(stack, _) => self.stack_set(stack, ValType::I64), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } } @@ -1423,12 +1503,14 @@ impl<'a, 'b> Compiler<'a, 'b> { match src { Source::Memory(mem) => self.i64_load(mem), Source::Stack(stack) => self.stack_get(stack, ValType::I64), - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => self.struct_get(s), + Source::Array(_) => todo!("CM+GC"), } match dst { Destination::Memory(mem) => self.i64_store(mem), Destination::Stack(stack, _) => self.stack_set(stack, ValType::I64), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } } @@ -1439,12 +1521,14 @@ impl<'a, 'b> Compiler<'a, 'b> { match src { Source::Memory(mem) => self.f32_load(mem), Source::Stack(stack) => self.stack_get(stack, ValType::F32), - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => self.struct_get(s), + Source::Array(_) => todo!("CM+GC"), } match dst { Destination::Memory(mem) => self.f32_store(mem), Destination::Stack(stack, _) => self.stack_set(stack, ValType::F32), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } } @@ -1455,12 +1539,14 @@ impl<'a, 'b> Compiler<'a, 'b> { match src { Source::Memory(mem) => self.f64_load(mem), Source::Stack(stack) => self.stack_get(stack, ValType::F64), - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => self.struct_get(s), + Source::Array(_) => todo!("CM+GC"), } match dst { Destination::Memory(mem) => self.f64_store(mem), Destination::Stack(stack, _) => self.stack_set(stack, ValType::F64), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } } @@ -1469,7 +1555,8 @@ impl<'a, 'b> Compiler<'a, 'b> { match src { Source::Memory(mem) => self.i32_load(mem), Source::Stack(stack) => self.stack_get(stack, ValType::I32), - Source::Struct(_) | Source::Array(_) => todo!("CM+GC"), + Source::Struct(s) => self.struct_get(s), + Source::Array(_) => todo!("CM+GC"), } let local = self.local_set_new_tmp(ValType::I32); @@ -1513,7 +1600,8 @@ impl<'a, 'b> Compiler<'a, 'b> { self.i32_store(mem); } Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32), - Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"), + Destination::Struct(_) => {} + Destination::Array(_) => todo!("CM+GC"), } self.free_temp_local(local); @@ -2996,14 +3084,16 @@ impl<'a, 'b> Compiler<'a, 'b> { assert_eq!(src_ty.types.len(), dst_ty.types.len()); let srcs = src - .record_field_srcs(self.types, src_ty.types.iter().copied()) + .aggregate_field_srcs(self.types, src_ty.types.iter().copied()) .zip(src_ty.types.iter()); - let dsts = dst - .record_field_dsts(self.types, dst_ty.types.iter().copied()) - .zip(dst_ty.types.iter()); - for ((src, src_ty), (dst, dst_ty)) in srcs.zip(dsts) { + let (dsts, dst_struct) = dst.aggregate_field_dsts(self.types, dst_ty.types.iter().copied()); + for ((src, src_ty), (dst, dst_ty)) in srcs.zip(dsts.zip(dst_ty.types.iter())) { self.translate(src_ty, &src, dst_ty, &dst); } + + if let Some(ty) = dst_struct { + self.instruction(StructNew(ty)); + } } fn translate_fixed_length_list( @@ -4302,8 +4392,75 @@ impl<'a, 'b> Compiler<'a, 'b> { } fn push_dst_addr(&mut self, dst: &Destination) { - if let Destination::Memory(mem) = dst { - self.instruction(LocalGet(mem.addr.idx)); + match dst { + Destination::Memory(mem) => self.instruction(LocalGet(mem.addr.idx)), + + // No address to push; see `Destination::Struct`. + Destination::Struct(_) | Destination::Stack(..) | Destination::Array(_) => {} + } + } + + /// Read the field `s` describes, which must not be packed. + /// + /// Aggregates use storage types, so `bool`/`s8`/`u8` live in an `i8` field + /// and `s16`/`u16` in an `i16`. Those widen via `struct_get_s`/`_u`. + /// + /// See https://github.com/WebAssembly/component-model/issues/525. + /// Read the field `s` describes, which must not be packed. + /// + /// Aggregates use storage types, so `bool`/`s8`/`u8` live in an `i8` field + /// and `s16`/`u16` in an `i16`. Those widen via `struct_get_s`/`_u`. + /// + /// See https://github.com/WebAssembly/component-model/issues/525. + fn struct_get(&mut self, s: &GcStruct<'_>) { + assert!( + !self.gc_field_is_packed(s), + "a packed field must widen via `struct_get_s` or `struct_get_u`", + ); + self.push_struct_ref(s); + self.instruction(StructGet { + struct_type_index: s.ty, + field_index: s.field, + }); + } + + fn struct_get_s(&mut self, s: &GcStruct<'_>) { + if !self.gc_field_is_packed(s) { + return self.struct_get(s); + } + self.push_struct_ref(s); + self.instruction(StructGetS { + struct_type_index: s.ty, + field_index: s.field, + }); + } + + fn struct_get_u(&mut self, s: &GcStruct<'_>) { + if !self.gc_field_is_packed(s) { + return self.struct_get(s); + } + self.push_struct_ref(s); + self.instruction(StructGetU { + struct_type_index: s.ty, + field_index: s.field, + }); + } + + fn push_struct_ref(&mut self, s: &GcStruct<'_>) { + self.instruction(LocalGet( + s.local.expect("source struct's local always exists"), + )); + } + + /// Whether the field `s` describes has a packed (`i8`/`i16`) storage type. + fn gc_field_is_packed(&self, s: &GcStruct<'_>) -> bool { + let types = self.types.module_types_builder().types(); + let ty = self.module.core_types.module_type_of(s.ty); + let field = &types[ty].composite_type.inner.unwrap_struct().fields + [usize::try_from(s.field).unwrap()]; + match field.element_type { + WasmStorageType::I8 | WasmStorageType::I16 => true, + WasmStorageType::Val(_) => false, } } @@ -4370,21 +4527,83 @@ impl<'a> Source<'a> { 'a: 'b, { let mut offset = 0; - fields.into_iter().map(move |ty| match self { - Source::Memory(mem) => { - let mem = next_field_offset(&mut offset, types, &ty, mem); - Source::Memory(mem) - } + fields + .into_iter() + .map(move |ty| self.field_src(&mut offset, types, &ty)) + } + + /// Advances `offset` past the field. + fn field_src( + &self, + offset: &mut u32, + types: &ComponentTypesBuilder, + ty: &InterfaceType, + ) -> Source<'a> { + match self { + Source::Memory(mem) => Source::Memory(next_field_offset(offset, types, ty, mem)), Source::Stack(stack) => { - let cnt = types.flat_types(&ty).unwrap().len() as u32; - offset += cnt; - Source::Stack(stack.slice((offset - cnt) as usize..offset as usize)) + let cnt = u32::try_from(stack.opts.flat_count(ty, types)).unwrap(); + *offset += cnt; + let start = usize::try_from(*offset - cnt).unwrap(); + let end = usize::try_from(*offset).unwrap(); + Source::Stack(stack.slice(start..end)) + } + Source::Struct(s) => { + let field = *offset; + *offset += 1; + Source::Struct(GcStruct { field, ..*s }) } - Source::Struct(_) => todo!(), - Source::Array(_) => todo!(), + Source::Array(_) => todo!("CM+GC"), + } + } + + /// Like `record_field_srcs`, but for a value that is itself an aggregate + /// rather than a parameter list. + /// + /// Differs only under GC, where the value is one local holding a struct + /// reference, so its fields are that struct's fields. + fn aggregate_field_srcs<'b>( + &'b self, + types: &'b ComponentTypesBuilder, + fields: impl IntoIterator + 'b, + ) -> impl Iterator> + 'b + where + 'a: 'b, + { + let gc_struct = self.as_gc_aggregate(); + let mut offset = 0; + fields.into_iter().map(move |ty| { + if let Some(s) = &gc_struct { + let field = offset; + offset += 1; + return Source::Struct(GcStruct { field, ..*s }); + } + self.field_src(&mut offset, types, &ty) }) } + /// If this source is a GC-lowered aggregate, the struct its fields live in. + fn as_gc_aggregate(&self) -> Option> { + match self { + Source::Stack(stack) if stack.opts.data_model.is_gc() => { + assert_eq!(stack.locals.len(), 1); + let (idx, ty) = stack.locals[0]; + Some(GcStruct { + opts: stack.opts, + local: Some(idx), + ty: gc_struct_type_index(&ty), + field: 0, + }) + } + Source::Stack(_) | Source::Memory(_) => None, + + // A nested aggregate, which would itself need translating. + Source::Struct(s) if s.opts.data_model.is_gc() => todo!("CM+GC"), + Source::Struct(_) => None, + Source::Array(_) => todo!("CM+GC"), + } + } + /// Returns the corresponding discriminant source and payload source f fn payload_src( &self, @@ -4434,19 +4653,93 @@ impl<'a> Destination<'a> { I: IntoIterator + 'b, { let mut offset = 0; - fields.into_iter().map(move |ty| match self { + fields + .into_iter() + .map(move |ty| self.field_dst(&mut offset, types, &ty)) + } + + /// Same as `Source::aggregate_field_srcs` but for destinations. + /// + /// Also returns the struct type for the caller to `struct.new` once every + /// field is pushed, if this is a GC destination. + fn aggregate_field_dsts<'b, I>( + &'b self, + types: &'b ComponentTypesBuilder, + fields: I, + ) -> ( + impl Iterator> + use<'b, I>, + Option, + ) + where + 'a: 'b, + I: IntoIterator + 'b, + { + let gc_struct = self.as_gc_aggregate(); + let gc_struct_ty = gc_struct.as_ref().map(|s| s.ty); + let mut offset = 0; + let iter = fields.into_iter().map(move |ty| { + if let Some(s) = &gc_struct { + let field = offset; + offset += 1; + return Destination::Struct(GcStruct { field, ..*s }); + } + self.field_dst(&mut offset, types, &ty) + }); + (iter, gc_struct_ty) + } + + /// Advances `offset` past the field. + fn field_dst<'b>( + &'b self, + offset: &mut u32, + types: &ComponentTypesBuilder, + ty: &InterfaceType, + ) -> Destination<'b> + where + 'a: 'b, + { + match self { Destination::Memory(mem) => { - let mem = next_field_offset(&mut offset, types, &ty, mem); - Destination::Memory(mem) + Destination::Memory(next_field_offset(offset, types, ty, mem)) } Destination::Stack(s, opts) => { - let cnt = types.flat_types(&ty).unwrap().len() as u32; - offset += cnt; - Destination::Stack(&s[(offset - cnt) as usize..offset as usize], opts) + let cnt = u32::try_from(opts.flat_count(ty, types)).unwrap(); + *offset += cnt; + let start = usize::try_from(*offset - cnt).unwrap(); + let end = usize::try_from(*offset).unwrap(); + Destination::Stack(&s[start..end], opts) } - Destination::Struct(_) => todo!(), - Destination::Array(_) => todo!(), - }) + Destination::Struct(s) => { + let field = *offset; + *offset += 1; + Destination::Struct(GcStruct { field, ..*s }) + } + Destination::Array(_) => todo!("CM+GC"), + } + } + + /// If this destination is a GC-lowered aggregate, the struct to build. + /// + /// Unlike `Source::as_gc_aggregate` the struct does not exist yet, so + /// there is no local to read from. + fn as_gc_aggregate(&self) -> Option> { + match self { + Destination::Stack(stack, opts) if opts.data_model.is_gc() => { + assert_eq!(stack.len(), 1); + Some(GcStruct { + opts, + local: None, + ty: gc_struct_type_index(&stack[0]), + field: 0, + }) + } + Destination::Stack(..) | Destination::Memory(_) => None, + + // A nested aggregate, as in `Source::as_gc_aggregate`. + Destination::Struct(s) if s.opts.data_model.is_gc() => todo!("CM+GC"), + Destination::Struct(_) => None, + Destination::Array(_) => todo!("CM+GC"), + } } /// Returns the corresponding discriminant source and payload source f @@ -4486,6 +4779,20 @@ impl<'a> Destination<'a> { } } +/// The adapter-local struct type index a GC-lowered aggregate names. +fn gc_struct_type_index(ty: &ValType) -> u32 { + match ty { + ValType::Ref(RefType { + heap_type: HeapType::Concrete(idx), + .. + }) => *idx, + + // Validation has already checked the lowering against the declared + // `core-type`, so anything else is a bug rather than invalid input. + _ => panic!("GC-lowered aggregate must be a concrete struct reference, found {ty:?}"), + } +} + fn next_field_offset<'a>( offset: &mut u32, types: &ComponentTypesBuilder, diff --git a/crates/environ/src/module_types.rs b/crates/environ/src/module_types.rs index 79af3abde9bb..2755f08045bf 100644 --- a/crates/environ/src/module_types.rs +++ b/crates/environ/src/module_types.rs @@ -1,6 +1,6 @@ use crate::{ - ModuleInternedRecGroupIndex, ModuleInternedTypeIndex, PanicOnOom as _, TypeTrace, WasmSubType, - packed_option::PackedOption, prelude::*, + EntityRef as _, ModuleInternedRecGroupIndex, ModuleInternedTypeIndex, PanicOnOom as _, + TypeTrace, WasmSubType, packed_option::PackedOption, prelude::*, }; use core::ops::{Index, Range}; use serde_derive::{Deserialize, Serialize}; @@ -62,6 +62,36 @@ impl ModuleTypes { self.rec_groups.iter().map(|(k, v)| (k, v.clone())) } + /// Get the rec group that `ty` was defined within. + /// + /// Groups are pushed in the same order as their types, so their ranges + /// partition the type index space in increasing order and can be binary + /// searched. + pub fn rec_group_of(&self, ty: ModuleInternedTypeIndex) -> ModuleInternedRecGroupIndex { + let index = self + .rec_groups + .as_values_slice() + .binary_search_by(|range| { + if range.end <= ty { + core::cmp::Ordering::Less + } else if range.start > ty { + core::cmp::Ordering::Greater + } else { + core::cmp::Ordering::Equal + } + }) + .unwrap_or_else(|_| panic!("{ty:?} is not a member of any rec group")); + ModuleInternedRecGroupIndex::new(index) + } + + /// Get the range of type indices that an already-defined rec group spans. + pub fn rec_group_range( + &self, + rec_group: ModuleInternedRecGroupIndex, + ) -> Range { + self.rec_groups[rec_group].clone() + } + /// Get the elements within an already-defined rec group. pub fn rec_group_elements( &self, diff --git a/crates/environ/src/types.rs b/crates/environ/src/types.rs index d08f0885fff6..cfe1ee01371a 100644 --- a/crates/environ/src/types.rs +++ b/crates/environ/src/types.rs @@ -605,6 +605,34 @@ impl TypeTrace for WasmHeapType { } impl WasmHeapType { + /// Get the type index this heap type refers to, if it is a concrete + /// (non-abstract) heap type. + #[inline] + pub fn as_concrete_type_index(&self) -> Option { + match *self { + Self::ConcreteArray(i) + | Self::ConcreteFunc(i) + | Self::ConcreteStruct(i) + | Self::ConcreteCont(i) + | Self::ConcreteExn(i) => Some(i), + + Self::Extern + | Self::NoExtern + | Self::Func + | Self::NoFunc + | Self::Exn + | Self::NoExn + | Self::Cont + | Self::NoCont + | Self::Any + | Self::Eq + | Self::I31 + | Self::Array + | Self::Struct + | Self::None => None, + } + } + /// Is this a type that is represented as a `VMGcRef`? #[inline] pub fn is_vmgcref_type(&self) -> bool { diff --git a/tests/misc_testsuite/component-model/gc/floats.wast b/tests/misc_testsuite/component-model/gc/floats.wast index ed3329399447..224b06f23532 100644 --- a/tests/misc_testsuite/component-model/gc/floats.wast +++ b/tests/misc_testsuite/component-model/gc/floats.wast @@ -5,11 +5,6 @@ ;; `f32` and `f64` lower to core `f32` and `f64` values. ;; ;; See https://github.com/WebAssembly/component-model/issues/525. -;; -;; The `wast` harness cannot pass a bare float to a component function: a -;; top-level `(f32.const ...)` always parses as a core value. So these tests -;; hand the value across as a bit pattern, reinterpret it inside wasm, send -;; the float through the adapter, and report whether it came back bit-exact. ;; `f32` @@ -31,33 +26,28 @@ (core module $m (import "" "f" (func $f (param f32) (result f32))) - ;; Round-trip the bit pattern and report whether it survived exactly. - (func (export "g") (param i32) (result i32) - local.get 0 - f32.reinterpret_i32 - call $f - i32.reinterpret_f32 + (func (export "g") (param f32) (result f32) local.get 0 - i32.eq) + call $f) ) (core instance $i (instantiate $m (with "" (instance (export "f" (func $l)))))) - (func (export "g") (param "a" s32) (result bool) + (func (export "g") (param "a" f32) (result f32) (canon lift (core func $i "g") gc)) ) ;; +/-0.0, +/-1.5, the smallest subnormal, the largest finite value, ;; +/-inf, a quiet NaN, and a signaling NaN. -(assert_return (invoke "g" (s32.const 0x00000000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x80000000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x3fc00000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0xbfc00000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x00000001)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7f7fffff)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7f800000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0xff800000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7fc00000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7f800001)) (bool.const true)) +(assert_return (invoke "g" (f32.const 0.0)) (f32.const 0.0)) +(assert_return (invoke "g" (f32.const -0.0)) (f32.const -0.0)) +(assert_return (invoke "g" (f32.const 1.5)) (f32.const 1.5)) +(assert_return (invoke "g" (f32.const -1.5)) (f32.const -1.5)) +(assert_return (invoke "g" (f32.const 0x1p-149)) (f32.const 0x1p-149)) +(assert_return (invoke "g" (f32.const 0x1.fffffep+127)) (f32.const 0x1.fffffep+127)) +(assert_return (invoke "g" (f32.const inf)) (f32.const inf)) +(assert_return (invoke "g" (f32.const -inf)) (f32.const -inf)) +(assert_return (invoke "g" (f32.const nan:0x400000)) (f32.const nan:0x400000)) +(assert_return (invoke "g" (f32.const nan:0x1)) (f32.const nan:0x1)) ;; A GC-lowered caller calling a linear-memory-lifted callee. (component @@ -78,33 +68,28 @@ (core module $m (import "" "f" (func $f (param f32) (result f32))) - ;; Round-trip the bit pattern and report whether it survived exactly. - (func (export "g") (param i32) (result i32) + (func (export "g") (param f32) (result f32) local.get 0 - f32.reinterpret_i32 - call $f - i32.reinterpret_f32 - local.get 0 - i32.eq) + call $f) ) (core instance $i (instantiate $m (with "" (instance (export "f" (func $l)))))) - (func (export "g") (param "a" s32) (result bool) + (func (export "g") (param "a" f32) (result f32) (canon lift (core func $i "g") gc)) ) ;; +/-0.0, +/-1.5, the smallest subnormal, the largest finite value, ;; +/-inf, a quiet NaN, and a signaling NaN. -(assert_return (invoke "g" (s32.const 0x00000000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x80000000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x3fc00000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0xbfc00000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x00000001)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7f7fffff)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7f800000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0xff800000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7fc00000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7f800001)) (bool.const true)) +(assert_return (invoke "g" (f32.const 0.0)) (f32.const 0.0)) +(assert_return (invoke "g" (f32.const -0.0)) (f32.const -0.0)) +(assert_return (invoke "g" (f32.const 1.5)) (f32.const 1.5)) +(assert_return (invoke "g" (f32.const -1.5)) (f32.const -1.5)) +(assert_return (invoke "g" (f32.const 0x1p-149)) (f32.const 0x1p-149)) +(assert_return (invoke "g" (f32.const 0x1.fffffep+127)) (f32.const 0x1.fffffep+127)) +(assert_return (invoke "g" (f32.const inf)) (f32.const inf)) +(assert_return (invoke "g" (f32.const -inf)) (f32.const -inf)) +(assert_return (invoke "g" (f32.const nan:0x400000)) (f32.const nan:0x400000)) +(assert_return (invoke "g" (f32.const nan:0x1)) (f32.const nan:0x1)) ;; A linear-memory-lowered caller calling a GC-lifted callee. (component @@ -124,33 +109,28 @@ (core module $m (import "" "f" (func $f (param f32) (result f32))) (memory (export "m") 1) - ;; Round-trip the bit pattern and report whether it survived exactly. - (func (export "g") (param i32) (result i32) - local.get 0 - f32.reinterpret_i32 - call $f - i32.reinterpret_f32 + (func (export "g") (param f32) (result f32) local.get 0 - i32.eq) + call $f) ) (core instance $i (instantiate $m (with "" (instance (export "f" (func $l)))))) - (func (export "g") (param "a" s32) (result bool) + (func (export "g") (param "a" f32) (result f32) (canon lift (core func $i "g") (memory (core memory $i "m")))) ) ;; +/-0.0, +/-1.5, the smallest subnormal, the largest finite value, ;; +/-inf, a quiet NaN, and a signaling NaN. -(assert_return (invoke "g" (s32.const 0x00000000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x80000000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x3fc00000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0xbfc00000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x00000001)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7f7fffff)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7f800000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0xff800000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7fc00000)) (bool.const true)) -(assert_return (invoke "g" (s32.const 0x7f800001)) (bool.const true)) +(assert_return (invoke "g" (f32.const 0.0)) (f32.const 0.0)) +(assert_return (invoke "g" (f32.const -0.0)) (f32.const -0.0)) +(assert_return (invoke "g" (f32.const 1.5)) (f32.const 1.5)) +(assert_return (invoke "g" (f32.const -1.5)) (f32.const -1.5)) +(assert_return (invoke "g" (f32.const 0x1p-149)) (f32.const 0x1p-149)) +(assert_return (invoke "g" (f32.const 0x1.fffffep+127)) (f32.const 0x1.fffffep+127)) +(assert_return (invoke "g" (f32.const inf)) (f32.const inf)) +(assert_return (invoke "g" (f32.const -inf)) (f32.const -inf)) +(assert_return (invoke "g" (f32.const nan:0x400000)) (f32.const nan:0x400000)) +(assert_return (invoke "g" (f32.const nan:0x1)) (f32.const nan:0x1)) ;; `f64` @@ -172,33 +152,30 @@ (core module $m (import "" "f" (func $f (param f64) (result f64))) - ;; Round-trip the bit pattern and report whether it survived exactly. - (func (export "g") (param i64) (result i32) + (func (export "g") (param f64) (result f64) local.get 0 - f64.reinterpret_i64 - call $f - i64.reinterpret_f64 - local.get 0 - i64.eq) + call $f) ) (core instance $i (instantiate $m (with "" (instance (export "f" (func $l)))))) - (func (export "g") (param "a" s64) (result bool) + (func (export "g") (param "a" f64) (result f64) (canon lift (core func $i "g") gc)) ) ;; +/-0.0, +/-1.5, the smallest subnormal, the largest finite value, ;; +/-inf, a quiet NaN, and a signaling NaN. -(assert_return (invoke "g" (s64.const 0x0000000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x8000000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x3ff8000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0xbff8000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x0000000000000001)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7fefffffffffffff)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7ff0000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0xfff0000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7ff8000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7ff0000000000001)) (bool.const true)) +(assert_return (invoke "g" (f64.const 0.0)) (f64.const 0.0)) +(assert_return (invoke "g" (f64.const -0.0)) (f64.const -0.0)) +(assert_return (invoke "g" (f64.const 1.5)) (f64.const 1.5)) +(assert_return (invoke "g" (f64.const -1.5)) (f64.const -1.5)) +(assert_return (invoke "g" (f64.const 0x1p-1074)) (f64.const 0x1p-1074)) +(assert_return + (invoke "g" (f64.const 0x1.fffffffffffffp+1023)) + (f64.const 0x1.fffffffffffffp+1023)) +(assert_return (invoke "g" (f64.const inf)) (f64.const inf)) +(assert_return (invoke "g" (f64.const -inf)) (f64.const -inf)) +(assert_return (invoke "g" (f64.const nan:0x8000000000000)) (f64.const nan:0x8000000000000)) +(assert_return (invoke "g" (f64.const nan:0x1)) (f64.const nan:0x1)) ;; A GC-lowered caller calling a linear-memory-lifted callee. (component @@ -219,33 +196,30 @@ (core module $m (import "" "f" (func $f (param f64) (result f64))) - ;; Round-trip the bit pattern and report whether it survived exactly. - (func (export "g") (param i64) (result i32) - local.get 0 - f64.reinterpret_i64 - call $f - i64.reinterpret_f64 + (func (export "g") (param f64) (result f64) local.get 0 - i64.eq) + call $f) ) (core instance $i (instantiate $m (with "" (instance (export "f" (func $l)))))) - (func (export "g") (param "a" s64) (result bool) + (func (export "g") (param "a" f64) (result f64) (canon lift (core func $i "g") gc)) ) ;; +/-0.0, +/-1.5, the smallest subnormal, the largest finite value, ;; +/-inf, a quiet NaN, and a signaling NaN. -(assert_return (invoke "g" (s64.const 0x0000000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x8000000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x3ff8000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0xbff8000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x0000000000000001)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7fefffffffffffff)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7ff0000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0xfff0000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7ff8000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7ff0000000000001)) (bool.const true)) +(assert_return (invoke "g" (f64.const 0.0)) (f64.const 0.0)) +(assert_return (invoke "g" (f64.const -0.0)) (f64.const -0.0)) +(assert_return (invoke "g" (f64.const 1.5)) (f64.const 1.5)) +(assert_return (invoke "g" (f64.const -1.5)) (f64.const -1.5)) +(assert_return (invoke "g" (f64.const 0x1p-1074)) (f64.const 0x1p-1074)) +(assert_return + (invoke "g" (f64.const 0x1.fffffffffffffp+1023)) + (f64.const 0x1.fffffffffffffp+1023)) +(assert_return (invoke "g" (f64.const inf)) (f64.const inf)) +(assert_return (invoke "g" (f64.const -inf)) (f64.const -inf)) +(assert_return (invoke "g" (f64.const nan:0x8000000000000)) (f64.const nan:0x8000000000000)) +(assert_return (invoke "g" (f64.const nan:0x1)) (f64.const nan:0x1)) ;; A linear-memory-lowered caller calling a GC-lifted callee. (component @@ -265,30 +239,27 @@ (core module $m (import "" "f" (func $f (param f64) (result f64))) (memory (export "m") 1) - ;; Round-trip the bit pattern and report whether it survived exactly. - (func (export "g") (param i64) (result i32) - local.get 0 - f64.reinterpret_i64 - call $f - i64.reinterpret_f64 + (func (export "g") (param f64) (result f64) local.get 0 - i64.eq) + call $f) ) (core instance $i (instantiate $m (with "" (instance (export "f" (func $l)))))) - (func (export "g") (param "a" s64) (result bool) + (func (export "g") (param "a" f64) (result f64) (canon lift (core func $i "g") (memory (core memory $i "m")))) ) ;; +/-0.0, +/-1.5, the smallest subnormal, the largest finite value, ;; +/-inf, a quiet NaN, and a signaling NaN. -(assert_return (invoke "g" (s64.const 0x0000000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x8000000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x3ff8000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0xbff8000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x0000000000000001)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7fefffffffffffff)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7ff0000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0xfff0000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7ff8000000000000)) (bool.const true)) -(assert_return (invoke "g" (s64.const 0x7ff0000000000001)) (bool.const true)) +(assert_return (invoke "g" (f64.const 0.0)) (f64.const 0.0)) +(assert_return (invoke "g" (f64.const -0.0)) (f64.const -0.0)) +(assert_return (invoke "g" (f64.const 1.5)) (f64.const 1.5)) +(assert_return (invoke "g" (f64.const -1.5)) (f64.const -1.5)) +(assert_return (invoke "g" (f64.const 0x1p-1074)) (f64.const 0x1p-1074)) +(assert_return + (invoke "g" (f64.const 0x1.fffffffffffffp+1023)) + (f64.const 0x1.fffffffffffffp+1023)) +(assert_return (invoke "g" (f64.const inf)) (f64.const inf)) +(assert_return (invoke "g" (f64.const -inf)) (f64.const -inf)) +(assert_return (invoke "g" (f64.const nan:0x8000000000000)) (f64.const nan:0x8000000000000)) +(assert_return (invoke "g" (f64.const nan:0x1)) (f64.const nan:0x1)) diff --git a/tests/misc_testsuite/component-model/gc/tuples.wast b/tests/misc_testsuite/component-model/gc/tuples.wast new file mode 100644 index 000000000000..4b91b16d3b7f --- /dev/null +++ b/tests/misc_testsuite/component-model/gc/tuples.wast @@ -0,0 +1,408 @@ +;;! component_model_gc = true +;;! gc = true + +;; Tests for the component model `tuple` type under the GC data model, where a +;; tuple lowers to a `(ref null (struct ...))` whose fields are the lowerings +;; of the tuple's element types. +;; +;; Note that the tuples here never cross the host boundary, because lifting a +;; GC-lowered type into a host value is not implemented yet. Each test instead +;; builds its tuple inside a core module and reads a scalar back out. +;; +;; See https://github.com/WebAssembly/component-model/issues/525. + +;; Both sides use the GC data model. +(component + (component $A + (core module $m + (type $t (struct (field (mut i32)) (field (mut i32)))) + (func (export "f") (param (ref null $t)) (result (ref null $t)) + local.get 0) + ) + (core instance $i (instantiate $m)) + (func (export "f") (param "a" (tuple u32 u32)) (result (tuple u32 u32)) + (canon lift (core func $i "f") gc)) + ) + (instance $a (instantiate $A)) + (alias export $a "f" (func $af)) + + (core type $t (struct (field (mut i32)) (field (mut i32)))) + (core type $sig (func (param (ref null $t)) (result (ref null $t)))) + (core func $l (canon lower (func $af) gc (core-type $sig))) + + (core module $m + (type $t (struct (field (mut i32)) (field (mut i32)))) + (import "" "f" (func $f (param (ref null $t)) (result (ref null $t)))) + (func (export "g") (param i32 i32) (result i32) + local.get 0 + local.get 1 + struct.new $t + call $f + struct.get $t 0) + (func (export "h") (param i32 i32) (result i32) + local.get 0 + local.get 1 + struct.new $t + call $f + struct.get $t 1) + ) + (core instance $i (instantiate $m (with "" (instance (export "f" (func $l)))))) + + (func (export "g") (param "a" u32) (param "b" u32) (result u32) + (canon lift (core func $i "g") gc)) + (func (export "h") (param "a" u32) (param "b" u32) (result u32) + (canon lift (core func $i "h") gc)) +) + +(assert_return (invoke "g" (u32.const 1) (u32.const 2)) (u32.const 1)) +(assert_return (invoke "h" (u32.const 1) (u32.const 2)) (u32.const 2)) + +;; A single-element tuple. +(component + (component $A + (core module $m + (type $t (struct (field (mut i32)))) + (func (export "f") (param (ref null $t)) (result (ref null $t)) + local.get 0) + ) + (core instance $i (instantiate $m)) + (func (export "f") (param "a" (tuple u32)) (result (tuple u32)) + (canon lift (core func $i "f") gc)) + ) + (instance $a (instantiate $A)) + (alias export $a "f" (func $af)) + + (core type $t (struct (field (mut i32)))) + (core type $sig (func (param (ref null $t)) (result (ref null $t)))) + (core func $l (canon lower (func $af) gc (core-type $sig))) + + (core module $m + (type $t (struct (field (mut i32)))) + (import "" "f" (func $f (param (ref null $t)) (result (ref null $t)))) + (func (export "g") (param i32) (result i32) + local.get 0 + struct.new $t + call $f + struct.get $t 0) + ) + (core instance $i (instantiate $m (with "" (instance (export "f" (func $l)))))) + + (func (export "g") (param "a" u32) (result u32) + (canon lift (core func $i "g") gc)) +) + +(assert_return (invoke "g" (u32.const 42)) (u32.const 42)) + +;; A tuple of mixed widths, including immutable fields. +(component + (component $A + (core module $m + (type $t (struct (field i8) (field (mut i64)) (field f64))) + (func (export "f") (param (ref null $t)) (result (ref null $t)) + local.get 0) + ) + (core instance $i (instantiate $m)) + (func (export "f") (param "a" (tuple u8 u64 f64)) (result (tuple u8 u64 f64)) + (canon lift (core func $i "f") gc)) + ) + (instance $a (instantiate $A)) + (alias export $a "f" (func $af)) + + (core type $t (struct (field i8) (field (mut i64)) (field f64))) + (core type $sig (func (param (ref null $t)) (result (ref null $t)))) + (core func $l (canon lower (func $af) gc (core-type $sig))) + + (core module $m + (type $t (struct (field i8) (field (mut i64)) (field f64))) + (import "" "f" (func $f (param (ref null $t)) (result (ref null $t)))) + (func $make (param i32 i64 f64) (result (ref null $t)) + local.get 0 + local.get 1 + local.get 2 + struct.new $t + call $f) + (func (export "g") (param i32 i64 f64) (result i32) + local.get 0 + local.get 1 + local.get 2 + call $make + struct.get_u $t 0) + (func (export "h") (param i32 i64 f64) (result i64) + local.get 0 + local.get 1 + local.get 2 + call $make + struct.get $t 1) + (func (export "i") (param i32 i64 f64) (result f64) + local.get 0 + local.get 1 + local.get 2 + call $make + struct.get $t 2) + ) + (core instance $i (instantiate $m (with "" (instance (export "f" (func $l)))))) + + (func (export "g") (param "a" u32) (param "b" u64) (param "c" f64) (result u32) + (canon lift (core func $i "g") gc)) + (func (export "h") (param "a" u32) (param "b" u64) (param "c" f64) (result u64) + (canon lift (core func $i "h") gc)) + (func (export "i") (param "a" u32) (param "b" u64) (param "c" f64) (result f64) + (canon lift (core func $i "i") gc)) +) + +(assert_return (invoke "g" (u32.const 0xff) (u64.const 0) (f64.const 0)) (u32.const 0xff)) +(assert_return + (invoke "h" (u32.const 0) (u64.const 0xfedcba9876543210) (f64.const 0)) + (u64.const 0xfedcba9876543210)) +(assert_return + (invoke "i" (u32.const 0) (u64.const 0) (f64.const 1.5)) + (f64.const 1.5)) +(assert_return + (invoke "i" (u32.const 0) (u64.const 0) (f64.const nan:0x4000000000000)) + (f64.const nan:0x4000000000000)) + +;; A GC-lowered caller calling a linear-memory-lifted callee. The tuple's +;; fields use packed storage types, and the callee widens each one to an +;; `i32`, which is where the choice between `struct.get_s` and `struct.get_u` +;; becomes observable. +(component + (component $A + (core module $m + (memory (export "m") 1) + (func (export "f") (param i32 i32) (result i32) local.get 0) + (func (export "f2") (param i32 i32) (result i32) local.get 1) + ) + (core instance $i (instantiate $m)) + (func (export "f") (param "a" (tuple s8 s16)) (result s32) + (canon lift (core func $i "f") (memory (core memory $i "m")))) + (func (export "f2") (param "a" (tuple s8 s16)) (result s32) + (canon lift (core func $i "f2") (memory (core memory $i "m")))) + ) + (instance $a (instantiate $A)) + (alias export $a "f" (func $af)) + (alias export $a "f2" (func $af2)) + + (core type $t (struct (field (mut i8)) (field (mut i16)))) + (core type $sig (func (param (ref null $t)) (result i32))) + (core func $l (canon lower (func $af) gc (core-type $sig))) + (core func $l2 (canon lower (func $af2) gc (core-type $sig))) + + (core module $m + (type $t (struct (field (mut i8)) (field (mut i16)))) + (import "" "f" (func $f (param (ref null $t)) (result i32))) + (import "" "f2" (func $f2 (param (ref null $t)) (result i32))) + (func (export "g") (param i32 i32) (result i32) + local.get 0 + local.get 1 + struct.new $t + call $f) + (func (export "h") (param i32 i32) (result i32) + local.get 0 + local.get 1 + struct.new $t + call $f2) + ) + (core instance $i (instantiate $m + (with "" (instance (export "f" (func $l)) (export "f2" (func $l2)))))) + + (func (export "g") (param "a" s32) (param "b" s32) (result s32) + (canon lift (core func $i "g") gc)) + (func (export "h") (param "a" s32) (param "b" s32) (result s32) + (canon lift (core func $i "h") gc)) +) + +(assert_return (invoke "g" (s32.const -1) (s32.const -2)) (s32.const -1)) +(assert_return (invoke "h" (s32.const -1) (s32.const -2)) (s32.const -2)) +(assert_return (invoke "g" (s32.const -128) (s32.const -32768)) (s32.const -128)) +(assert_return (invoke "h" (s32.const -128) (s32.const -32768)) (s32.const -32768)) + +;; Unsigned packed fields are zero-extended rather than sign-extended. +(component + (component $A + (core module $m + (memory (export "m") 1) + (func (export "f") (param i32 i32) (result i32) local.get 0) + (func (export "f2") (param i32 i32) (result i32) local.get 1) + ) + (core instance $i (instantiate $m)) + (func (export "f") (param "a" (tuple u8 u16)) (result u32) + (canon lift (core func $i "f") (memory (core memory $i "m")))) + (func (export "f2") (param "a" (tuple u8 u16)) (result u32) + (canon lift (core func $i "f2") (memory (core memory $i "m")))) + ) + (instance $a (instantiate $A)) + (alias export $a "f" (func $af)) + (alias export $a "f2" (func $af2)) + + (core type $t (struct (field (mut i8)) (field (mut i16)))) + (core type $sig (func (param (ref null $t)) (result i32))) + (core func $l (canon lower (func $af) gc (core-type $sig))) + (core func $l2 (canon lower (func $af2) gc (core-type $sig))) + + (core module $m + (type $t (struct (field (mut i8)) (field (mut i16)))) + (import "" "f" (func $f (param (ref null $t)) (result i32))) + (import "" "f2" (func $f2 (param (ref null $t)) (result i32))) + (func (export "g") (param i32 i32) (result i32) + local.get 0 + local.get 1 + struct.new $t + call $f) + (func (export "h") (param i32 i32) (result i32) + local.get 0 + local.get 1 + struct.new $t + call $f2) + ) + (core instance $i (instantiate $m + (with "" (instance (export "f" (func $l)) (export "f2" (func $l2)))))) + + (func (export "g") (param "a" u32) (param "b" u32) (result u32) + (canon lift (core func $i "g") gc)) + (func (export "h") (param "a" u32) (param "b" u32) (result u32) + (canon lift (core func $i "h") gc)) +) + +(assert_return (invoke "g" (u32.const 0xff) (u32.const 0xffff)) (u32.const 0xff)) +(assert_return (invoke "h" (u32.const 0xff) (u32.const 0xffff)) (u32.const 0xffff)) + +;; A linear-memory-lowered caller calling a GC-lifted callee. +(component + (component $A + (core module $m + (type $t (struct (field (mut i32)) (field (mut i32)))) + (func (export "f") (param (ref null $t)) (result i32) + local.get 0 + struct.get $t 1) + ) + (core instance $i (instantiate $m)) + (func (export "f") (param "a" (tuple u32 u32)) (result u32) + (canon lift (core func $i "f") gc)) + ) + (instance $a (instantiate $A)) + (alias export $a "f" (func $af)) + + (core func $l (canon lower (func $af))) + + (core module $m + (import "" "f" (func $f (param i32 i32) (result i32))) + (memory (export "m") 1) + (func (export "g") (param i32 i32) (result i32) + local.get 0 + local.get 1 + call $f) + ) + (core instance $i (instantiate $m (with "" (instance (export "f" (func $l)))))) + + (func (export "g") (param "a" u32) (param "b" u32) (result u32) + (canon lift (core func $i "g") (memory (core memory $i "m")))) +) + +(assert_return (invoke "g" (u32.const 3) (u32.const 4)) (u32.const 4)) + +;; The tuple's struct type sits in a multi-member rec group, alongside members +;; that are unrelated to it and that refer to each other. Core Wasm +;; canonicalizes types at whole-rec-group granularity, so the adapter must +;; clone the entire group, preserving member order, for its struct type to be +;; the same type as the caller's and the callee's. +(component + (component $A + (core module $m + (rec + (type $a (struct (field (ref null $b)))) + (type $t (struct (field (mut i32)) (field (mut i32)))) + (type $b (struct (field (ref null $a)))) + ) + (func (export "f") (param (ref null $t)) (result (ref null $t)) + local.get 0) + ) + (core instance $i (instantiate $m)) + (func (export "f") (param "a" (tuple u32 u32)) (result (tuple u32 u32)) + (canon lift (core func $i "f") gc)) + ) + (instance $a (instantiate $A)) + (alias export $a "f" (func $af)) + + (core rec + (type $a (struct (field (ref null $b)))) + (type $t (struct (field (mut i32)) (field (mut i32)))) + (type $b (struct (field (ref null $a)))) + ) + (core type $sig (func (param (ref null $t)) (result (ref null $t)))) + (core func $l (canon lower (func $af) gc (core-type $sig))) + + (core module $m + (rec + (type $a (struct (field (ref null $b)))) + (type $t (struct (field (mut i32)) (field (mut i32)))) + (type $b (struct (field (ref null $a)))) + ) + (import "" "f" (func $f (param (ref null $t)) (result (ref null $t)))) + (func (export "g") (param i32 i32) (result i32) + local.get 0 + local.get 1 + struct.new $t + call $f + struct.get $t 1) + ) + (core instance $i (instantiate $m (with "" (instance (export "f" (func $l)))))) + + (func (export "g") (param "a" u32) (param "b" u32) (result u32) + (canon lift (core func $i "g") gc)) +) + +(assert_return (invoke "g" (u32.const 7) (u32.const 9)) (u32.const 9)) + +;; A GC adapter with no params and no results, and one with a tuple param and +;; no result. Validation requires the declared `core-type` to have exactly as +;; many parameters as the component function, and at most one result, so these +;; exercise the empty ends of that 1:1 mapping. +(component + (component $A + (core module $m + (type $t (struct (field (mut i32)))) + (global $g (export "g") (mut i32) (i32.const 0)) + (func (export "f")) + (func (export "f2") (param (ref null $t)) + local.get 0 + struct.get $t 0 + global.set $g) + ) + (core instance $i (instantiate $m)) + (func (export "f") (canon lift (core func $i "f") gc)) + (func (export "f2") (param "a" (tuple u32)) + (canon lift (core func $i "f2") gc)) + ) + (instance $a (instantiate $A)) + (alias export $a "f" (func $af)) + (alias export $a "f2" (func $af2)) + + (core type $t (struct (field (mut i32)))) + (core type $sig (func)) + (core type $sig2 (func (param (ref null $t)))) + (core func $l (canon lower (func $af) gc (core-type $sig))) + (core func $l2 (canon lower (func $af2) gc (core-type $sig2))) + + (core module $m + (type $t (struct (field (mut i32)))) + (import "" "f" (func $f)) + (import "" "f2" (func $f2 (param (ref null $t)))) + (func (export "g") (result i32) + call $f + i32.const 11) + (func (export "h") (param i32) (result i32) + local.get 0 + struct.new $t + call $f2 + i32.const 22) + ) + (core instance $i (instantiate $m + (with "" (instance (export "f" (func $l)) (export "f2" (func $l2)))))) + + (func (export "g") (result u32) (canon lift (core func $i "g") gc)) + (func (export "h") (param "a" u32) (result u32) + (canon lift (core func $i "h") gc)) +) + +(assert_return (invoke "g") (u32.const 11)) +(assert_return (invoke "h" (u32.const 3)) (u32.const 22))