From 715a7ac452133206378decdeb317b5ff9d67f2ae Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Wed, 15 Oct 2025 20:29:18 -0400 Subject: [PATCH 01/14] feat(wit): add map type support Signed-off-by: Yordis Prieto --- crates/wasm-compose/src/encoding.rs | 18 ++ crates/wasm-encoder/src/component/types.rs | 7 + crates/wasm-encoder/src/reencode/component.rs | 6 + .../wasmparser/src/readers/component/types.rs | 3 + crates/wasmparser/src/validator/component.rs | 7 + .../src/validator/component_types.rs | 34 ++- crates/wasmprinter/src/component.rs | 15 ++ crates/wit-component/src/encoding.rs | 3 + crates/wit-component/src/encoding/types.rs | 7 + crates/wit-component/src/printing.rs | 38 +++ .../wit-component/tests/interfaces/maps.wat | 186 +++++++++++++ .../wit-component/tests/interfaces/maps.wit | 38 +++ .../tests/interfaces/maps.wit.print | 57 ++++ crates/wit-dylib/ffi/src/ffi.rs | 12 + crates/wit-dylib/ffi/src/lib.rs | 79 ++++++ crates/wit-dylib/ffi/src/types.rs | 52 ++++ crates/wit-dylib/src/bindgen.rs | 249 +++++++++++++++++ crates/wit-dylib/src/lib.rs | 10 + crates/wit-dylib/src/metadata.rs | 27 ++ crates/wit-dylib/test-programs/src/lib.rs | 64 ++++- crates/wit-dylib/wit_dylib.h | 20 ++ crates/wit-encoder/src/from_parser.rs | 7 + crates/wit-encoder/src/ty.rs | 7 + crates/wit-parser/src/abi.rs | 4 + crates/wit-parser/src/ast.rs | 22 ++ crates/wit-parser/src/ast/lex.rs | 3 + crates/wit-parser/src/ast/resolve.rs | 40 +++ crates/wit-parser/src/decoding.rs | 17 ++ crates/wit-parser/src/lib.rs | 6 + crates/wit-parser/src/live.rs | 4 + crates/wit-parser/src/resolve.rs | 8 + crates/wit-parser/src/resolve/clone.rs | 4 + crates/wit-parser/src/sizealign.rs | 3 + crates/wit-parser/tests/ui/maps.wit | 34 +++ crates/wit-parser/tests/ui/maps.wit.json | 255 ++++++++++++++++++ .../tests/ui/parse-fail/map-invalid-key.wit | 7 + .../ui/parse-fail/map-invalid-key.wit.result | 5 + tests/cli/wit-dylib-smoke.wit.stdout | 76 +++--- 38 files changed, 1393 insertions(+), 41 deletions(-) create mode 100644 crates/wit-component/tests/interfaces/maps.wat create mode 100644 crates/wit-component/tests/interfaces/maps.wit create mode 100644 crates/wit-component/tests/interfaces/maps.wit.print create mode 100644 crates/wit-parser/tests/ui/maps.wit create mode 100644 crates/wit-parser/tests/ui/maps.wit.json create mode 100644 crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit create mode 100644 crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit.result diff --git a/crates/wasm-compose/src/encoding.rs b/crates/wasm-compose/src/encoding.rs index 8b8c2a30d8..fd7efad3dc 100644 --- a/crates/wasm-compose/src/encoding.rs +++ b/crates/wasm-compose/src/encoding.rs @@ -656,6 +656,7 @@ impl<'a> TypeEncoder<'a> { ComponentDefinedType::Record(r) => self.record(state, r), ComponentDefinedType::Variant(v) => self.variant(state, v), ComponentDefinedType::List(ty) => self.list(state, *ty), + ComponentDefinedType::Map(key, value) => self.map(state, *key, *value), ComponentDefinedType::FixedSizeList(ty, elements) => { self.fixed_size_list(state, *ty, *elements) } @@ -719,6 +720,19 @@ impl<'a> TypeEncoder<'a> { index } + fn map( + &self, + state: &mut TypeState<'a>, + key: ct::ComponentValType, + value: ct::ComponentValType, + ) -> u32 { + let key = self.component_val_type(state, key); + let value = self.component_val_type(state, value); + let index = state.cur.encodable.type_count(); + state.cur.encodable.ty().defined_type().map(key, value); + index + } + fn fixed_size_list( &self, state: &mut TypeState<'a>, @@ -1259,6 +1273,10 @@ impl DependencyRegistrar<'_, '_> { ComponentDefinedType::List(t) | ComponentDefinedType::FixedSizeList(t, _) | ComponentDefinedType::Option(t) => self.val_type(*t), + ComponentDefinedType::Map(k, v) => { + self.val_type(*k); + self.val_type(*v); + } ComponentDefinedType::Own(r) | ComponentDefinedType::Borrow(r) => { self.ty(ComponentAnyTypeId::Resource(*r)) } diff --git a/crates/wasm-encoder/src/component/types.rs b/crates/wasm-encoder/src/component/types.rs index f9887e0e7e..d44f812aa4 100644 --- a/crates/wasm-encoder/src/component/types.rs +++ b/crates/wasm-encoder/src/component/types.rs @@ -620,6 +620,13 @@ impl ComponentDefinedTypeEncoder<'_> { ty.into().encode(self.0); } + /// Define a map type. + pub fn map(self, key: impl Into, value: impl Into) { + self.0.push(0x63); + key.into().encode(self.0); + value.into().encode(self.0); + } + /// Define a fixed size list type. pub fn fixed_size_list(self, ty: impl Into, elements: u32) { self.0.push(0x67); diff --git a/crates/wasm-encoder/src/reencode/component.rs b/crates/wasm-encoder/src/reencode/component.rs index a06d8b96a4..8c2cd60665 100644 --- a/crates/wasm-encoder/src/reencode/component.rs +++ b/crates/wasm-encoder/src/reencode/component.rs @@ -769,6 +769,12 @@ pub mod component_utils { wasmparser::ComponentDefinedType::List(t) => { defined.list(reencoder.component_val_type(t)); } + wasmparser::ComponentDefinedType::Map(k, v) => { + defined.map( + reencoder.component_val_type(k), + reencoder.component_val_type(v), + ); + } wasmparser::ComponentDefinedType::FixedSizeList(t, elements) => { defined.fixed_size_list(reencoder.component_val_type(t), elements); } diff --git a/crates/wasmparser/src/readers/component/types.rs b/crates/wasmparser/src/readers/component/types.rs index b87c26da92..629b76c873 100644 --- a/crates/wasmparser/src/readers/component/types.rs +++ b/crates/wasmparser/src/readers/component/types.rs @@ -446,6 +446,8 @@ pub enum ComponentDefinedType<'a> { Variant(Box<[VariantCase<'a>]>), /// The type is a list of the given value type. List(ComponentValType), + /// The type is a map of the given key and value types. + Map(ComponentValType, ComponentValType), /// The type is a fixed size list of the given value type. FixedSizeList(ComponentValType, u32), /// The type is a tuple of the given value types. @@ -487,6 +489,7 @@ impl<'a> ComponentDefinedType<'a> { .collect::>()?, ), 0x70 => ComponentDefinedType::List(reader.read()?), + 0x63 => ComponentDefinedType::Map(reader.read()?, reader.read()?), 0x6f => ComponentDefinedType::Tuple( reader .read_iter(MAX_WASM_TUPLE_TYPES, "tuple types")? diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index 6a21481602..cae44f044d 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -951,6 +951,9 @@ impl ComponentState { ComponentDefinedType::List(ty) | ComponentDefinedType::FixedSizeList(ty, _) | ComponentDefinedType::Option(ty) => types.type_named_valtype(ty, set), + ComponentDefinedType::Map(k, v) => { + types.type_named_valtype(k, set) && types.type_named_valtype(v, set) + } // The resource referred to by own/borrow must be named. ComponentDefinedType::Own(id) | ComponentDefinedType::Borrow(id) => { @@ -3917,6 +3920,10 @@ impl ComponentState { crate::ComponentDefinedType::List(ty) => Ok(ComponentDefinedType::List( self.create_component_val_type(ty, offset)?, )), + crate::ComponentDefinedType::Map(key, value) => Ok(ComponentDefinedType::Map( + self.create_component_val_type(key, offset)?, + self.create_component_val_type(value, offset)?, + )), crate::ComponentDefinedType::FixedSizeList(ty, elements) => { if !self.features.cm_fixed_size_list() { bail!( diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs index 19bd8f21dc..bc0fa7e5f2 100644 --- a/crates/wasmparser/src/validator/component_types.rs +++ b/crates/wasmparser/src/validator/component_types.rs @@ -1468,6 +1468,8 @@ pub enum ComponentDefinedType { Variant(VariantType), /// The type is a list. List(ComponentValType), + /// The type is a map. + Map(ComponentValType, ComponentValType), /// The type is a fixed size list. FixedSizeList(ComponentValType, u32), /// The type is a tuple. @@ -1511,6 +1513,11 @@ impl TypeData for ComponentDefinedType { Self::Variant(v) => v.info, Self::Tuple(t) => t.info, Self::List(ty) | Self::FixedSizeList(ty, _) | Self::Option(ty) => ty.info(types), + Self::Map(k, v) => { + let mut info = k.info(types); + info.combine(v.info(types), 0).unwrap(); + info + } Self::Result { ok, err } => { let default = TypeInfo::new(); let mut info = ok.map(|ty| ty.type_info(types)).unwrap_or(default); @@ -1531,7 +1538,7 @@ impl ComponentDefinedType { .cases .values() .any(|case| case.ty.map(|ty| ty.contains_ptr(types)).unwrap_or(false)), - Self::List(_) => true, + Self::List(_) | Self::Map(_, _) => true, Self::Tuple(t) => t.types.iter().any(|ty| ty.contains_ptr(types)), Self::Flags(_) | Self::Enum(_) @@ -1559,7 +1566,7 @@ impl ComponentDefinedType { types, lowered_types, ), - Self::List(_) => { + Self::List(_) | Self::Map(_, _) => { lowered_types.try_push(ValType::I32) && lowered_types.try_push(ValType::I32) } Self::FixedSizeList(ty, length) => { @@ -1639,6 +1646,7 @@ impl ComponentDefinedType { ComponentDefinedType::Flags(_) => "flags", ComponentDefinedType::Option(_) => "option", ComponentDefinedType::List(_) => "list", + ComponentDefinedType::Map(_, _) => "map", ComponentDefinedType::FixedSizeList(_, _) => "fixed size list", ComponentDefinedType::Result { .. } => "result", ComponentDefinedType::Own(_) => "own", @@ -1663,7 +1671,9 @@ impl ComponentDefinedType { ComponentDefinedType::Variant(ty) => ty.lower_gc(types, abi, options, offset, core), - ComponentDefinedType::List(ty) | ComponentDefinedType::FixedSizeList(ty, _) => { + ComponentDefinedType::List(ty) + | ComponentDefinedType::Map(ty, _) + | ComponentDefinedType::FixedSizeList(ty, _) => { let id = match core.as_concrete_ref() { Some(id) => id, None => bail!( @@ -2515,6 +2525,10 @@ impl TypeAlloc { | ComponentDefinedType::Option(ty) => { self.free_variables_valtype(ty, set); } + ComponentDefinedType::Map(k, v) => { + self.free_variables_valtype(k, set); + self.free_variables_valtype(v, set); + } ComponentDefinedType::Result { ok, err } => { if let Some(ok) = ok { self.free_variables_valtype(ok, set); @@ -2657,6 +2671,9 @@ impl TypeAlloc { ComponentDefinedType::List(ty) | ComponentDefinedType::FixedSizeList(ty, _) | ComponentDefinedType::Option(ty) => self.type_named_valtype(ty, set), + ComponentDefinedType::Map(k, v) => { + self.type_named_valtype(k, set) && self.type_named_valtype(v, set) + } // own/borrow themselves don't have to be named, but the resource // they refer to must be named. @@ -2849,6 +2866,10 @@ where | ComponentDefinedType::Option(ty) => { any_changed |= self.remap_valtype(ty, map); } + ComponentDefinedType::Map(k, v) => { + any_changed |= self.remap_valtype(k, map); + any_changed |= self.remap_valtype(v, map); + } ComponentDefinedType::Result { ok, err } => { if let Some(ok) = ok { any_changed |= self.remap_valtype(ok, map); @@ -3760,6 +3781,13 @@ impl<'a> SubtypeCx<'a> { (Variant(_), b) => bail!(offset, "expected {}, found variant", b.desc()), (List(a), List(b)) | (Option(a), Option(b)) => self.component_val_type(a, b, offset), (List(_), b) => bail!(offset, "expected {}, found list", b.desc()), + (Map(ak, av), Map(bk, bv)) => { + self.component_val_type(ak, bk, offset) + .with_context(|| "type mismatch in map key")?; + self.component_val_type(av, bv, offset) + .with_context(|| "type mismatch in map value") + } + (Map(_, _), b) => bail!(offset, "expected {}, found map", b.desc()), (FixedSizeList(a, asize), FixedSizeList(b, bsize)) => { if asize != bsize { bail!(offset, "expected fixed size {bsize}, found size {asize}") diff --git a/crates/wasmprinter/src/component.rs b/crates/wasmprinter/src/component.rs index 9003a71de9..1be065fd6b 100644 --- a/crates/wasmprinter/src/component.rs +++ b/crates/wasmprinter/src/component.rs @@ -169,6 +169,20 @@ impl Printer<'_, '_> { Ok(()) } + pub(crate) fn print_map_type( + &mut self, + state: &State, + key_ty: &ComponentValType, + value_ty: &ComponentValType, + ) -> Result<()> { + self.start_group("map ")?; + self.print_component_val_type(state, key_ty)?; + self.result.write_str(" ")?; + self.print_component_val_type(state, value_ty)?; + self.end_group()?; + Ok(()) + } + pub(crate) fn print_fixed_size_list_type( &mut self, state: &State, @@ -278,6 +292,7 @@ impl Printer<'_, '_> { ComponentDefinedType::Record(fields) => self.print_record_type(state, fields)?, ComponentDefinedType::Variant(cases) => self.print_variant_type(state, cases)?, ComponentDefinedType::List(ty) => self.print_list_type(state, ty)?, + ComponentDefinedType::Map(key, value) => self.print_map_type(state, key, value)?, ComponentDefinedType::FixedSizeList(ty, elements) => { self.print_fixed_size_list_type(state, ty, *elements)? } diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index e0d23b554c..0ec2812511 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -327,6 +327,9 @@ impl TypeContents { } TypeDefKind::Enum(_) => Self::empty(), TypeDefKind::List(t) => Self::for_type(resolve, t) | Self::LIST, + TypeDefKind::Map(k, v) => { + Self::for_type(resolve, k) | Self::for_type(resolve, v) | Self::LIST + } TypeDefKind::FixedSizeList(t, _elements) => Self::for_type(resolve, t), TypeDefKind::Type(t) => Self::for_type(resolve, t), TypeDefKind::Future(_) => Self::empty(), diff --git a/crates/wit-component/src/encoding/types.rs b/crates/wit-component/src/encoding/types.rs index 4e2e5c4047..7dc16911c6 100644 --- a/crates/wit-component/src/encoding/types.rs +++ b/crates/wit-component/src/encoding/types.rs @@ -189,6 +189,13 @@ pub trait ValtypeEncoder<'a> { encoder.list(ty); ComponentValType::Type(index) } + TypeDefKind::Map(key_ty, value_ty) => { + let key = self.encode_valtype(resolve, key_ty)?; + let value = self.encode_valtype(resolve, value_ty)?; + let (index, encoder) = self.defined_type(); + encoder.map(key, value); + ComponentValType::Type(index) + } TypeDefKind::FixedSizeList(ty, elements) => { let ty = self.encode_valtype(resolve, ty)?; let (index, encoder) = self.defined_type(); diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index e97e77bfb0..4a260fc00e 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -602,6 +602,14 @@ impl WitPrinter { self.print_type_name(resolve, ty)?; self.output.generic_args_end(); } + TypeDefKind::Map(key_ty, value_ty) => { + self.output.ty("map", TypeKind::BuiltIn); + self.output.generic_args_start(); + self.print_type_name(resolve, key_ty)?; + self.output.str(", "); + self.print_type_name(resolve, value_ty)?; + self.output.generic_args_end(); + } TypeDefKind::FixedSizeList(ty, size) => { self.output.ty("list", TypeKind::BuiltIn); self.output.generic_args_start(); @@ -783,6 +791,9 @@ impl WitPrinter { TypeDefKind::List(inner) => { self.declare_list(resolve, ty.name.as_deref(), inner)? } + TypeDefKind::Map(key, value) => { + self.declare_map(resolve, ty.name.as_deref(), key, value)? + } TypeDefKind::FixedSizeList(inner, size) => { self.declare_fixed_size_list(resolve, ty.name.as_deref(), inner, *size)? } @@ -999,6 +1010,31 @@ impl WitPrinter { Ok(()) } + fn declare_map( + &mut self, + resolve: &Resolve, + name: Option<&str>, + key_ty: &Type, + value_ty: &Type, + ) -> Result<()> { + if let Some(name) = name { + self.output.keyword("type"); + self.output.str(" "); + self.print_name_type(name, TypeKind::Map); + self.output.str(" = "); + self.output.ty("map", TypeKind::BuiltIn); + self.output.str("<"); + self.print_type_name(resolve, key_ty)?; + self.output.str(", "); + self.print_type_name(resolve, value_ty)?; + self.output.str(">"); + self.output.semicolon(); + return Ok(()); + } + + Ok(()) + } + fn declare_fixed_size_list( &mut self, resolve: &Resolve, @@ -1339,6 +1375,8 @@ pub enum TypeKind { InterfacePath, /// A list type name. List, + /// A map type name. + Map, /// A namespace declaration. NamespaceDeclaration, /// A namespace when printing a path, for example in `use`. diff --git a/crates/wit-component/tests/interfaces/maps.wat b/crates/wit-component/tests/interfaces/maps.wat new file mode 100644 index 0000000000..91e2f862aa --- /dev/null +++ b/crates/wit-component/tests/interfaces/maps.wat @@ -0,0 +1,186 @@ +(component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (map bool string)) + (export (;1;) "bool-map" (type (eq 0))) + (type (;2;) (map u8 string)) + (export (;3;) "u8-map" (type (eq 2))) + (type (;4;) (map u16 string)) + (export (;5;) "u16-map" (type (eq 4))) + (type (;6;) (map u32 string)) + (export (;7;) "u32-map" (type (eq 6))) + (type (;8;) (map u64 string)) + (export (;9;) "u64-map" (type (eq 8))) + (type (;10;) (map s8 string)) + (export (;11;) "s8-map" (type (eq 10))) + (type (;12;) (map s16 string)) + (export (;13;) "s16-map" (type (eq 12))) + (type (;14;) (map s32 string)) + (export (;15;) "s32-map" (type (eq 14))) + (type (;16;) (map s64 string)) + (export (;17;) "s64-map" (type (eq 16))) + (type (;18;) (map char string)) + (export (;19;) "char-map" (type (eq 18))) + (type (;20;) (map string u32)) + (export (;21;) "string-map" (type (eq 20))) + (type (;22;) (map string bool)) + (export (;23;) "string-to-bool" (type (eq 22))) + (type (;24;) (list u8)) + (type (;25;) (map string 24)) + (export (;26;) "string-to-list" (type (eq 25))) + (type (;27;) (option u32)) + (type (;28;) (map string 27)) + (export (;29;) "string-to-option" (type (eq 28))) + (type (;30;) (result u32 (error string))) + (type (;31;) (map string 30)) + (export (;32;) "string-to-result" (type (eq 31))) + (type (;33;) (tuple u32 string)) + (type (;34;) (map string 33)) + (export (;35;) "string-to-tuple" (type (eq 34))) + (type (;36;) (map string u32)) + (type (;37;) (map string 36)) + (export (;38;) "map-of-maps" (type (eq 37))) + (type (;39;) (list 36)) + (export (;40;) "list-of-maps" (type (eq 39))) + (type (;41;) (option 36)) + (export (;42;) "option-of-map" (type (eq 41))) + (type (;43;) (func (param "x" 36))) + (export (;0;) "map-param" (func (type 43))) + (type (;44;) (func (result 36))) + (export (;1;) "map-result" (func (type 44))) + (type (;45;) (map u32 string)) + (type (;46;) (func (param "x" 45) (result 45))) + (export (;2;) "map-roundtrip" (func (type 46))) + ) + ) + (export (;0;) "foo:maps-test/maps-interface" (instance (type 0))) + ) + ) + (export (;1;) "maps-interface" (type 0)) + (type (;2;) + (component + (type (;0;) + (component + (type (;0;) + (instance + (type (;0;) (map bool string)) + (export (;1;) "bool-map" (type (eq 0))) + (type (;2;) (map u8 string)) + (export (;3;) "u8-map" (type (eq 2))) + (type (;4;) (map u16 string)) + (export (;5;) "u16-map" (type (eq 4))) + (type (;6;) (map u32 string)) + (export (;7;) "u32-map" (type (eq 6))) + (type (;8;) (map u64 string)) + (export (;9;) "u64-map" (type (eq 8))) + (type (;10;) (map s8 string)) + (export (;11;) "s8-map" (type (eq 10))) + (type (;12;) (map s16 string)) + (export (;13;) "s16-map" (type (eq 12))) + (type (;14;) (map s32 string)) + (export (;15;) "s32-map" (type (eq 14))) + (type (;16;) (map s64 string)) + (export (;17;) "s64-map" (type (eq 16))) + (type (;18;) (map char string)) + (export (;19;) "char-map" (type (eq 18))) + (type (;20;) (map string u32)) + (export (;21;) "string-map" (type (eq 20))) + (type (;22;) (map string bool)) + (export (;23;) "string-to-bool" (type (eq 22))) + (type (;24;) (list u8)) + (type (;25;) (map string 24)) + (export (;26;) "string-to-list" (type (eq 25))) + (type (;27;) (option u32)) + (type (;28;) (map string 27)) + (export (;29;) "string-to-option" (type (eq 28))) + (type (;30;) (result u32 (error string))) + (type (;31;) (map string 30)) + (export (;32;) "string-to-result" (type (eq 31))) + (type (;33;) (tuple u32 string)) + (type (;34;) (map string 33)) + (export (;35;) "string-to-tuple" (type (eq 34))) + (type (;36;) (map string u32)) + (type (;37;) (map string 36)) + (export (;38;) "map-of-maps" (type (eq 37))) + (type (;39;) (list 36)) + (export (;40;) "list-of-maps" (type (eq 39))) + (type (;41;) (option 36)) + (export (;42;) "option-of-map" (type (eq 41))) + (type (;43;) (func (param "x" 36))) + (export (;0;) "map-param" (func (type 43))) + (type (;44;) (func (result 36))) + (export (;1;) "map-result" (func (type 44))) + (type (;45;) (map u32 string)) + (type (;46;) (func (param "x" 45) (result 45))) + (export (;2;) "map-roundtrip" (func (type 46))) + ) + ) + (import "foo:maps-test/maps-interface" (instance (;0;) (type 0))) + (type (;1;) + (instance + (type (;0;) (map bool string)) + (export (;1;) "bool-map" (type (eq 0))) + (type (;2;) (map u8 string)) + (export (;3;) "u8-map" (type (eq 2))) + (type (;4;) (map u16 string)) + (export (;5;) "u16-map" (type (eq 4))) + (type (;6;) (map u32 string)) + (export (;7;) "u32-map" (type (eq 6))) + (type (;8;) (map u64 string)) + (export (;9;) "u64-map" (type (eq 8))) + (type (;10;) (map s8 string)) + (export (;11;) "s8-map" (type (eq 10))) + (type (;12;) (map s16 string)) + (export (;13;) "s16-map" (type (eq 12))) + (type (;14;) (map s32 string)) + (export (;15;) "s32-map" (type (eq 14))) + (type (;16;) (map s64 string)) + (export (;17;) "s64-map" (type (eq 16))) + (type (;18;) (map char string)) + (export (;19;) "char-map" (type (eq 18))) + (type (;20;) (map string u32)) + (export (;21;) "string-map" (type (eq 20))) + (type (;22;) (map string bool)) + (export (;23;) "string-to-bool" (type (eq 22))) + (type (;24;) (list u8)) + (type (;25;) (map string 24)) + (export (;26;) "string-to-list" (type (eq 25))) + (type (;27;) (option u32)) + (type (;28;) (map string 27)) + (export (;29;) "string-to-option" (type (eq 28))) + (type (;30;) (result u32 (error string))) + (type (;31;) (map string 30)) + (export (;32;) "string-to-result" (type (eq 31))) + (type (;33;) (tuple u32 string)) + (type (;34;) (map string 33)) + (export (;35;) "string-to-tuple" (type (eq 34))) + (type (;36;) (map string u32)) + (type (;37;) (map string 36)) + (export (;38;) "map-of-maps" (type (eq 37))) + (type (;39;) (list 36)) + (export (;40;) "list-of-maps" (type (eq 39))) + (type (;41;) (option 36)) + (export (;42;) "option-of-map" (type (eq 41))) + (type (;43;) (func (param "x" 36))) + (export (;0;) "map-param" (func (type 43))) + (type (;44;) (func (result 36))) + (export (;1;) "map-result" (func (type 44))) + (type (;45;) (map u32 string)) + (type (;46;) (func (param "x" 45) (result 45))) + (export (;2;) "map-roundtrip" (func (type 46))) + ) + ) + (export (;1;) "foo:maps-test/maps-interface" (instance (type 1))) + ) + ) + (export (;0;) "foo:maps-test/maps-test-world" (component (type 0))) + ) + ) + (export (;3;) "maps-test-world" (type 2)) + (@custom "package-docs" "\01{\22interfaces\22:{\22maps-interface\22:{\22funcs\22:{\22map-param\22:{\22docs\22:\22Functions\22}},\22types\22:{\22bool-map\22:{\22docs\22:\22Test all primitive key types\22},\22string-to-bool\22:{\22docs\22:\22Test all value types\22},\22map-of-maps\22:{\22docs\22:\22Nested structures\22}}}}}") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/interfaces/maps.wit b/crates/wit-component/tests/interfaces/maps.wit new file mode 100644 index 0000000000..93bc64a92d --- /dev/null +++ b/crates/wit-component/tests/interfaces/maps.wit @@ -0,0 +1,38 @@ +package foo:maps-test; + +interface maps-interface { + // Test all primitive key types + type bool-map = map; + type u8-map = map; + type u16-map = map; + type u32-map = map; + type u64-map = map; + type s8-map = map; + type s16-map = map; + type s32-map = map; + type s64-map = map; + type char-map = map; + type string-map = map; + + // Test all value types + type string-to-bool = map; + type string-to-list = map>; + type string-to-option = map>; + type string-to-result = map>; + type string-to-tuple = map>; + + // Nested structures + type map-of-maps = map>; + type list-of-maps = list>; + type option-of-map = option>; + + // Functions + map-param: func(x: map); + map-result: func() -> map; + map-roundtrip: func(x: map) -> map; +} + +world maps-test-world { + import maps-interface; + export maps-interface; +} diff --git a/crates/wit-component/tests/interfaces/maps.wit.print b/crates/wit-component/tests/interfaces/maps.wit.print new file mode 100644 index 0000000000..f9235085bc --- /dev/null +++ b/crates/wit-component/tests/interfaces/maps.wit.print @@ -0,0 +1,57 @@ +package foo:maps-test; + +interface maps-interface { + /// Test all primitive key types + type bool-map = map; + + type u8-map = map; + + type u16-map = map; + + type u32-map = map; + + type u64-map = map; + + type s8-map = map; + + type s16-map = map; + + type s32-map = map; + + type s64-map = map; + + type char-map = map; + + type string-map = map; + + /// Test all value types + type string-to-bool = map; + + type string-to-list = map>; + + type string-to-option = map>; + + type string-to-result = map>; + + type string-to-tuple = map>; + + /// Nested structures + type map-of-maps = map>; + + type list-of-maps = list>; + + type option-of-map = option>; + + /// Functions + map-param: func(x: map); + + map-result: func() -> map; + + map-roundtrip: func(x: map) -> map; +} + +world maps-test-world { + import maps-interface; + + export maps-interface; +} diff --git a/crates/wit-dylib/ffi/src/ffi.rs b/crates/wit-dylib/ffi/src/ffi.rs index 9ba95428be..de2607360b 100644 --- a/crates/wit-dylib/ffi/src/ffi.rs +++ b/crates/wit-dylib/ffi/src/ffi.rs @@ -28,6 +28,7 @@ pub const WIT_TYPE_FIXED_SIZE_LIST: u32 = 24; pub const WIT_TYPE_FUTURE: u32 = 25; pub const WIT_TYPE_STREAM: u32 = 26; pub const WIT_TYPE_ALIAS: u32 = 27; +pub const WIT_TYPE_MAP: u32 = 28; pub const WIT_TYPE_EMPTY: u32 = 255; pub const WIT_CURRENT_VERSION: u32 = 2; pub type wit_type_t = u32; @@ -169,6 +170,15 @@ pub struct wit_list { pub type wit_list_t = wit_list; #[repr(C)] #[derive(Debug, Copy, Clone)] +pub struct wit_map { + pub interface: *const ::std::os::raw::c_char, + pub name: *const ::std::os::raw::c_char, + pub key_ty: wit_type_t, + pub value_ty: wit_type_t, +} +pub type wit_map_t = wit_map; +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct wit_fixed_size_list { pub interface: *const ::std::os::raw::c_char, pub name: *const ::std::os::raw::c_char, @@ -280,6 +290,8 @@ pub struct wit { pub results: *const wit_result_t, pub num_lists: usize, pub lists: *const wit_list_t, + pub num_maps: usize, + pub maps: *const wit_map_t, pub num_fixed_size_lists: usize, pub fixed_size_lists: *const wit_fixed_size_list_t, pub num_futures: usize, diff --git a/crates/wit-dylib/ffi/src/lib.rs b/crates/wit-dylib/ffi/src/lib.rs index 5e91f7c6d4..93660eb5f8 100644 --- a/crates/wit-dylib/ffi/src/lib.rs +++ b/crates/wit-dylib/ffi/src/lib.rs @@ -353,6 +353,30 @@ macro_rules! export { unsafe { <$name as $crate::RawInterpreter>::raw_list_append(cx, ty) } } + #[no_mangle] + pub unsafe extern "C" fn wit_dylib_pop_map( + cx: *mut u8, + ty: usize, + ptr: &mut *const u8, + ) -> usize { + unsafe { <$name as $crate::RawInterpreter>::raw_pop_map(cx, ty, ptr) } + } + + #[no_mangle] + pub unsafe extern "C" fn wit_dylib_push_map( + cx: *mut u8, + ty: usize, + ptr: *mut u8, + len: usize, + ) -> u32 { + unsafe { <$name as $crate::RawInterpreter>::raw_push_map(cx, ty, ptr, len) } + } + + #[no_mangle] + pub unsafe extern "C" fn wit_dylib_map_append(cx: *mut u8, ty: usize) { + unsafe { <$name as $crate::RawInterpreter>::raw_map_append(cx, ty) } + } + fn main() { unreachable!(); } @@ -437,6 +461,12 @@ pub trait Call { fn pop_iter_next(&mut self, ty: List); fn pop_iter(&mut self, ty: List); + unsafe fn maybe_pop_map(&mut self, ty: Map) -> Option<(*const u8, usize)> { + let _ = ty; + None + } + fn pop_map(&mut self, ty: Map) -> usize; + fn push_bool(&mut self, val: bool); fn push_char(&mut self, val: char); fn push_u8(&mut self, val: u8); @@ -467,6 +497,13 @@ pub trait Call { } fn push_list(&mut self, ty: List, capacity: usize); fn list_append(&mut self, ty: List); + + unsafe fn push_raw_map(&mut self, ty: Map, ptr: *mut u8, len: usize) -> bool { + let _ = (ty, ptr, len); + false + } + fn push_map(&mut self, ty: Map, capacity: usize); + fn map_append(&mut self, ty: Map); } static mut WIT_T: *const ffi::wit_t = ptr::null_mut(); @@ -959,6 +996,48 @@ pub trait RawInterpreter: Interpreter { Self::cx_mut(cx).list_append(wit.list(ty)) } } + + unsafe fn raw_pop_map(cx: *mut u8, ty: usize, retptr: &mut *const u8) -> usize { + debug_println!("pop_map({cx:?}, {ty})"); + unsafe { + let wit = Wit::from_raw(WIT_T); + let cx = Self::cx_mut(cx); + let ty = wit.map(ty); + match cx.maybe_pop_map(ty) { + Some((ptr, len)) => { + *retptr = ptr; + len + } + None => { + *retptr = ptr::null(); + cx.pop_map(ty) + } + } + } + } + + unsafe fn raw_push_map(cx: *mut u8, ty: usize, map: *mut u8, len: usize) -> u32 { + debug_println!("push_map({cx:?}, {ty}, {map:?}, {len})"); + unsafe { + let wit = Wit::from_raw(WIT_T); + let cx = Self::cx_mut(cx); + let ty = wit.map(ty); + if cx.push_raw_map(ty, map, len) { + 1 + } else { + cx.push_map(ty, len); + 0 + } + } + } + + unsafe fn raw_map_append(cx: *mut u8, ty: usize) { + debug_println!("map_append({cx:?}, {ty})"); + unsafe { + let wit = Wit::from_raw(WIT_T); + Self::cx_mut(cx).map_append(wit.map(ty)) + } + } } impl RawInterpreter for T {} diff --git a/crates/wit-dylib/ffi/src/types.rs b/crates/wit-dylib/ffi/src/types.rs index 1b337d9e55..51b419b515 100644 --- a/crates/wit-dylib/ffi/src/types.rs +++ b/crates/wit-dylib/ffi/src/types.rs @@ -250,6 +250,21 @@ impl Wit { self.raw_lists().iter().map(|e| List { wit: *self, ptr: e }) } + fn raw_maps(&self) -> &'static [ffi::wit_map_t] { + unsafe { slice(self.ptr.maps, self.ptr.num_maps) } + } + + pub fn map(&self, index: usize) -> Map { + Map { + wit: *self, + ptr: &self.raw_maps()[index], + } + } + + pub fn iter_maps(&self) -> impl ExactSizeIterator + Clone + '_ { + self.raw_maps().iter().map(|e| Map { wit: *self, ptr: e }) + } + fn raw_fixed_size_lists(&self) -> &'static [ffi::wit_fixed_size_list_t] { unsafe { slice(self.ptr.fixed_size_lists, self.ptr.num_fixed_size_lists) } } @@ -999,6 +1014,43 @@ impl fmt::Debug for List { } } +#[derive(Copy, Clone)] +pub struct Map { + wit: Wit, + ptr: &'static ffi::wit_map_t, +} + +impl_extra_traits!(Map); + +impl Map { + pub fn interface(&self) -> Option<&'static str> { + unsafe { opt_str(self.ptr.interface) } + } + + pub fn name(&self) -> Option<&'static str> { + unsafe { opt_str(self.ptr.name) } + } + + pub fn key_ty(&self) -> Type { + Type::from_raw(self.wit, self.ptr.key_ty) + } + + pub fn value_ty(&self) -> Type { + Type::from_raw(self.wit, self.ptr.value_ty) + } +} + +impl fmt::Debug for Map { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Map") + .field("interface", &self.interface()) + .field("name", &self.name()) + .field("key_ty", &self.key_ty()) + .field("value_ty", &self.value_ty()) + .finish() + } +} + #[derive(Copy, Clone)] pub struct FixedSizeList { wit: Wit, diff --git a/crates/wit-dylib/src/bindgen.rs b/crates/wit-dylib/src/bindgen.rs index cd8ca366f1..c135d44d73 100644 --- a/crates/wit-dylib/src/bindgen.rs +++ b/crates/wit-dylib/src/bindgen.rs @@ -69,6 +69,7 @@ intrinsics! { pop_list : [ValType::I32; 3] -> [ValType::I32] = "wit_dylib_pop_list", pop_iter_next : [ValType::I32; 2] -> [] = "wit_dylib_pop_iter_next", pop_iter : [ValType::I32; 2] -> [] = "wit_dylib_pop_iter", + pop_map : [ValType::I32; 3] -> [ValType::I32] = "wit_dylib_pop_map", push_bool : [ValType::I32; 2] -> [] = "wit_dylib_push_bool", push_char : [ValType::I32; 2] -> [] = "wit_dylib_push_char", @@ -96,6 +97,8 @@ intrinsics! { push_result : [ValType::I32; 3] -> [] = "wit_dylib_push_result", push_list : [ValType::I32; 4] -> [ValType::I32] = "wit_dylib_push_list", list_append : [ValType::I32; 2] -> [] = "wit_dylib_list_append", + push_map : [ValType::I32; 4] -> [ValType::I32] = "wit_dylib_push_map", + map_append : [ValType::I32; 2] -> [] = "wit_dylib_map_append", } pub fn import( @@ -966,6 +969,137 @@ impl<'a> FunctionCompiler<'a> { // that's returned directly (e.g. `list`) but if it's NULL // then the list is allocated manually with `cabi_realloc` and then // lowered element-by-element. + TypeDefKind::Map(key_ty, value_ty) => { + let metadata::Type::Map(map_index) = ty else { + unreachable!() + }; + let pop_map = self.adapter.intrinsics().pop_map; + let pop_iter_next = self.adapter.intrinsics().pop_iter_next; + let pop_iter = self.adapter.intrinsics().pop_iter; + let cabi_realloc = self.adapter.intrinsics().cabi_realloc; + let dealloc_bytes = self.adapter.intrinsics().dealloc_bytes; + + // Calculate size of tuple + let key_size = self.adapter.sizes.size(key_ty).size_wasm32(); + let key_align = self.adapter.sizes.align(key_ty).align_wasm32(); + let value_size = self.adapter.sizes.size(value_ty).size_wasm32(); + let value_align = self.adapter.sizes.align(value_ty).align_wasm32(); + + // Tuple alignment is max of element alignments + let tuple_align = key_align.max(value_align); + // Tuple size is key_size + padding + value_size + let key_end = key_size; + let value_offset = align_up(key_end, value_align); + let tuple_size = value_offset + value_size; + + let (dst_ptr, dst_len) = dest.split_ptr_len(); + + // Load the map ptr/len into locals + self.local_get_ctx(); + self.ins().i32_const(map_index.try_into().unwrap()); + self.local_get_stack_temp_addr(); + self.ins().call(pop_map); + let m_len = self.local_set_new_tmp(ValType::I32); + self.local_get_stack_temp_addr(); + self.ins().i32_load(MemArg { + memory_index: 0, + offset: 0, + align: 2, + }); + let m_ptr = self.local_set_new_tmp(ValType::I32); + + // If the pointer is null then the interpreter doesn't support + // the same canonical ABI view of this map so a loop is + // required to lower element-by-element. + self.ins().local_get(m_ptr.idx); + self.ins().i32_eqz(); + self.ins().if_(BlockType::Empty); + { + self.ins().i32_const(0); + let m_index = self.local_set_new_tmp(ValType::I32); + + self.ins().i32_const(0); + self.ins().i32_const(0); + self.ins().i32_const(tuple_align.try_into().unwrap()); + self.ins().local_get(m_len.idx); + self.ins().i32_const(tuple_size.try_into().unwrap()); + self.ins().i32_mul(); + let m_byte_size = self.local_tee_new_tmp(ValType::I32); + self.ins().call(cabi_realloc); + let m_elem_addr = self.local_tee_new_tmp(ValType::I32); + self.ins().local_set(m_ptr.idx); + + // Loop over each element of the map. + self.ins().loop_(BlockType::Empty); + { + // Entry loop condition, `m_index != m_len` + self.ins().local_get(m_index.idx); + self.ins().local_get(m_len.idx); + self.ins().i32_ne(); + self.ins().if_(BlockType::Empty); + { + // Get the `m_index`th element from map (puts key then value on stack) + self.local_get_ctx(); + self.ins().i32_const(map_index.try_into().unwrap()); + self.ins().call(pop_iter_next); + + // Lower the key + self.lower( + *key_ty, + &AbiLoc::Memory(Memory { + addr: TempLocal::new(m_elem_addr.idx, ValType::I32), + offset: 0, + }), + ); + + // Lower the value + self.lower( + *value_ty, + &AbiLoc::Memory(Memory { + addr: TempLocal::new(m_elem_addr.idx, ValType::I32), + offset: value_offset.try_into().unwrap(), + }), + ); + + // Increment the `m_index` counter + self.ins().local_get(m_index.idx); + self.ins().i32_const(1); + self.ins().i32_add(); + self.ins().local_set(m_index.idx); + + // Increment the `m_elem_addr` counter + self.ins().local_get(m_elem_addr.idx); + self.ins().i32_const(tuple_size.try_into().unwrap()); + self.ins().i32_add(); + self.ins().local_set(m_elem_addr.idx); + + // Continue the loop. + self.ins().br(1); + } + self.ins().end(); + } + self.ins().end(); + + self.local_get_ctx(); + self.ins().i32_const(map_index.try_into().unwrap()); + self.ins().call(pop_iter); + + self.local_get_ctx(); + self.ins().local_get(m_ptr.idx); + self.ins().local_get(m_byte_size.idx); + self.ins().i32_const(tuple_align.try_into().unwrap()); + self.ins().i32_const(tuple_size.try_into().unwrap()); + self.ins().call(dealloc_bytes); + } + self.ins().end(); + + // Store the ptr/len + self.ins().local_get(m_ptr.idx); + self.store_scalar_from_top_of_stack(&dst_ptr, ValType::I32, 2); + self.ins().local_get(m_len.idx); + self.store_scalar_from_top_of_stack(&dst_len, ValType::I32, 2); + } + TypeDefKind::List(t) => { let metadata::Type::List(list_index) = ty else { unreachable!() @@ -1485,6 +1619,121 @@ impl<'a> FunctionCompiler<'a> { ); } + TypeDefKind::Map(key_ty, value_ty) => { + let metadata::Type::Map(map_index) = ty else { + unreachable!() + }; + + // Calculate size of tuple + let key_size = self.adapter.sizes.size(key_ty).size_wasm32(); + let key_align = self.adapter.sizes.align(key_ty).align_wasm32(); + let value_size = self.adapter.sizes.size(value_ty).size_wasm32(); + let value_align = self.adapter.sizes.align(value_ty).align_wasm32(); + + // Tuple alignment is max of element alignments + let tuple_align = key_align.max(value_align); + // Tuple size is key_size + padding + value_size + let key_end = key_size; + let value_offset = align_up(key_end, value_align); + let tuple_size = value_offset + value_size; + + let map_index = i32::try_from(map_index).unwrap(); + let push_map = i.push_map; + let map_append = i.map_append; + let dealloc_bytes = i.dealloc_bytes; + let (src_ptr, src_len) = src.split_ptr_len(); + + // Give the interpreter to lift the map exactly as-is and take + // ownership of the allocation. + self.local_get_ctx(); + self.ins().i32_const(map_index); + self.load_abi_loc(&src_ptr, ValType::I32, 2); + let m_ptr = self.local_tee_new_tmp(ValType::I32); + self.load_abi_loc(&src_len, ValType::I32, 2); + let m_len = self.local_tee_new_tmp(ValType::I32); + self.ins().call(push_map); + + // If the interpreter returned 0 then the map needs to be + // lifted manually element-by-element. + self.ins().i32_eqz(); + self.ins().if_(BlockType::Empty); + { + // Prep deallocation of the map after the loop by saving + // off the pointer/byte size. + self.ins().local_get(m_len.idx); + self.ins().i32_const(tuple_size.try_into().unwrap()); + self.ins().i32_mul(); + let m_byte_size = self.local_set_new_tmp(ValType::I32); + self.ins().local_get(m_ptr.idx); + let m_ptr_to_free = self.local_set_new_tmp(ValType::I32); + + // Using `m_len` as the loop counter, element-by-element + // lift from the map and push into the map. + self.ins().loop_(BlockType::Empty); + { + self.ins().local_get(m_len.idx); + self.ins().if_(BlockType::Empty); + { + // Lift the key from memory + let key_src = AbiLoc::Memory(Memory { + addr: TempLocal::new(m_ptr.idx, m_ptr.ty), + offset: 0, + }); + self.lift(&key_src, *key_ty); + + // Lift the value from memory + let value_src = AbiLoc::Memory(Memory { + addr: TempLocal::new(m_ptr.idx, m_ptr.ty), + offset: value_offset.try_into().unwrap(), + }); + self.lift(&value_src, *value_ty); + + // Push the lifted key-value pair onto the map. + self.local_get_ctx(); + self.ins().i32_const(map_index); + self.ins().call(map_append); + + // decrement the length counter + self.ins().local_get(m_len.idx); + self.ins().i32_const(1); + self.ins().i32_sub(); + self.ins().local_set(m_len.idx); + + // increment the pointer + self.ins().local_get(m_ptr.idx); + self.ins().i32_const(tuple_size.try_into().unwrap()); + self.ins().i32_add(); + self.ins().local_set(m_ptr.idx); + + self.ins().br(1); + } + self.ins().end(); + } + self.ins().end(); + + // The canonical ABI representation of this map is no + // longer needed, so discard it. + self.ins().local_get(m_byte_size.idx); + self.ins().if_(BlockType::Empty); + { + self.local_get_ctx(); + self.ins().local_get(m_ptr_to_free.idx); + self.ins().local_get(m_byte_size.idx); + self.ins().i32_const(tuple_align.try_into().unwrap()); + self.ins().i32_const(0); + self.ins().call(dealloc_bytes); + } + self.ins().end(); + + self.free_temp_local(m_byte_size); + self.free_temp_local(m_ptr_to_free); + } + self.ins().end(); + + self.free_temp_local(m_ptr); + self.free_temp_local(m_len); + } + TypeDefKind::List(t) => { let metadata::Type::List(list_index) = ty else { unreachable!() diff --git a/crates/wit-dylib/src/lib.rs b/crates/wit-dylib/src/lib.rs index b1671d2228..5badea52ff 100644 --- a/crates/wit-dylib/src/lib.rs +++ b/crates/wit-dylib/src/lib.rs @@ -768,6 +768,16 @@ impl Adapter { }); metadata::Type::List(index) } + TypeDefKind::Map(key_ty, value_ty) => { + let index = self.metadata.maps.len(); + self.metadata.maps.push(metadata::Map { + interface, + name, + key_ty: self.lookup_ty(key_ty), + value_ty: self.lookup_ty(value_ty), + }); + metadata::Type::Map(index) + } TypeDefKind::FixedSizeList(t, len) => { let index = self.metadata.fixed_size_lists.len(); self.metadata diff --git a/crates/wit-dylib/src/metadata.rs b/crates/wit-dylib/src/metadata.rs index 00a8e59785..514ef2b57a 100644 --- a/crates/wit-dylib/src/metadata.rs +++ b/crates/wit-dylib/src/metadata.rs @@ -18,6 +18,7 @@ pub struct Metadata { pub options: Vec, pub results: Vec, pub lists: Vec, + pub maps: Vec, pub fixed_size_lists: Vec, pub futures: Vec, pub streams: Vec, @@ -109,6 +110,13 @@ pub struct List { pub ty: Type, } +pub struct Map { + pub interface: Option, + pub name: Option, + pub key_ty: Type, + pub value_ty: Type, +} + pub struct FixedSizeList { pub id: TypeId, pub interface: Option, @@ -186,6 +194,7 @@ pub enum Type { Option(usize), Result(usize), List(usize), + Map(usize), FixedSizeList(usize), Future(usize), Stream(usize), @@ -242,6 +251,7 @@ impl Metadata { let options = encoder.encode_list(&self.options, Encoder::encode_options); let results = encoder.encode_list(&self.results, Encoder::encode_results); let lists = encoder.encode_list(&self.lists, Encoder::encode_lists); + let maps = encoder.encode_list(&self.maps, Encoder::encode_maps); let fixed_size_lists = encoder.encode_list(&self.fixed_size_lists, Encoder::encode_fixed_size_lists); let futures = encoder.encode_list(&self.futures, Encoder::encode_futures); @@ -263,6 +273,7 @@ impl Metadata { options, results, lists, + maps, fixed_size_lists, futures, streams, @@ -538,6 +549,21 @@ impl Encoder { } } + fn encode_maps(&mut self, maps: &[Map]) { + for map in maps { + let Map { + interface, + name, + key_ty, + value_ty, + } = map; + self.opt_string_ptr(interface.as_deref()); + self.opt_string_ptr(name.as_deref()); + self.ty(key_ty); + self.ty(value_ty); + } + } + fn encode_fixed_size_lists(&mut self, lists: &[FixedSizeList]) { for list in lists { let FixedSizeList { @@ -762,6 +788,7 @@ impl Encoder { Type::Future(i) => index(25, i), Type::Stream(i) => index(26, i), Type::Alias(i) => index(27, i), + Type::Map(i) => index(28, i), }; self.put_u32(val); } diff --git a/crates/wit-dylib/test-programs/src/lib.rs b/crates/wit-dylib/test-programs/src/lib.rs index d154ec8883..fc42e3a185 100644 --- a/crates/wit-dylib/test-programs/src/lib.rs +++ b/crates/wit-dylib/test-programs/src/lib.rs @@ -121,6 +121,8 @@ impl Drop for Cx<'_> { enum CowIter<'a> { Borrowed(std::slice::Iter<'a, Val>), Owned(std::vec::IntoIter), + BorrowedMap(std::slice::Iter<'a, (Val, Val)>), + OwnedMap(std::vec::IntoIter<(Val, Val)>), } impl<'a> Iterator for CowIter<'a> { @@ -130,6 +132,25 @@ impl<'a> Iterator for CowIter<'a> { match self { CowIter::Borrowed(i) => Some(Cow::Borrowed(i.next()?)), CowIter::Owned(i) => Some(Cow::Owned(i.next()?)), + CowIter::BorrowedMap(_) | CowIter::OwnedMap(_) => { + panic!("use next_map_entry for map iterators") + } + } + } +} + +impl<'a> CowIter<'a> { + fn next_map_entry(&mut self) -> Option<(Cow<'a, Val>, Cow<'a, Val>)> { + match self { + CowIter::BorrowedMap(i) => { + let (k, v) = i.next()?; + Some((Cow::Borrowed(k), Cow::Borrowed(v))) + } + CowIter::OwnedMap(i) => { + let (k, v) = i.next()?; + Some((Cow::Owned(k), Cow::Owned(v))) + } + _ => panic!("next_map_entry called on non-map iterator"), } } } @@ -575,8 +596,18 @@ impl Call for Cx<'_> { } fn pop_iter_next(&mut self, _ty: List) { - let value = self.iterators.last_mut().unwrap().next().unwrap(); - self.stack.push(value); + let iter = self.iterators.last_mut().unwrap(); + match iter { + CowIter::BorrowedMap(_) | CowIter::OwnedMap(_) => { + let (key, value) = iter.next_map_entry().unwrap(); + self.stack.push(key); + self.stack.push(value); + } + _ => { + let value = iter.next().unwrap(); + self.stack.push(value); + } + } } fn pop_iter(&mut self, _ty: List) { @@ -604,6 +635,34 @@ impl Call for Cx<'_> { _ => invalid(), } } + + fn pop_map(&mut self, _ty: Map) -> usize { + match self.always_pop() { + Cow::Borrowed(Val::GenericMap(m)) => { + self.iterators.push(CowIter::BorrowedMap(m.iter())); + m.len() + } + Cow::Owned(Val::GenericMap(m)) => { + let ret = m.len(); + self.iterators.push(CowIter::OwnedMap(m.into_iter())); + ret + } + _ => invalid(), + } + } + + fn push_map(&mut self, _ty: Map, capacity: usize) { + self.push_own(Val::GenericMap(Vec::with_capacity(capacity))); + } + + fn map_append(&mut self, _ty: Map) { + let value = self.always_pop().into_owned(); + let key = self.always_pop().into_owned(); + match self.stack.last_mut() { + Some(Cow::Owned(Val::GenericMap(map))) => map.push((key, value)), + _ => invalid(), + } + } } #[derive(Clone, Debug, PartialEq)] @@ -634,6 +693,7 @@ pub enum Val { Variant(u32, Option>), GenericList(Vec), ByteList(Vec), + GenericMap(Vec<(Val, Val)>), } #[derive(Clone, Debug)] diff --git a/crates/wit-dylib/wit_dylib.h b/crates/wit-dylib/wit_dylib.h index 25b2b66e5e..6f97600ea4 100644 --- a/crates/wit-dylib/wit_dylib.h +++ b/crates/wit-dylib/wit_dylib.h @@ -50,6 +50,7 @@ typedef uint32_t wit_type_t; #define WIT_TYPE_FUTURE 25 #define WIT_TYPE_STREAM 26 #define WIT_TYPE_ALIAS 27 +#define WIT_TYPE_MAP 28 #define WIT_TYPE_EMPTY 0xff typedef void(*wit_import_fn_t)(void* cx); @@ -199,6 +200,13 @@ typedef struct wit_list { wit_type_t ty; } wit_list_t; +typedef struct wit_map { + const char *interface; + const char *name; + wit_type_t key_ty; + wit_type_t value_ty; +} wit_map_t; + typedef struct wit_fixed_size_list { const char *interface; const char *name; @@ -292,6 +300,8 @@ typedef struct wit { const wit_result_t *results; size_t num_lists; const wit_list_t *lists; + size_t num_maps; + const wit_map_t *maps; size_t num_fixed_size_lists; const wit_fixed_size_list_t *fixed_size_lists; size_t num_futures; @@ -402,6 +412,11 @@ void wit_dylib_push_variant(void *cx, size_t ty, uint32_t discr); // it onto the list which is then at the top of the stack. bool wit_dylib_push_list(void *cx, size_t ty, uint8_t *bytes, size_t len); void wit_dylib_list_append(void *cx, size_t ty); +// Map functions work similarly to list functions. If `wit_dylib_push_map` returns +// false then key-value pairs need to be pushed one-by-one. The `wit_dylib_map_append` +// function expects the top two stack entries to be value (top) and key (second from top). +bool wit_dylib_push_map(void *cx, size_t ty, uint8_t *bytes, size_t len); +void wit_dylib_map_append(void *cx, size_t ty); uint8_t wit_dylib_pop_u8(void *cx); uint16_t wit_dylib_pop_u16(void *cx); @@ -462,5 +477,10 @@ void wit_dylib_pop_tuple(void *cx, size_t ty); size_t wit_dylib_pop_list(void *cx, size_t ty, void **ptr); void wit_dylib_pop_iter_next(void *cx, size_t ty); void wit_dylib_pop_iter(void *cx, size_t ty); +// Map popping works similarly to list popping. Returns the number of key-value pairs. +// If `ptr` is set to non-NULL, the data is in canonical ABI format (as tuple array). +// If `ptr` is set to NULL, then an iterator is pushed to the stack and +// `wit_dylib_pop_iter_next` should be called to extract each key-value pair. +size_t wit_dylib_pop_map(void *cx, size_t ty, void **ptr); #endif diff --git a/crates/wit-encoder/src/from_parser.rs b/crates/wit-encoder/src/from_parser.rs index f11fa8e040..84d8b62c7c 100644 --- a/crates/wit-encoder/src/from_parser.rs +++ b/crates/wit-encoder/src/from_parser.rs @@ -243,6 +243,10 @@ impl<'a> Converter<'a> { let output = Type::list(self.convert_type(ty)); TypeDefKind::Type(output) } + wit_parser::TypeDefKind::Map(key, value) => { + let output = Type::map(self.convert_type(key), self.convert_type(value)); + TypeDefKind::Type(output) + } wit_parser::TypeDefKind::FixedSizeList(ty, size) => { let output = Type::fixed_size_list(self.convert_type(ty), *size); TypeDefKind::Type(output) @@ -308,6 +312,9 @@ impl<'a> Converter<'a> { wit_parser::TypeDefKind::List(type_) => { Type::list(self.convert_type(type_)) } + wit_parser::TypeDefKind::Map(key, value) => { + Type::map(self.convert_type(key), self.convert_type(value)) + } wit_parser::TypeDefKind::FixedSizeList(type_, size) => { Type::fixed_size_list(self.convert_type(type_), *size) } diff --git a/crates/wit-encoder/src/ty.rs b/crates/wit-encoder/src/ty.rs index 18aa2786d1..f0fdb7d56c 100644 --- a/crates/wit-encoder/src/ty.rs +++ b/crates/wit-encoder/src/ty.rs @@ -26,6 +26,7 @@ pub enum Type { Option(Box), Result(Box), List(Box), + Map(Box, Box), FixedSizeList(Box, u32), Tuple(Tuple), Future(Option>), @@ -59,6 +60,9 @@ impl Type { pub fn list(type_: Type) -> Self { Type::List(Box::new(type_)) } + pub fn map(key: Type, value: Type) -> Self { + Type::Map(Box::new(key), Box::new(value)) + } pub fn fixed_size_list(type_: Type, size: u32) -> Self { Type::FixedSizeList(Box::new(type_), size) } @@ -120,6 +124,9 @@ impl Display for Type { Type::List(type_) => { write!(f, "list<{type_}>") } + Type::Map(key, value) => { + write!(f, "map<{key}, {value}>") + } Type::FixedSizeList(type_, size) => { write!(f, "list<{type_}, {size}>") } diff --git a/crates/wit-parser/src/abi.rs b/crates/wit-parser/src/abi.rs index 3cba9eba95..147cad3f7d 100644 --- a/crates/wit-parser/src/abi.rs +++ b/crates/wit-parser/src/abi.rs @@ -335,6 +335,10 @@ impl Resolve { result.push(WasmType::Pointer) && result.push(WasmType::Length) } + TypeDefKind::Map(_, _) => { + result.push(WasmType::Pointer) && result.push(WasmType::Length) + } + TypeDefKind::FixedSizeList(ty, size) => { self.push_flat_list((0..*size).map(|_| ty), result) } diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs index fba94d0e3e..b59805c2f0 100644 --- a/crates/wit-parser/src/ast.rs +++ b/crates/wit-parser/src/ast.rs @@ -754,6 +754,7 @@ enum Type<'a> { String(Span), Name(Id<'a>), List(List<'a>), + Map(Map<'a>), FixedSizeList(FixedSizeList<'a>), Handle(Handle<'a>), Resource(Resource<'a>), @@ -912,6 +913,12 @@ struct List<'a> { ty: Box>, } +struct Map<'a> { + span: Span, + key: Box>, + value: Box>, +} + struct FixedSizeList<'a> { span: Span, ty: Box>, @@ -1406,6 +1413,20 @@ impl<'a> Type<'a> { } } + // map + Some((span, Token::Map)) => { + tokens.expect(Token::LessThan)?; + let key = Type::parse(tokens)?; + tokens.expect(Token::Comma)?; + let value = Type::parse(tokens)?; + tokens.expect(Token::GreaterThan)?; + Ok(Type::Map(Map { + span, + key: Box::new(key), + value: Box::new(value), + })) + } + // option Some((span, Token::Option_)) => { tokens.expect(Token::LessThan)?; @@ -1516,6 +1537,7 @@ impl<'a> Type<'a> { | Type::ErrorContext(span) => *span, Type::Name(id) => id.span, Type::List(l) => l.span, + Type::Map(m) => m.span, Type::FixedSizeList(l) => l.span, Type::Handle(h) => h.span(), Type::Resource(r) => r.span, diff --git a/crates/wit-parser/src/ast/lex.rs b/crates/wit-parser/src/ast/lex.rs index ec383932d4..7c838d6c75 100644 --- a/crates/wit-parser/src/ast/lex.rs +++ b/crates/wit-parser/src/ast/lex.rs @@ -80,6 +80,7 @@ pub enum Token { Stream, ErrorContext, List, + Map, Underscore, As, From_, @@ -301,6 +302,7 @@ impl<'a> Tokenizer<'a> { "stream" => Stream, "error-context" => ErrorContext, "list" => List, + "map" => Map, "_" => Underscore, "as" => As, "from" => From_, @@ -558,6 +560,7 @@ impl Token { Stream => "keyword `stream`", ErrorContext => "keyword `error-context`", List => "keyword `list`", + Map => "keyword `map`", Underscore => "keyword `_`", Id => "an identifier", ExplicitId => "an '%' identifier", diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index a997add506..5f2483985d 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -92,6 +92,7 @@ enum Key { Tuple(Vec), Enum(Vec), List(Type), + Map(Type, Type), FixedSizeList(Type, u32), Option(Type), Result(Option, Option), @@ -1180,6 +1181,37 @@ impl<'a> Resolver<'a> { let ty = self.resolve_type(&list.ty, stability)?; TypeDefKind::List(ty) } + ast::Type::Map(map) => { + let key_ty = self.resolve_type(&map.key, stability)?; + let value_ty = self.resolve_type(&map.value, stability)?; + + // Validate key type according to spec: only bool, integers, char, and string allowed + let is_valid_key = match key_ty { + Type::Bool + | Type::U8 + | Type::U16 + | Type::U32 + | Type::U64 + | Type::S8 + | Type::S16 + | Type::S32 + | Type::S64 + | Type::Char + | Type::String => true, + _ => false, + }; + + if !is_valid_key { + bail!(Error::new( + map.span, + format!( + "invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string" + ) + )); + } + + TypeDefKind::Map(key_ty, value_ty) + } ast::Type::FixedSizeList(list) => { let ty = self.resolve_type(&list.ty, stability)?; TypeDefKind::FixedSizeList(ty, list.size) @@ -1367,6 +1399,9 @@ impl<'a> Resolver<'a> { TypeDefKind::List(ty) | TypeDefKind::FixedSizeList(ty, _) | TypeDefKind::Option(ty) => find_in_type(types, *ty), + TypeDefKind::Map(k, v) => { + find_in_type(types, *k).or_else(|| find_in_type(types, *v)) + } TypeDefKind::Future(ty) | TypeDefKind::Stream(ty) => { ty.as_ref().and_then(|ty| find_in_type(types, *ty)) } @@ -1458,6 +1493,7 @@ impl<'a> Resolver<'a> { Key::Enum(r.cases.iter().map(|f| f.name.clone()).collect::>()) } TypeDefKind::List(ty) => Key::List(*ty), + TypeDefKind::Map(k, v) => Key::Map(*k, *v), TypeDefKind::FixedSizeList(ty, size) => Key::FixedSizeList(*ty, *size), TypeDefKind::Option(t) => Key::Option(*t), TypeDefKind::Result(r) => Key::Result(r.ok, r.err), @@ -1747,6 +1783,10 @@ fn collect_deps<'a>(ty: &ast::Type<'a>, deps: &mut Vec>) { ast::Type::Option(ast::Option_ { ty, .. }) | ast::Type::List(ast::List { ty, .. }) | ast::Type::FixedSizeList(ast::FixedSizeList { ty, .. }) => collect_deps(ty, deps), + ast::Type::Map(ast::Map { key, value, .. }) => { + collect_deps(key, deps); + collect_deps(value, deps); + } ast::Type::Result(r) => { if let Some(ty) = &r.ok { collect_deps(ty, deps); diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index 54b023a9a3..d87628c8e0 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -1261,6 +1261,7 @@ impl WitPackageDecoder<'_> { match &kind { TypeDefKind::Type(_) | TypeDefKind::List(_) + | TypeDefKind::Map(_, _) | TypeDefKind::FixedSizeList(..) | TypeDefKind::Tuple(_) | TypeDefKind::Option(_) @@ -1302,6 +1303,12 @@ impl WitPackageDecoder<'_> { Ok(TypeDefKind::List(t)) } + ComponentDefinedType::Map(k, v) => { + let k = self.convert_valtype(k)?; + let v = self.convert_valtype(v)?; + Ok(TypeDefKind::Map(k, v)) + } + ComponentDefinedType::FixedSizeList(t, size) => { let t = self.convert_valtype(t)?; Ok(TypeDefKind::FixedSizeList(t, *size)) @@ -1600,6 +1607,16 @@ impl Registrar<'_> { self.valtype(t, ty) } + ComponentDefinedType::Map(k, v) => { + let (key_ty, value_ty) = match &self.resolve.types[id].kind { + TypeDefKind::Map(k, v) => (k, v), + TypeDefKind::Type(Type::Id(_)) => return Ok(()), + _ => bail!("expected a map"), + }; + self.valtype(k, key_ty)?; + self.valtype(v, value_ty) + } + ComponentDefinedType::FixedSizeList(t, elements) => { let ty = match &self.resolve.types[id].kind { TypeDefKind::FixedSizeList(r, elements2) if elements2 == elements => r, diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index e591e775d1..f6afdfe3fb 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -596,6 +596,7 @@ pub enum TypeDefKind { Option(Type), Result(Result_), List(Type), + Map(Type, Type), FixedSizeList(Type, u32), Future(Option), Stream(Option), @@ -625,6 +626,7 @@ impl TypeDefKind { TypeDefKind::Option(_) => "option", TypeDefKind::Result(_) => "result", TypeDefKind::List(_) => "list", + TypeDefKind::Map(_, _) => "map", TypeDefKind::FixedSizeList(..) => "fixed size list", TypeDefKind::Future(_) => "future", TypeDefKind::Stream(_) => "stream", @@ -1256,6 +1258,10 @@ fn find_futures_and_streams(resolve: &Resolve, ty: Type, results: &mut Vec { find_futures_and_streams(resolve, *ty, results); } + TypeDefKind::Map(k, v) => { + find_futures_and_streams(resolve, *k, results); + find_futures_and_streams(resolve, *v, results); + } TypeDefKind::Result(r) => { if let Some(ty) = r.ok { find_futures_and_streams(resolve, ty, results); diff --git a/crates/wit-parser/src/live.rs b/crates/wit-parser/src/live.rs index bc436faac2..6e21cbd379 100644 --- a/crates/wit-parser/src/live.rs +++ b/crates/wit-parser/src/live.rs @@ -140,6 +140,10 @@ pub trait TypeIdVisitor { | TypeDefKind::Option(t) | TypeDefKind::Future(Some(t)) | TypeDefKind::Stream(Some(t)) => self.visit_type(resolve, t), + TypeDefKind::Map(k, v) => { + self.visit_type(resolve, k); + self.visit_type(resolve, v); + } TypeDefKind::Handle(handle) => match handle { crate::Handle::Own(ty) => self.visit_type_id(resolve, *ty), crate::Handle::Borrow(ty) => self.visit_type_id(resolve, *ty), diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 617996da3b..89d3f7489c 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -574,6 +574,7 @@ package {name} is defined in two different locations:\n\ Type::Id(id) => match &self.types[*id].kind { TypeDefKind::List(_) + | TypeDefKind::Map(_, _) | TypeDefKind::Variant(_) | TypeDefKind::Enum(_) | TypeDefKind::Option(_) @@ -3312,6 +3313,10 @@ impl Remap { Option(t) | List(t, ..) | FixedSizeList(t, ..) | Future(Some(t)) | Stream(Some(t)) => { self.update_ty(resolve, t, span)? } + Map(k, v) => { + self.update_ty(resolve, k, span)?; + self.update_ty(resolve, v, span)?; + } Result(r) => { if let Some(ty) = &mut r.ok { self.update_ty(resolve, ty, span)?; @@ -3806,6 +3811,9 @@ impl Remap { | TypeDefKind::Future(Some(ty)) | TypeDefKind::Stream(Some(ty)) | TypeDefKind::Option(ty) => self.type_has_borrow(resolve, ty), + TypeDefKind::Map(k, v) => { + self.type_has_borrow(resolve, k) || self.type_has_borrow(resolve, v) + } TypeDefKind::Result(r) => [&r.ok, &r.err] .iter() .filter_map(|t| t.as_ref()) diff --git a/crates/wit-parser/src/resolve/clone.rs b/crates/wit-parser/src/resolve/clone.rs index f7beb6472f..07ab256e1c 100644 --- a/crates/wit-parser/src/resolve/clone.rs +++ b/crates/wit-parser/src/resolve/clone.rs @@ -144,6 +144,10 @@ impl<'a> Cloner<'a> { | TypeDefKind::FixedSizeList(ty, ..) => { self.ty(ty); } + TypeDefKind::Map(k, v) => { + self.ty(k); + self.ty(v); + } TypeDefKind::Tuple(list) => { for ty in list.types.iter_mut() { self.ty(ty); diff --git a/crates/wit-parser/src/sizealign.rs b/crates/wit-parser/src/sizealign.rs index a9b96dc008..a060f99ea5 100644 --- a/crates/wit-parser/src/sizealign.rs +++ b/crates/wit-parser/src/sizealign.rs @@ -274,6 +274,9 @@ impl SizeAlign { TypeDefKind::List(_) => { ElementInfo::new(ArchitectureSize::new(0, 2), Alignment::Pointer) } + TypeDefKind::Map(_, _) => { + ElementInfo::new(ArchitectureSize::new(0, 2), Alignment::Pointer) + } TypeDefKind::Record(r) => self.record(r.fields.iter().map(|f| &f.ty)), TypeDefKind::Tuple(t) => self.record(t.types.iter()), TypeDefKind::Flags(f) => match f.repr() { diff --git a/crates/wit-parser/tests/ui/maps.wit b/crates/wit-parser/tests/ui/maps.wit new file mode 100644 index 0000000000..deac260d00 --- /dev/null +++ b/crates/wit-parser/tests/ui/maps.wit @@ -0,0 +1,34 @@ +package foo:maps; + +interface maps { + // Basic maps + type string-to-u32 = map; + type u32-to-string = map; + + // Nested maps + type nested-map = map>; + + // Maps with complex types + record person { + name: string, + age: u32, + } + type person-map = map; + + // Maps with tuples, lists, options + type complex-map = map>>; + + // Functions using maps + get-value: func(m: map, key: string) -> option; + set-value: func(m: map, key: string, value: u32) -> map; + merge-maps: func(a: map, b: map) -> map; + + // Map with various key types + type int-key-map = map; + type char-key-map = map; +} + +world maps-world { + import maps; + export maps; +} diff --git a/crates/wit-parser/tests/ui/maps.wit.json b/crates/wit-parser/tests/ui/maps.wit.json new file mode 100644 index 0000000000..33f5065f12 --- /dev/null +++ b/crates/wit-parser/tests/ui/maps.wit.json @@ -0,0 +1,255 @@ +{ + "worlds": [ + { + "name": "maps-world", + "imports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "exports": { + "interface-0": { + "interface": { + "id": 0 + } + } + }, + "package": 0 + } + ], + "interfaces": [ + { + "name": "maps", + "types": { + "string-to-u32": 0, + "u32-to-string": 1, + "nested-map": 3, + "person": 4, + "person-map": 5, + "complex-map": 8, + "int-key-map": 9, + "char-key-map": 10 + }, + "functions": { + "get-value": { + "name": "get-value", + "kind": "freestanding", + "params": [ + { + "name": "m", + "type": 2 + }, + { + "name": "key", + "type": "string" + } + ], + "result": 11, + "docs": { + "contents": "Functions using maps" + } + }, + "set-value": { + "name": "set-value", + "kind": "freestanding", + "params": [ + { + "name": "m", + "type": 2 + }, + { + "name": "key", + "type": "string" + }, + { + "name": "value", + "type": "u32" + } + ], + "result": 2 + }, + "merge-maps": { + "name": "merge-maps", + "kind": "freestanding", + "params": [ + { + "name": "a", + "type": 2 + }, + { + "name": "b", + "type": 2 + } + ], + "result": 2 + } + }, + "package": 0 + } + ], + "types": [ + { + "name": "string-to-u32", + "kind": { + "map": [ + "string", + "u32" + ] + }, + "owner": { + "interface": 0 + }, + "docs": { + "contents": "Basic maps" + } + }, + { + "name": "u32-to-string", + "kind": { + "map": [ + "u32", + "string" + ] + }, + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "map": [ + "string", + "u32" + ] + }, + "owner": null + }, + { + "name": "nested-map", + "kind": { + "map": [ + "string", + 2 + ] + }, + "owner": { + "interface": 0 + }, + "docs": { + "contents": "Nested maps" + } + }, + { + "name": "person", + "kind": { + "record": { + "fields": [ + { + "name": "name", + "type": "string" + }, + { + "name": "age", + "type": "u32" + } + ] + } + }, + "owner": { + "interface": 0 + }, + "docs": { + "contents": "Maps with complex types" + } + }, + { + "name": "person-map", + "kind": { + "map": [ + "string", + 4 + ] + }, + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "list": "u32" + }, + "owner": null + }, + { + "name": null, + "kind": { + "option": 6 + }, + "owner": null + }, + { + "name": "complex-map", + "kind": { + "map": [ + "string", + 7 + ] + }, + "owner": { + "interface": 0 + }, + "docs": { + "contents": "Maps with tuples, lists, options" + } + }, + { + "name": "int-key-map", + "kind": { + "map": [ + "u32", + "string" + ] + }, + "owner": { + "interface": 0 + }, + "docs": { + "contents": "Map with various key types" + } + }, + { + "name": "char-key-map", + "kind": { + "map": [ + "char", + "string" + ] + }, + "owner": { + "interface": 0 + } + }, + { + "name": null, + "kind": { + "option": "u32" + }, + "owner": null + } + ], + "packages": [ + { + "name": "foo:maps", + "interfaces": { + "maps": 0 + }, + "worlds": { + "maps-world": 0 + } + } + ] +} \ No newline at end of file diff --git a/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit b/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit new file mode 100644 index 0000000000..75a5f899cc --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit @@ -0,0 +1,7 @@ +// Map keys must be primitive types only +package foo:invalid-map-key; + +interface test { + // This should fail - tuple is not a valid map key type + type invalid = map, u32>; +} diff --git a/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit.result b/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit.result new file mode 100644 index 0000000000..4d8b09c5d2 --- /dev/null +++ b/crates/wit-parser/tests/ui/parse-fail/map-invalid-key.wit.result @@ -0,0 +1,5 @@ +invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string + --> tests/ui/parse-fail/map-invalid-key.wit:6:18 + | + 6 | type invalid = map, u32>; + | ^-- \ No newline at end of file diff --git a/tests/cli/wit-dylib-smoke.wit.stdout b/tests/cli/wit-dylib-smoke.wit.stdout index aebdc25481..5b1db1d87d 100644 --- a/tests/cli/wit-dylib-smoke.wit.stdout +++ b/tests/cli/wit-dylib-smoke.wit.stdout @@ -1,6 +1,6 @@ (module (@dylink.0 - (mem-info (memory 192 2) (table 1 0)) + (mem-info (memory 200 2) (table 1 0)) ) (type (;0;) (func (param i32))) (type (;1;) (func (param i32 i32 i32 i32) (result i32))) @@ -53,33 +53,36 @@ (import "env" "wit_dylib_pop_list" (func $wit_dylib_pop_list (;33;) (type 9))) (import "env" "wit_dylib_pop_iter_next" (func $wit_dylib_pop_iter_next (;34;) (type 4))) (import "env" "wit_dylib_pop_iter" (func $wit_dylib_pop_iter (;35;) (type 4))) - (import "env" "wit_dylib_push_bool" (func $wit_dylib_push_bool (;36;) (type 4))) - (import "env" "wit_dylib_push_char" (func $wit_dylib_push_char (;37;) (type 4))) - (import "env" "wit_dylib_push_u8" (func $wit_dylib_push_u8 (;38;) (type 4))) - (import "env" "wit_dylib_push_s8" (func $wit_dylib_push_s8 (;39;) (type 4))) - (import "env" "wit_dylib_push_u16" (func $wit_dylib_push_u16 (;40;) (type 4))) - (import "env" "wit_dylib_push_s16" (func $wit_dylib_push_s16 (;41;) (type 4))) - (import "env" "wit_dylib_push_u32" (func $wit_dylib_push_u32 (;42;) (type 4))) - (import "env" "wit_dylib_push_s32" (func $wit_dylib_push_s32 (;43;) (type 4))) - (import "env" "wit_dylib_push_u64" (func $wit_dylib_push_u64 (;44;) (type 10))) - (import "env" "wit_dylib_push_s64" (func $wit_dylib_push_s64 (;45;) (type 10))) - (import "env" "wit_dylib_push_f32" (func $wit_dylib_push_f32 (;46;) (type 11))) - (import "env" "wit_dylib_push_f64" (func $wit_dylib_push_f64 (;47;) (type 12))) - (import "env" "wit_dylib_push_string" (func $wit_dylib_push_string (;48;) (type 13))) - (import "env" "wit_dylib_push_record" (func $wit_dylib_push_record (;49;) (type 4))) - (import "env" "wit_dylib_push_tuple" (func $wit_dylib_push_tuple (;50;) (type 4))) - (import "env" "wit_dylib_push_flags" (func $wit_dylib_push_flags (;51;) (type 13))) - (import "env" "wit_dylib_push_enum" (func $wit_dylib_push_enum (;52;) (type 13))) - (import "env" "wit_dylib_push_borrow" (func $wit_dylib_push_borrow (;53;) (type 13))) - (import "env" "wit_dylib_push_own" (func $wit_dylib_push_own (;54;) (type 13))) - (import "env" "wit_dylib_push_future" (func $wit_dylib_push_future (;55;) (type 13))) - (import "env" "wit_dylib_push_stream" (func $wit_dylib_push_stream (;56;) (type 13))) - (import "env" "wit_dylib_push_variant" (func $wit_dylib_push_variant (;57;) (type 13))) - (import "env" "wit_dylib_push_option" (func $wit_dylib_push_option (;58;) (type 13))) - (import "env" "wit_dylib_push_result" (func $wit_dylib_push_result (;59;) (type 13))) - (import "env" "wit_dylib_push_list" (func $wit_dylib_push_list (;60;) (type 1))) - (import "env" "wit_dylib_list_append" (func $wit_dylib_list_append (;61;) (type 4))) - (import "$root" "x" (func $x (;62;) (type 14))) + (import "env" "wit_dylib_pop_map" (func $wit_dylib_pop_map (;36;) (type 9))) + (import "env" "wit_dylib_push_bool" (func $wit_dylib_push_bool (;37;) (type 4))) + (import "env" "wit_dylib_push_char" (func $wit_dylib_push_char (;38;) (type 4))) + (import "env" "wit_dylib_push_u8" (func $wit_dylib_push_u8 (;39;) (type 4))) + (import "env" "wit_dylib_push_s8" (func $wit_dylib_push_s8 (;40;) (type 4))) + (import "env" "wit_dylib_push_u16" (func $wit_dylib_push_u16 (;41;) (type 4))) + (import "env" "wit_dylib_push_s16" (func $wit_dylib_push_s16 (;42;) (type 4))) + (import "env" "wit_dylib_push_u32" (func $wit_dylib_push_u32 (;43;) (type 4))) + (import "env" "wit_dylib_push_s32" (func $wit_dylib_push_s32 (;44;) (type 4))) + (import "env" "wit_dylib_push_u64" (func $wit_dylib_push_u64 (;45;) (type 10))) + (import "env" "wit_dylib_push_s64" (func $wit_dylib_push_s64 (;46;) (type 10))) + (import "env" "wit_dylib_push_f32" (func $wit_dylib_push_f32 (;47;) (type 11))) + (import "env" "wit_dylib_push_f64" (func $wit_dylib_push_f64 (;48;) (type 12))) + (import "env" "wit_dylib_push_string" (func $wit_dylib_push_string (;49;) (type 13))) + (import "env" "wit_dylib_push_record" (func $wit_dylib_push_record (;50;) (type 4))) + (import "env" "wit_dylib_push_tuple" (func $wit_dylib_push_tuple (;51;) (type 4))) + (import "env" "wit_dylib_push_flags" (func $wit_dylib_push_flags (;52;) (type 13))) + (import "env" "wit_dylib_push_enum" (func $wit_dylib_push_enum (;53;) (type 13))) + (import "env" "wit_dylib_push_borrow" (func $wit_dylib_push_borrow (;54;) (type 13))) + (import "env" "wit_dylib_push_own" (func $wit_dylib_push_own (;55;) (type 13))) + (import "env" "wit_dylib_push_future" (func $wit_dylib_push_future (;56;) (type 13))) + (import "env" "wit_dylib_push_stream" (func $wit_dylib_push_stream (;57;) (type 13))) + (import "env" "wit_dylib_push_variant" (func $wit_dylib_push_variant (;58;) (type 13))) + (import "env" "wit_dylib_push_option" (func $wit_dylib_push_option (;59;) (type 13))) + (import "env" "wit_dylib_push_result" (func $wit_dylib_push_result (;60;) (type 13))) + (import "env" "wit_dylib_push_list" (func $wit_dylib_push_list (;61;) (type 1))) + (import "env" "wit_dylib_list_append" (func $wit_dylib_list_append (;62;) (type 4))) + (import "env" "wit_dylib_push_map" (func $wit_dylib_push_map (;63;) (type 1))) + (import "env" "wit_dylib_map_append" (func $wit_dylib_map_append (;64;) (type 4))) + (import "$root" "x" (func $x (;65;) (type 14))) (import "env" "__table_base" (global $__table_base (;0;) i32)) (import "env" "__memory_base" (global $__memory_base (;1;) i32)) (import "env" "__stack_pointer" (global $__stack_pointer (;2;) (mut i32))) @@ -91,7 +94,7 @@ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs)) (export "__wasm_call_ctors" (func $__wasm_call_ctors)) (elem (;0;) (table 0) (global.get $__table_base) func $"adapter x") - (func $"adapter x" (;63;) (type 0) (param i32) + (func $"adapter x" (;66;) (type 0) (param i32) (local i32) global.get $__stack_pointer i32.const 16 @@ -104,7 +107,7 @@ i32.add global.set $__stack_pointer ) - (func $y (;64;) (type 14) + (func $y (;67;) (type 14) (local i32 i32 i32) global.get $__stack_pointer i32.const 16 @@ -126,7 +129,7 @@ local.get 1 i32.store ) - (func $cabi_post_y (;65;) (type 14) + (func $cabi_post_y (;68;) (type 14) (local i32 i32) global.get $__stack_pointer local.set 0 @@ -147,10 +150,10 @@ i32.add global.set $__stack_pointer ) - (func $__wasm_apply_data_relocs (;66;) (type 14) + (func $__wasm_apply_data_relocs (;69;) (type 14) global.get $__memory_base global.get $__memory_base - i32.const 188 + i32.const 196 i32.add i32.store offset=4 global.get $__memory_base @@ -158,7 +161,7 @@ i32.store offset=8 global.get $__memory_base global.get $__memory_base - i32.const 190 + i32.const 198 i32.add i32.store offset=44 global.get $__memory_base @@ -172,11 +175,12 @@ i32.add i32.store offset=80 ) - (func $__wasm_call_ctors (;67;) (type 14) + (func $__wasm_call_ctors (;70;) (type 14) i32.const 64 global.get $__memory_base i32.add call $wit_dylib_initialize ) - (data (;0;) (global.get $__memory_base) "/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/ff/ff/ff/ff/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/ff/ff/ff/ff/02/00/00/00/01/00/00/00/00/00/00/00/01/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00x/00y/00") + (data (;0;) (global.get $__memory_base) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\ff\ff\ff\ff\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\ff\ff\ff\ff\02\00\00\00\01\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00x\00y\00") + (@custom "component-type" (after data) "\00asm\0d\00\01\00\00\19\16wit-component-encoding\04\00\07,\01A\02\01A\03\01@\00\01\00\03\00\01x\01\00\04\00\01y\01\00\04\00\10the:test/adapter\04\00\0b\0d\01\00\07adapter\03\00\00\00/\09producers\01\0cprocessed-by\01\0dwit-component\070.243.0") ) From 3cfde015de6dd9005527c30634dcd7f0747d08f4 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 27 Oct 2025 09:15:56 -0400 Subject: [PATCH 02/14] refactor(wit): remove key type validation from map resolution --- crates/wit-parser/src/ast/resolve.rs | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 5f2483985d..8fd884fde8 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1184,32 +1184,6 @@ impl<'a> Resolver<'a> { ast::Type::Map(map) => { let key_ty = self.resolve_type(&map.key, stability)?; let value_ty = self.resolve_type(&map.value, stability)?; - - // Validate key type according to spec: only bool, integers, char, and string allowed - let is_valid_key = match key_ty { - Type::Bool - | Type::U8 - | Type::U16 - | Type::U32 - | Type::U64 - | Type::S8 - | Type::S16 - | Type::S32 - | Type::S64 - | Type::Char - | Type::String => true, - _ => false, - }; - - if !is_valid_key { - bail!(Error::new( - map.span, - format!( - "invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string" - ) - )); - } - TypeDefKind::Map(key_ty, value_ty) } ast::Type::FixedSizeList(list) => { From d4ca0ea176985470f77a23c89ed0dd538a2a918e Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 21 Nov 2025 14:01:19 -0500 Subject: [PATCH 03/14] refactor(wit): rename LIST to NEEDS_MEMORY in TypeContents for clarity --- crates/wit-component/src/encoding.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 0ec2812511..15d82b23f8 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -186,7 +186,7 @@ impl RequiredOptions { // If lists/strings are lowered into wasm then memory is required as // usual but `realloc` is also required to allow the external caller to // allocate space in the destination for the list/string. - if types.contains(TypeContents::LIST) { + if types.contains(TypeContents::NEEDS_MEMORY) { *self |= RequiredOptions::MEMORY | RequiredOptions::REALLOC; } if types.contains(TypeContents::STRING) { @@ -200,7 +200,7 @@ impl RequiredOptions { // Unlike for `lower` when lifting a string/list all that's needed is // memory, since the string/list already resides in memory `realloc` // isn't needed. - if types.contains(TypeContents::LIST) { + if types.contains(TypeContents::NEEDS_MEMORY) { *self |= RequiredOptions::MEMORY; } if types.contains(TypeContents::STRING) { @@ -279,7 +279,7 @@ bitflags::bitflags! { /// structure of a type. struct TypeContents: u8 { const STRING = 1 << 0; - const LIST = 1 << 1; + const NEEDS_MEMORY = 1 << 1; } } @@ -326,9 +326,9 @@ impl TypeContents { Self::for_optional_types(resolve, v.cases.iter().map(|c| c.ty.as_ref())) } TypeDefKind::Enum(_) => Self::empty(), - TypeDefKind::List(t) => Self::for_type(resolve, t) | Self::LIST, + TypeDefKind::List(t) => Self::for_type(resolve, t) | Self::NEEDS_MEMORY, TypeDefKind::Map(k, v) => { - Self::for_type(resolve, k) | Self::for_type(resolve, v) | Self::LIST + Self::for_type(resolve, k) | Self::for_type(resolve, v) | Self::NEEDS_MEMORY } TypeDefKind::FixedSizeList(t, _elements) => Self::for_type(resolve, t), TypeDefKind::Type(t) => Self::for_type(resolve, t), From f39d9c86c8ef8184d414b9f63946db3447a8474a Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 21 Nov 2025 14:25:58 -0500 Subject: [PATCH 04/14] feat(wasmparser): add error handling for unimplemented GC lowering of map type --- crates/wasmparser/src/validator/component_types.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs index bc0fa7e5f2..8dd9ac6234 100644 --- a/crates/wasmparser/src/validator/component_types.rs +++ b/crates/wasmparser/src/validator/component_types.rs @@ -1672,7 +1672,6 @@ impl ComponentDefinedType { ComponentDefinedType::Variant(ty) => ty.lower_gc(types, abi, options, offset, core), ComponentDefinedType::List(ty) - | ComponentDefinedType::Map(ty, _) | ComponentDefinedType::FixedSizeList(ty, _) => { let id = match core.as_concrete_ref() { Some(id) => id, @@ -1693,6 +1692,11 @@ impl ComponentDefinedType { ty.lower_gc(types, abi, options, offset, array_ty.0.element_type.into()) } + ComponentDefinedType::Map(_, _) => bail!( + offset, + "GC lowering for component `map` type is not yet implemented" + ), + ComponentDefinedType::Tuple(ty) => ty.lower_gc(types, abi, options, offset, core), ComponentDefinedType::Flags(flags) => { From bde4b84d62b14681d8de74b63a2ced6ee2e148cb Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 21 Nov 2025 14:56:05 -0500 Subject: [PATCH 05/14] refactor(wit): revert map type support from the dylib --- crates/wit-dylib/ffi/src/ffi.rs | 12 - crates/wit-dylib/ffi/src/lib.rs | 79 ------- crates/wit-dylib/ffi/src/types.rs | 52 ----- crates/wit-dylib/src/bindgen.rs | 259 +--------------------- crates/wit-dylib/src/lib.rs | 13 +- crates/wit-dylib/src/metadata.rs | 27 --- crates/wit-dylib/test-programs/src/lib.rs | 64 +----- crates/wit-dylib/wit_dylib.h | 20 -- 8 files changed, 15 insertions(+), 511 deletions(-) diff --git a/crates/wit-dylib/ffi/src/ffi.rs b/crates/wit-dylib/ffi/src/ffi.rs index de2607360b..9ba95428be 100644 --- a/crates/wit-dylib/ffi/src/ffi.rs +++ b/crates/wit-dylib/ffi/src/ffi.rs @@ -28,7 +28,6 @@ pub const WIT_TYPE_FIXED_SIZE_LIST: u32 = 24; pub const WIT_TYPE_FUTURE: u32 = 25; pub const WIT_TYPE_STREAM: u32 = 26; pub const WIT_TYPE_ALIAS: u32 = 27; -pub const WIT_TYPE_MAP: u32 = 28; pub const WIT_TYPE_EMPTY: u32 = 255; pub const WIT_CURRENT_VERSION: u32 = 2; pub type wit_type_t = u32; @@ -170,15 +169,6 @@ pub struct wit_list { pub type wit_list_t = wit_list; #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct wit_map { - pub interface: *const ::std::os::raw::c_char, - pub name: *const ::std::os::raw::c_char, - pub key_ty: wit_type_t, - pub value_ty: wit_type_t, -} -pub type wit_map_t = wit_map; -#[repr(C)] -#[derive(Debug, Copy, Clone)] pub struct wit_fixed_size_list { pub interface: *const ::std::os::raw::c_char, pub name: *const ::std::os::raw::c_char, @@ -290,8 +280,6 @@ pub struct wit { pub results: *const wit_result_t, pub num_lists: usize, pub lists: *const wit_list_t, - pub num_maps: usize, - pub maps: *const wit_map_t, pub num_fixed_size_lists: usize, pub fixed_size_lists: *const wit_fixed_size_list_t, pub num_futures: usize, diff --git a/crates/wit-dylib/ffi/src/lib.rs b/crates/wit-dylib/ffi/src/lib.rs index 93660eb5f8..5e91f7c6d4 100644 --- a/crates/wit-dylib/ffi/src/lib.rs +++ b/crates/wit-dylib/ffi/src/lib.rs @@ -353,30 +353,6 @@ macro_rules! export { unsafe { <$name as $crate::RawInterpreter>::raw_list_append(cx, ty) } } - #[no_mangle] - pub unsafe extern "C" fn wit_dylib_pop_map( - cx: *mut u8, - ty: usize, - ptr: &mut *const u8, - ) -> usize { - unsafe { <$name as $crate::RawInterpreter>::raw_pop_map(cx, ty, ptr) } - } - - #[no_mangle] - pub unsafe extern "C" fn wit_dylib_push_map( - cx: *mut u8, - ty: usize, - ptr: *mut u8, - len: usize, - ) -> u32 { - unsafe { <$name as $crate::RawInterpreter>::raw_push_map(cx, ty, ptr, len) } - } - - #[no_mangle] - pub unsafe extern "C" fn wit_dylib_map_append(cx: *mut u8, ty: usize) { - unsafe { <$name as $crate::RawInterpreter>::raw_map_append(cx, ty) } - } - fn main() { unreachable!(); } @@ -461,12 +437,6 @@ pub trait Call { fn pop_iter_next(&mut self, ty: List); fn pop_iter(&mut self, ty: List); - unsafe fn maybe_pop_map(&mut self, ty: Map) -> Option<(*const u8, usize)> { - let _ = ty; - None - } - fn pop_map(&mut self, ty: Map) -> usize; - fn push_bool(&mut self, val: bool); fn push_char(&mut self, val: char); fn push_u8(&mut self, val: u8); @@ -497,13 +467,6 @@ pub trait Call { } fn push_list(&mut self, ty: List, capacity: usize); fn list_append(&mut self, ty: List); - - unsafe fn push_raw_map(&mut self, ty: Map, ptr: *mut u8, len: usize) -> bool { - let _ = (ty, ptr, len); - false - } - fn push_map(&mut self, ty: Map, capacity: usize); - fn map_append(&mut self, ty: Map); } static mut WIT_T: *const ffi::wit_t = ptr::null_mut(); @@ -996,48 +959,6 @@ pub trait RawInterpreter: Interpreter { Self::cx_mut(cx).list_append(wit.list(ty)) } } - - unsafe fn raw_pop_map(cx: *mut u8, ty: usize, retptr: &mut *const u8) -> usize { - debug_println!("pop_map({cx:?}, {ty})"); - unsafe { - let wit = Wit::from_raw(WIT_T); - let cx = Self::cx_mut(cx); - let ty = wit.map(ty); - match cx.maybe_pop_map(ty) { - Some((ptr, len)) => { - *retptr = ptr; - len - } - None => { - *retptr = ptr::null(); - cx.pop_map(ty) - } - } - } - } - - unsafe fn raw_push_map(cx: *mut u8, ty: usize, map: *mut u8, len: usize) -> u32 { - debug_println!("push_map({cx:?}, {ty}, {map:?}, {len})"); - unsafe { - let wit = Wit::from_raw(WIT_T); - let cx = Self::cx_mut(cx); - let ty = wit.map(ty); - if cx.push_raw_map(ty, map, len) { - 1 - } else { - cx.push_map(ty, len); - 0 - } - } - } - - unsafe fn raw_map_append(cx: *mut u8, ty: usize) { - debug_println!("map_append({cx:?}, {ty})"); - unsafe { - let wit = Wit::from_raw(WIT_T); - Self::cx_mut(cx).map_append(wit.map(ty)) - } - } } impl RawInterpreter for T {} diff --git a/crates/wit-dylib/ffi/src/types.rs b/crates/wit-dylib/ffi/src/types.rs index 51b419b515..1b337d9e55 100644 --- a/crates/wit-dylib/ffi/src/types.rs +++ b/crates/wit-dylib/ffi/src/types.rs @@ -250,21 +250,6 @@ impl Wit { self.raw_lists().iter().map(|e| List { wit: *self, ptr: e }) } - fn raw_maps(&self) -> &'static [ffi::wit_map_t] { - unsafe { slice(self.ptr.maps, self.ptr.num_maps) } - } - - pub fn map(&self, index: usize) -> Map { - Map { - wit: *self, - ptr: &self.raw_maps()[index], - } - } - - pub fn iter_maps(&self) -> impl ExactSizeIterator + Clone + '_ { - self.raw_maps().iter().map(|e| Map { wit: *self, ptr: e }) - } - fn raw_fixed_size_lists(&self) -> &'static [ffi::wit_fixed_size_list_t] { unsafe { slice(self.ptr.fixed_size_lists, self.ptr.num_fixed_size_lists) } } @@ -1014,43 +999,6 @@ impl fmt::Debug for List { } } -#[derive(Copy, Clone)] -pub struct Map { - wit: Wit, - ptr: &'static ffi::wit_map_t, -} - -impl_extra_traits!(Map); - -impl Map { - pub fn interface(&self) -> Option<&'static str> { - unsafe { opt_str(self.ptr.interface) } - } - - pub fn name(&self) -> Option<&'static str> { - unsafe { opt_str(self.ptr.name) } - } - - pub fn key_ty(&self) -> Type { - Type::from_raw(self.wit, self.ptr.key_ty) - } - - pub fn value_ty(&self) -> Type { - Type::from_raw(self.wit, self.ptr.value_ty) - } -} - -impl fmt::Debug for Map { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Map") - .field("interface", &self.interface()) - .field("name", &self.name()) - .field("key_ty", &self.key_ty()) - .field("value_ty", &self.value_ty()) - .finish() - } -} - #[derive(Copy, Clone)] pub struct FixedSizeList { wit: Wit, diff --git a/crates/wit-dylib/src/bindgen.rs b/crates/wit-dylib/src/bindgen.rs index c135d44d73..b88a55cd47 100644 --- a/crates/wit-dylib/src/bindgen.rs +++ b/crates/wit-dylib/src/bindgen.rs @@ -69,7 +69,6 @@ intrinsics! { pop_list : [ValType::I32; 3] -> [ValType::I32] = "wit_dylib_pop_list", pop_iter_next : [ValType::I32; 2] -> [] = "wit_dylib_pop_iter_next", pop_iter : [ValType::I32; 2] -> [] = "wit_dylib_pop_iter", - pop_map : [ValType::I32; 3] -> [ValType::I32] = "wit_dylib_pop_map", push_bool : [ValType::I32; 2] -> [] = "wit_dylib_push_bool", push_char : [ValType::I32; 2] -> [] = "wit_dylib_push_char", @@ -97,8 +96,6 @@ intrinsics! { push_result : [ValType::I32; 3] -> [] = "wit_dylib_push_result", push_list : [ValType::I32; 4] -> [ValType::I32] = "wit_dylib_push_list", list_append : [ValType::I32; 2] -> [] = "wit_dylib_list_append", - push_map : [ValType::I32; 4] -> [ValType::I32] = "wit_dylib_push_map", - map_append : [ValType::I32; 2] -> [] = "wit_dylib_map_append", } pub fn import( @@ -969,137 +966,6 @@ impl<'a> FunctionCompiler<'a> { // that's returned directly (e.g. `list`) but if it's NULL // then the list is allocated manually with `cabi_realloc` and then // lowered element-by-element. - TypeDefKind::Map(key_ty, value_ty) => { - let metadata::Type::Map(map_index) = ty else { - unreachable!() - }; - let pop_map = self.adapter.intrinsics().pop_map; - let pop_iter_next = self.adapter.intrinsics().pop_iter_next; - let pop_iter = self.adapter.intrinsics().pop_iter; - let cabi_realloc = self.adapter.intrinsics().cabi_realloc; - let dealloc_bytes = self.adapter.intrinsics().dealloc_bytes; - - // Calculate size of tuple - let key_size = self.adapter.sizes.size(key_ty).size_wasm32(); - let key_align = self.adapter.sizes.align(key_ty).align_wasm32(); - let value_size = self.adapter.sizes.size(value_ty).size_wasm32(); - let value_align = self.adapter.sizes.align(value_ty).align_wasm32(); - - // Tuple alignment is max of element alignments - let tuple_align = key_align.max(value_align); - // Tuple size is key_size + padding + value_size - let key_end = key_size; - let value_offset = align_up(key_end, value_align); - let tuple_size = value_offset + value_size; - - let (dst_ptr, dst_len) = dest.split_ptr_len(); - - // Load the map ptr/len into locals - self.local_get_ctx(); - self.ins().i32_const(map_index.try_into().unwrap()); - self.local_get_stack_temp_addr(); - self.ins().call(pop_map); - let m_len = self.local_set_new_tmp(ValType::I32); - self.local_get_stack_temp_addr(); - self.ins().i32_load(MemArg { - memory_index: 0, - offset: 0, - align: 2, - }); - let m_ptr = self.local_set_new_tmp(ValType::I32); - - // If the pointer is null then the interpreter doesn't support - // the same canonical ABI view of this map so a loop is - // required to lower element-by-element. - self.ins().local_get(m_ptr.idx); - self.ins().i32_eqz(); - self.ins().if_(BlockType::Empty); - { - self.ins().i32_const(0); - let m_index = self.local_set_new_tmp(ValType::I32); - - self.ins().i32_const(0); - self.ins().i32_const(0); - self.ins().i32_const(tuple_align.try_into().unwrap()); - self.ins().local_get(m_len.idx); - self.ins().i32_const(tuple_size.try_into().unwrap()); - self.ins().i32_mul(); - let m_byte_size = self.local_tee_new_tmp(ValType::I32); - self.ins().call(cabi_realloc); - let m_elem_addr = self.local_tee_new_tmp(ValType::I32); - self.ins().local_set(m_ptr.idx); - - // Loop over each element of the map. - self.ins().loop_(BlockType::Empty); - { - // Entry loop condition, `m_index != m_len` - self.ins().local_get(m_index.idx); - self.ins().local_get(m_len.idx); - self.ins().i32_ne(); - self.ins().if_(BlockType::Empty); - { - // Get the `m_index`th element from map (puts key then value on stack) - self.local_get_ctx(); - self.ins().i32_const(map_index.try_into().unwrap()); - self.ins().call(pop_iter_next); - - // Lower the key - self.lower( - *key_ty, - &AbiLoc::Memory(Memory { - addr: TempLocal::new(m_elem_addr.idx, ValType::I32), - offset: 0, - }), - ); - - // Lower the value - self.lower( - *value_ty, - &AbiLoc::Memory(Memory { - addr: TempLocal::new(m_elem_addr.idx, ValType::I32), - offset: value_offset.try_into().unwrap(), - }), - ); - - // Increment the `m_index` counter - self.ins().local_get(m_index.idx); - self.ins().i32_const(1); - self.ins().i32_add(); - self.ins().local_set(m_index.idx); - - // Increment the `m_elem_addr` counter - self.ins().local_get(m_elem_addr.idx); - self.ins().i32_const(tuple_size.try_into().unwrap()); - self.ins().i32_add(); - self.ins().local_set(m_elem_addr.idx); - - // Continue the loop. - self.ins().br(1); - } - self.ins().end(); - } - self.ins().end(); - - self.local_get_ctx(); - self.ins().i32_const(map_index.try_into().unwrap()); - self.ins().call(pop_iter); - - self.local_get_ctx(); - self.ins().local_get(m_ptr.idx); - self.ins().local_get(m_byte_size.idx); - self.ins().i32_const(tuple_align.try_into().unwrap()); - self.ins().i32_const(tuple_size.try_into().unwrap()); - self.ins().call(dealloc_bytes); - } - self.ins().end(); - - // Store the ptr/len - self.ins().local_get(m_ptr.idx); - self.store_scalar_from_top_of_stack(&dst_ptr, ValType::I32, 2); - self.ins().local_get(m_len.idx); - self.store_scalar_from_top_of_stack(&dst_len, ValType::I32, 2); - } - TypeDefKind::List(t) => { let metadata::Type::List(list_index) = ty else { unreachable!() @@ -1249,6 +1115,11 @@ impl<'a> FunctionCompiler<'a> { todo!("fixed-size-list") } + TypeDefKind::Map(k, v) => { + let _ = (k, v); + todo!("map") + } + // Should not be possible to hit during lowering. TypeDefKind::Resource => unreachable!(), TypeDefKind::Unknown => unreachable!(), @@ -1619,121 +1490,6 @@ impl<'a> FunctionCompiler<'a> { ); } - TypeDefKind::Map(key_ty, value_ty) => { - let metadata::Type::Map(map_index) = ty else { - unreachable!() - }; - - // Calculate size of tuple - let key_size = self.adapter.sizes.size(key_ty).size_wasm32(); - let key_align = self.adapter.sizes.align(key_ty).align_wasm32(); - let value_size = self.adapter.sizes.size(value_ty).size_wasm32(); - let value_align = self.adapter.sizes.align(value_ty).align_wasm32(); - - // Tuple alignment is max of element alignments - let tuple_align = key_align.max(value_align); - // Tuple size is key_size + padding + value_size - let key_end = key_size; - let value_offset = align_up(key_end, value_align); - let tuple_size = value_offset + value_size; - - let map_index = i32::try_from(map_index).unwrap(); - let push_map = i.push_map; - let map_append = i.map_append; - let dealloc_bytes = i.dealloc_bytes; - let (src_ptr, src_len) = src.split_ptr_len(); - - // Give the interpreter to lift the map exactly as-is and take - // ownership of the allocation. - self.local_get_ctx(); - self.ins().i32_const(map_index); - self.load_abi_loc(&src_ptr, ValType::I32, 2); - let m_ptr = self.local_tee_new_tmp(ValType::I32); - self.load_abi_loc(&src_len, ValType::I32, 2); - let m_len = self.local_tee_new_tmp(ValType::I32); - self.ins().call(push_map); - - // If the interpreter returned 0 then the map needs to be - // lifted manually element-by-element. - self.ins().i32_eqz(); - self.ins().if_(BlockType::Empty); - { - // Prep deallocation of the map after the loop by saving - // off the pointer/byte size. - self.ins().local_get(m_len.idx); - self.ins().i32_const(tuple_size.try_into().unwrap()); - self.ins().i32_mul(); - let m_byte_size = self.local_set_new_tmp(ValType::I32); - self.ins().local_get(m_ptr.idx); - let m_ptr_to_free = self.local_set_new_tmp(ValType::I32); - - // Using `m_len` as the loop counter, element-by-element - // lift from the map and push into the map. - self.ins().loop_(BlockType::Empty); - { - self.ins().local_get(m_len.idx); - self.ins().if_(BlockType::Empty); - { - // Lift the key from memory - let key_src = AbiLoc::Memory(Memory { - addr: TempLocal::new(m_ptr.idx, m_ptr.ty), - offset: 0, - }); - self.lift(&key_src, *key_ty); - - // Lift the value from memory - let value_src = AbiLoc::Memory(Memory { - addr: TempLocal::new(m_ptr.idx, m_ptr.ty), - offset: value_offset.try_into().unwrap(), - }); - self.lift(&value_src, *value_ty); - - // Push the lifted key-value pair onto the map. - self.local_get_ctx(); - self.ins().i32_const(map_index); - self.ins().call(map_append); - - // decrement the length counter - self.ins().local_get(m_len.idx); - self.ins().i32_const(1); - self.ins().i32_sub(); - self.ins().local_set(m_len.idx); - - // increment the pointer - self.ins().local_get(m_ptr.idx); - self.ins().i32_const(tuple_size.try_into().unwrap()); - self.ins().i32_add(); - self.ins().local_set(m_ptr.idx); - - self.ins().br(1); - } - self.ins().end(); - } - self.ins().end(); - - // The canonical ABI representation of this map is no - // longer needed, so discard it. - self.ins().local_get(m_byte_size.idx); - self.ins().if_(BlockType::Empty); - { - self.local_get_ctx(); - self.ins().local_get(m_ptr_to_free.idx); - self.ins().local_get(m_byte_size.idx); - self.ins().i32_const(tuple_align.try_into().unwrap()); - self.ins().i32_const(0); - self.ins().call(dealloc_bytes); - } - self.ins().end(); - - self.free_temp_local(m_byte_size); - self.free_temp_local(m_ptr_to_free); - } - self.ins().end(); - - self.free_temp_local(m_ptr); - self.free_temp_local(m_len); - } - TypeDefKind::List(t) => { let metadata::Type::List(list_index) = ty else { unreachable!() @@ -1831,6 +1587,11 @@ impl<'a> FunctionCompiler<'a> { self.free_temp_local(l_len); } + TypeDefKind::Map(k, v) => { + let _ = (k, v); + todo!("map") + } + // Should not be possible to hit during lifting. TypeDefKind::Resource => unreachable!(), TypeDefKind::Unknown => unreachable!(), diff --git a/crates/wit-dylib/src/lib.rs b/crates/wit-dylib/src/lib.rs index 5badea52ff..96e5784565 100644 --- a/crates/wit-dylib/src/lib.rs +++ b/crates/wit-dylib/src/lib.rs @@ -768,16 +768,6 @@ impl Adapter { }); metadata::Type::List(index) } - TypeDefKind::Map(key_ty, value_ty) => { - let index = self.metadata.maps.len(); - self.metadata.maps.push(metadata::Map { - interface, - name, - key_ty: self.lookup_ty(key_ty), - value_ty: self.lookup_ty(value_ty), - }); - metadata::Type::Map(index) - } TypeDefKind::FixedSizeList(t, len) => { let index = self.metadata.fixed_size_lists.len(); self.metadata @@ -948,6 +938,9 @@ impl Adapter { TypeDefKind::Handle(Handle::Borrow(t)) => { metadata::Type::Borrow(self.resource_map[&dealias(resolve, *t)]) } + TypeDefKind::Map(_, _) => { + todo!("map") + } TypeDefKind::Unknown => unreachable!(), }; self.type_map.insert(id, result); diff --git a/crates/wit-dylib/src/metadata.rs b/crates/wit-dylib/src/metadata.rs index 514ef2b57a..00a8e59785 100644 --- a/crates/wit-dylib/src/metadata.rs +++ b/crates/wit-dylib/src/metadata.rs @@ -18,7 +18,6 @@ pub struct Metadata { pub options: Vec, pub results: Vec, pub lists: Vec, - pub maps: Vec, pub fixed_size_lists: Vec, pub futures: Vec, pub streams: Vec, @@ -110,13 +109,6 @@ pub struct List { pub ty: Type, } -pub struct Map { - pub interface: Option, - pub name: Option, - pub key_ty: Type, - pub value_ty: Type, -} - pub struct FixedSizeList { pub id: TypeId, pub interface: Option, @@ -194,7 +186,6 @@ pub enum Type { Option(usize), Result(usize), List(usize), - Map(usize), FixedSizeList(usize), Future(usize), Stream(usize), @@ -251,7 +242,6 @@ impl Metadata { let options = encoder.encode_list(&self.options, Encoder::encode_options); let results = encoder.encode_list(&self.results, Encoder::encode_results); let lists = encoder.encode_list(&self.lists, Encoder::encode_lists); - let maps = encoder.encode_list(&self.maps, Encoder::encode_maps); let fixed_size_lists = encoder.encode_list(&self.fixed_size_lists, Encoder::encode_fixed_size_lists); let futures = encoder.encode_list(&self.futures, Encoder::encode_futures); @@ -273,7 +263,6 @@ impl Metadata { options, results, lists, - maps, fixed_size_lists, futures, streams, @@ -549,21 +538,6 @@ impl Encoder { } } - fn encode_maps(&mut self, maps: &[Map]) { - for map in maps { - let Map { - interface, - name, - key_ty, - value_ty, - } = map; - self.opt_string_ptr(interface.as_deref()); - self.opt_string_ptr(name.as_deref()); - self.ty(key_ty); - self.ty(value_ty); - } - } - fn encode_fixed_size_lists(&mut self, lists: &[FixedSizeList]) { for list in lists { let FixedSizeList { @@ -788,7 +762,6 @@ impl Encoder { Type::Future(i) => index(25, i), Type::Stream(i) => index(26, i), Type::Alias(i) => index(27, i), - Type::Map(i) => index(28, i), }; self.put_u32(val); } diff --git a/crates/wit-dylib/test-programs/src/lib.rs b/crates/wit-dylib/test-programs/src/lib.rs index fc42e3a185..d154ec8883 100644 --- a/crates/wit-dylib/test-programs/src/lib.rs +++ b/crates/wit-dylib/test-programs/src/lib.rs @@ -121,8 +121,6 @@ impl Drop for Cx<'_> { enum CowIter<'a> { Borrowed(std::slice::Iter<'a, Val>), Owned(std::vec::IntoIter), - BorrowedMap(std::slice::Iter<'a, (Val, Val)>), - OwnedMap(std::vec::IntoIter<(Val, Val)>), } impl<'a> Iterator for CowIter<'a> { @@ -132,25 +130,6 @@ impl<'a> Iterator for CowIter<'a> { match self { CowIter::Borrowed(i) => Some(Cow::Borrowed(i.next()?)), CowIter::Owned(i) => Some(Cow::Owned(i.next()?)), - CowIter::BorrowedMap(_) | CowIter::OwnedMap(_) => { - panic!("use next_map_entry for map iterators") - } - } - } -} - -impl<'a> CowIter<'a> { - fn next_map_entry(&mut self) -> Option<(Cow<'a, Val>, Cow<'a, Val>)> { - match self { - CowIter::BorrowedMap(i) => { - let (k, v) = i.next()?; - Some((Cow::Borrowed(k), Cow::Borrowed(v))) - } - CowIter::OwnedMap(i) => { - let (k, v) = i.next()?; - Some((Cow::Owned(k), Cow::Owned(v))) - } - _ => panic!("next_map_entry called on non-map iterator"), } } } @@ -596,18 +575,8 @@ impl Call for Cx<'_> { } fn pop_iter_next(&mut self, _ty: List) { - let iter = self.iterators.last_mut().unwrap(); - match iter { - CowIter::BorrowedMap(_) | CowIter::OwnedMap(_) => { - let (key, value) = iter.next_map_entry().unwrap(); - self.stack.push(key); - self.stack.push(value); - } - _ => { - let value = iter.next().unwrap(); - self.stack.push(value); - } - } + let value = self.iterators.last_mut().unwrap().next().unwrap(); + self.stack.push(value); } fn pop_iter(&mut self, _ty: List) { @@ -635,34 +604,6 @@ impl Call for Cx<'_> { _ => invalid(), } } - - fn pop_map(&mut self, _ty: Map) -> usize { - match self.always_pop() { - Cow::Borrowed(Val::GenericMap(m)) => { - self.iterators.push(CowIter::BorrowedMap(m.iter())); - m.len() - } - Cow::Owned(Val::GenericMap(m)) => { - let ret = m.len(); - self.iterators.push(CowIter::OwnedMap(m.into_iter())); - ret - } - _ => invalid(), - } - } - - fn push_map(&mut self, _ty: Map, capacity: usize) { - self.push_own(Val::GenericMap(Vec::with_capacity(capacity))); - } - - fn map_append(&mut self, _ty: Map) { - let value = self.always_pop().into_owned(); - let key = self.always_pop().into_owned(); - match self.stack.last_mut() { - Some(Cow::Owned(Val::GenericMap(map))) => map.push((key, value)), - _ => invalid(), - } - } } #[derive(Clone, Debug, PartialEq)] @@ -693,7 +634,6 @@ pub enum Val { Variant(u32, Option>), GenericList(Vec), ByteList(Vec), - GenericMap(Vec<(Val, Val)>), } #[derive(Clone, Debug)] diff --git a/crates/wit-dylib/wit_dylib.h b/crates/wit-dylib/wit_dylib.h index 6f97600ea4..25b2b66e5e 100644 --- a/crates/wit-dylib/wit_dylib.h +++ b/crates/wit-dylib/wit_dylib.h @@ -50,7 +50,6 @@ typedef uint32_t wit_type_t; #define WIT_TYPE_FUTURE 25 #define WIT_TYPE_STREAM 26 #define WIT_TYPE_ALIAS 27 -#define WIT_TYPE_MAP 28 #define WIT_TYPE_EMPTY 0xff typedef void(*wit_import_fn_t)(void* cx); @@ -200,13 +199,6 @@ typedef struct wit_list { wit_type_t ty; } wit_list_t; -typedef struct wit_map { - const char *interface; - const char *name; - wit_type_t key_ty; - wit_type_t value_ty; -} wit_map_t; - typedef struct wit_fixed_size_list { const char *interface; const char *name; @@ -300,8 +292,6 @@ typedef struct wit { const wit_result_t *results; size_t num_lists; const wit_list_t *lists; - size_t num_maps; - const wit_map_t *maps; size_t num_fixed_size_lists; const wit_fixed_size_list_t *fixed_size_lists; size_t num_futures; @@ -412,11 +402,6 @@ void wit_dylib_push_variant(void *cx, size_t ty, uint32_t discr); // it onto the list which is then at the top of the stack. bool wit_dylib_push_list(void *cx, size_t ty, uint8_t *bytes, size_t len); void wit_dylib_list_append(void *cx, size_t ty); -// Map functions work similarly to list functions. If `wit_dylib_push_map` returns -// false then key-value pairs need to be pushed one-by-one. The `wit_dylib_map_append` -// function expects the top two stack entries to be value (top) and key (second from top). -bool wit_dylib_push_map(void *cx, size_t ty, uint8_t *bytes, size_t len); -void wit_dylib_map_append(void *cx, size_t ty); uint8_t wit_dylib_pop_u8(void *cx); uint16_t wit_dylib_pop_u16(void *cx); @@ -477,10 +462,5 @@ void wit_dylib_pop_tuple(void *cx, size_t ty); size_t wit_dylib_pop_list(void *cx, size_t ty, void **ptr); void wit_dylib_pop_iter_next(void *cx, size_t ty); void wit_dylib_pop_iter(void *cx, size_t ty); -// Map popping works similarly to list popping. Returns the number of key-value pairs. -// If `ptr` is set to non-NULL, the data is in canonical ABI format (as tuple array). -// If `ptr` is set to NULL, then an iterator is pushed to the stack and -// `wit_dylib_pop_iter_next` should be called to extract each key-value pair. -size_t wit_dylib_pop_map(void *cx, size_t ty, void **ptr); #endif From 72ea306aa41b1339c836658e5834ec7c3aac0895 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 21 Nov 2025 15:05:28 -0500 Subject: [PATCH 06/14] refactor(wasmparser): clean up match statement for List and FixedSizeList types --- crates/wasmparser/src/validator/component_types.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/wasmparser/src/validator/component_types.rs b/crates/wasmparser/src/validator/component_types.rs index 8dd9ac6234..a5c67a2086 100644 --- a/crates/wasmparser/src/validator/component_types.rs +++ b/crates/wasmparser/src/validator/component_types.rs @@ -1671,8 +1671,7 @@ impl ComponentDefinedType { ComponentDefinedType::Variant(ty) => ty.lower_gc(types, abi, options, offset, core), - ComponentDefinedType::List(ty) - | ComponentDefinedType::FixedSizeList(ty, _) => { + ComponentDefinedType::List(ty) | ComponentDefinedType::FixedSizeList(ty, _) => { let id = match core.as_concrete_ref() { Some(id) => id, None => bail!( From e6bbddd463697e084ff152b3f17dae1f9b59e9b3 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 21 Nov 2025 15:21:21 -0500 Subject: [PATCH 07/14] fix(wit-dylib): adjust memory size and update function indices in dylib smoke test output --- tests/cli/wit-dylib-smoke.wit.stdout | 76 +++++++++++++--------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/tests/cli/wit-dylib-smoke.wit.stdout b/tests/cli/wit-dylib-smoke.wit.stdout index 5b1db1d87d..aebdc25481 100644 --- a/tests/cli/wit-dylib-smoke.wit.stdout +++ b/tests/cli/wit-dylib-smoke.wit.stdout @@ -1,6 +1,6 @@ (module (@dylink.0 - (mem-info (memory 200 2) (table 1 0)) + (mem-info (memory 192 2) (table 1 0)) ) (type (;0;) (func (param i32))) (type (;1;) (func (param i32 i32 i32 i32) (result i32))) @@ -53,36 +53,33 @@ (import "env" "wit_dylib_pop_list" (func $wit_dylib_pop_list (;33;) (type 9))) (import "env" "wit_dylib_pop_iter_next" (func $wit_dylib_pop_iter_next (;34;) (type 4))) (import "env" "wit_dylib_pop_iter" (func $wit_dylib_pop_iter (;35;) (type 4))) - (import "env" "wit_dylib_pop_map" (func $wit_dylib_pop_map (;36;) (type 9))) - (import "env" "wit_dylib_push_bool" (func $wit_dylib_push_bool (;37;) (type 4))) - (import "env" "wit_dylib_push_char" (func $wit_dylib_push_char (;38;) (type 4))) - (import "env" "wit_dylib_push_u8" (func $wit_dylib_push_u8 (;39;) (type 4))) - (import "env" "wit_dylib_push_s8" (func $wit_dylib_push_s8 (;40;) (type 4))) - (import "env" "wit_dylib_push_u16" (func $wit_dylib_push_u16 (;41;) (type 4))) - (import "env" "wit_dylib_push_s16" (func $wit_dylib_push_s16 (;42;) (type 4))) - (import "env" "wit_dylib_push_u32" (func $wit_dylib_push_u32 (;43;) (type 4))) - (import "env" "wit_dylib_push_s32" (func $wit_dylib_push_s32 (;44;) (type 4))) - (import "env" "wit_dylib_push_u64" (func $wit_dylib_push_u64 (;45;) (type 10))) - (import "env" "wit_dylib_push_s64" (func $wit_dylib_push_s64 (;46;) (type 10))) - (import "env" "wit_dylib_push_f32" (func $wit_dylib_push_f32 (;47;) (type 11))) - (import "env" "wit_dylib_push_f64" (func $wit_dylib_push_f64 (;48;) (type 12))) - (import "env" "wit_dylib_push_string" (func $wit_dylib_push_string (;49;) (type 13))) - (import "env" "wit_dylib_push_record" (func $wit_dylib_push_record (;50;) (type 4))) - (import "env" "wit_dylib_push_tuple" (func $wit_dylib_push_tuple (;51;) (type 4))) - (import "env" "wit_dylib_push_flags" (func $wit_dylib_push_flags (;52;) (type 13))) - (import "env" "wit_dylib_push_enum" (func $wit_dylib_push_enum (;53;) (type 13))) - (import "env" "wit_dylib_push_borrow" (func $wit_dylib_push_borrow (;54;) (type 13))) - (import "env" "wit_dylib_push_own" (func $wit_dylib_push_own (;55;) (type 13))) - (import "env" "wit_dylib_push_future" (func $wit_dylib_push_future (;56;) (type 13))) - (import "env" "wit_dylib_push_stream" (func $wit_dylib_push_stream (;57;) (type 13))) - (import "env" "wit_dylib_push_variant" (func $wit_dylib_push_variant (;58;) (type 13))) - (import "env" "wit_dylib_push_option" (func $wit_dylib_push_option (;59;) (type 13))) - (import "env" "wit_dylib_push_result" (func $wit_dylib_push_result (;60;) (type 13))) - (import "env" "wit_dylib_push_list" (func $wit_dylib_push_list (;61;) (type 1))) - (import "env" "wit_dylib_list_append" (func $wit_dylib_list_append (;62;) (type 4))) - (import "env" "wit_dylib_push_map" (func $wit_dylib_push_map (;63;) (type 1))) - (import "env" "wit_dylib_map_append" (func $wit_dylib_map_append (;64;) (type 4))) - (import "$root" "x" (func $x (;65;) (type 14))) + (import "env" "wit_dylib_push_bool" (func $wit_dylib_push_bool (;36;) (type 4))) + (import "env" "wit_dylib_push_char" (func $wit_dylib_push_char (;37;) (type 4))) + (import "env" "wit_dylib_push_u8" (func $wit_dylib_push_u8 (;38;) (type 4))) + (import "env" "wit_dylib_push_s8" (func $wit_dylib_push_s8 (;39;) (type 4))) + (import "env" "wit_dylib_push_u16" (func $wit_dylib_push_u16 (;40;) (type 4))) + (import "env" "wit_dylib_push_s16" (func $wit_dylib_push_s16 (;41;) (type 4))) + (import "env" "wit_dylib_push_u32" (func $wit_dylib_push_u32 (;42;) (type 4))) + (import "env" "wit_dylib_push_s32" (func $wit_dylib_push_s32 (;43;) (type 4))) + (import "env" "wit_dylib_push_u64" (func $wit_dylib_push_u64 (;44;) (type 10))) + (import "env" "wit_dylib_push_s64" (func $wit_dylib_push_s64 (;45;) (type 10))) + (import "env" "wit_dylib_push_f32" (func $wit_dylib_push_f32 (;46;) (type 11))) + (import "env" "wit_dylib_push_f64" (func $wit_dylib_push_f64 (;47;) (type 12))) + (import "env" "wit_dylib_push_string" (func $wit_dylib_push_string (;48;) (type 13))) + (import "env" "wit_dylib_push_record" (func $wit_dylib_push_record (;49;) (type 4))) + (import "env" "wit_dylib_push_tuple" (func $wit_dylib_push_tuple (;50;) (type 4))) + (import "env" "wit_dylib_push_flags" (func $wit_dylib_push_flags (;51;) (type 13))) + (import "env" "wit_dylib_push_enum" (func $wit_dylib_push_enum (;52;) (type 13))) + (import "env" "wit_dylib_push_borrow" (func $wit_dylib_push_borrow (;53;) (type 13))) + (import "env" "wit_dylib_push_own" (func $wit_dylib_push_own (;54;) (type 13))) + (import "env" "wit_dylib_push_future" (func $wit_dylib_push_future (;55;) (type 13))) + (import "env" "wit_dylib_push_stream" (func $wit_dylib_push_stream (;56;) (type 13))) + (import "env" "wit_dylib_push_variant" (func $wit_dylib_push_variant (;57;) (type 13))) + (import "env" "wit_dylib_push_option" (func $wit_dylib_push_option (;58;) (type 13))) + (import "env" "wit_dylib_push_result" (func $wit_dylib_push_result (;59;) (type 13))) + (import "env" "wit_dylib_push_list" (func $wit_dylib_push_list (;60;) (type 1))) + (import "env" "wit_dylib_list_append" (func $wit_dylib_list_append (;61;) (type 4))) + (import "$root" "x" (func $x (;62;) (type 14))) (import "env" "__table_base" (global $__table_base (;0;) i32)) (import "env" "__memory_base" (global $__memory_base (;1;) i32)) (import "env" "__stack_pointer" (global $__stack_pointer (;2;) (mut i32))) @@ -94,7 +91,7 @@ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs)) (export "__wasm_call_ctors" (func $__wasm_call_ctors)) (elem (;0;) (table 0) (global.get $__table_base) func $"adapter x") - (func $"adapter x" (;66;) (type 0) (param i32) + (func $"adapter x" (;63;) (type 0) (param i32) (local i32) global.get $__stack_pointer i32.const 16 @@ -107,7 +104,7 @@ i32.add global.set $__stack_pointer ) - (func $y (;67;) (type 14) + (func $y (;64;) (type 14) (local i32 i32 i32) global.get $__stack_pointer i32.const 16 @@ -129,7 +126,7 @@ local.get 1 i32.store ) - (func $cabi_post_y (;68;) (type 14) + (func $cabi_post_y (;65;) (type 14) (local i32 i32) global.get $__stack_pointer local.set 0 @@ -150,10 +147,10 @@ i32.add global.set $__stack_pointer ) - (func $__wasm_apply_data_relocs (;69;) (type 14) + (func $__wasm_apply_data_relocs (;66;) (type 14) global.get $__memory_base global.get $__memory_base - i32.const 196 + i32.const 188 i32.add i32.store offset=4 global.get $__memory_base @@ -161,7 +158,7 @@ i32.store offset=8 global.get $__memory_base global.get $__memory_base - i32.const 198 + i32.const 190 i32.add i32.store offset=44 global.get $__memory_base @@ -175,12 +172,11 @@ i32.add i32.store offset=80 ) - (func $__wasm_call_ctors (;70;) (type 14) + (func $__wasm_call_ctors (;67;) (type 14) i32.const 64 global.get $__memory_base i32.add call $wit_dylib_initialize ) - (data (;0;) (global.get $__memory_base) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\ff\ff\ff\ff\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\ff\ff\ff\ff\02\00\00\00\01\00\00\00\00\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00x\00y\00") - (@custom "component-type" (after data) "\00asm\0d\00\01\00\00\19\16wit-component-encoding\04\00\07,\01A\02\01A\03\01@\00\01\00\03\00\01x\01\00\04\00\01y\01\00\04\00\10the:test/adapter\04\00\0b\0d\01\00\07adapter\03\00\00\00/\09producers\01\0cprocessed-by\01\0dwit-component\070.243.0") + (data (;0;) (global.get $__memory_base) "/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/ff/ff/ff/ff/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/ff/ff/ff/ff/02/00/00/00/01/00/00/00/00/00/00/00/01/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00/00x/00y/00") ) From 37dc4b9a33681c085dc823b5797f713e85a13ee2 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 15 Dec 2025 02:23:37 -0500 Subject: [PATCH 08/14] fix(wit-parser): add validation for map key types Map keys must be primitive types only. Add validation to reject non-primitive types (floats, user-defined types, etc.) as map keys. --- crates/wit-parser/src/ast/resolve.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 8fd884fde8..6e114ed33c 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1184,6 +1184,19 @@ impl<'a> Resolver<'a> { ast::Type::Map(map) => { let key_ty = self.resolve_type(&map.key, stability)?; let value_ty = self.resolve_type(&map.value, stability)?; + + match key_ty { + Type::Bool | Type::U8 | Type::U16 | Type::U32 | Type::U64 | + Type::S8 | Type::S16 | Type::S32 | Type::S64 | + Type::Char | Type::String => {} + _ => { + bail!(Error::new( + map.span, + "invalid map key type: map keys must be bool, u8, u16, u32, u64, s8, s16, s32, s64, char, or string", + )) + } + } + TypeDefKind::Map(key_ty, value_ty) } ast::Type::FixedSizeList(list) => { From 00afdb174b3dfdd2c85b9cfd4e1db252279ca195 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 15 Dec 2025 03:02:47 -0500 Subject: [PATCH 09/14] chore: format code --- crates/wit-parser/src/ast/resolve.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/wit-parser/src/ast/resolve.rs b/crates/wit-parser/src/ast/resolve.rs index 6e114ed33c..83e27334ac 100644 --- a/crates/wit-parser/src/ast/resolve.rs +++ b/crates/wit-parser/src/ast/resolve.rs @@ -1186,9 +1186,17 @@ impl<'a> Resolver<'a> { let value_ty = self.resolve_type(&map.value, stability)?; match key_ty { - Type::Bool | Type::U8 | Type::U16 | Type::U32 | Type::U64 | - Type::S8 | Type::S16 | Type::S32 | Type::S64 | - Type::Char | Type::String => {} + Type::Bool + | Type::U8 + | Type::U16 + | Type::U32 + | Type::U64 + | Type::S8 + | Type::S16 + | Type::S32 + | Type::S64 + | Type::Char + | Type::String => {} _ => { bail!(Error::new( map.span, From ee6b3d7bd9b2b49af047f02a49c48ed07692370d Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 19 Dec 2025 12:44:36 -0500 Subject: [PATCH 10/14] feat(wasmparser): add feature flag support for component model map feature --- crates/wasmparser/src/features.rs | 5 +++++ crates/wasmparser/src/validator/component.rs | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/wasmparser/src/features.rs b/crates/wasmparser/src/features.rs index 072fc629bc..3d83150727 100644 --- a/crates/wasmparser/src/features.rs +++ b/crates/wasmparser/src/features.rs @@ -286,6 +286,11 @@ define_wasm_features! { /// Corresponds to the 🛸 character in /// . pub cm_gc: CM_GC(1 << 33) = false; + /// Support for maps in the component model proposal. + /// + /// Corresponds to the 🗺️ character in + /// . + pub cm_map: CM_MAP(1 << 37) = false; /// Subset of the reference-types WebAssembly proposal which only /// encompasses the leb-encoding of the table immediate to the diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index cae44f044d..ec74700111 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -3920,10 +3920,18 @@ impl ComponentState { crate::ComponentDefinedType::List(ty) => Ok(ComponentDefinedType::List( self.create_component_val_type(ty, offset)?, )), - crate::ComponentDefinedType::Map(key, value) => Ok(ComponentDefinedType::Map( - self.create_component_val_type(key, offset)?, - self.create_component_val_type(value, offset)?, - )), + crate::ComponentDefinedType::Map(key, value) => { + if !self.features.cm_map() { + bail!( + offset, + "Maps require the component model map feature" + ) + } + Ok(ComponentDefinedType::Map( + self.create_component_val_type(key, offset)?, + self.create_component_val_type(value, offset)?, + )) + }, crate::ComponentDefinedType::FixedSizeList(ty, elements) => { if !self.features.cm_fixed_size_list() { bail!( From 2a24e34ac599f4f08962f48f9ded8f8a74f6d23d Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 19 Dec 2025 12:47:39 -0500 Subject: [PATCH 11/14] feat(wast): add support for map custom keyword and encoding in component model Signed-off-by: Yordis Prieto --- crates/wasmparser/src/validator/component.rs | 7 +- crates/wast/src/component/binary.rs | 3 + crates/wast/src/component/expand.rs | 4 ++ crates/wast/src/component/resolve.rs | 4 ++ crates/wast/src/component/types.rs | 25 +++++++ crates/wast/src/lib.rs | 1 + tests/cli/component-model/map.wast | 69 +++++++++++++++++++ .../missing-features/component-model/map.wast | 39 +++++++++++ .../cli/validate-unknown-features.wat.stderr | 2 +- 9 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 tests/cli/component-model/map.wast create mode 100644 tests/cli/missing-features/component-model/map.wast diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index ec74700111..f65db8c3f9 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -3922,16 +3922,13 @@ impl ComponentState { )), crate::ComponentDefinedType::Map(key, value) => { if !self.features.cm_map() { - bail!( - offset, - "Maps require the component model map feature" - ) + bail!(offset, "Maps require the component model map feature") } Ok(ComponentDefinedType::Map( self.create_component_val_type(key, offset)?, self.create_component_val_type(value, offset)?, )) - }, + } crate::ComponentDefinedType::FixedSizeList(ty, elements) => { if !self.features.cm_fixed_size_list() { bail!( diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs index d779e93cd1..02427e33a3 100644 --- a/crates/wast/src/component/binary.rs +++ b/crates/wast/src/component/binary.rs @@ -109,6 +109,9 @@ fn encode_defined_type(encoder: ComponentDefinedTypeEncoder, ty: &ComponentDefin ComponentDefinedType::List(l) => { encoder.list(l.element.as_ref()); } + ComponentDefinedType::Map(m) => { + encoder.map(m.key.as_ref(), m.value.as_ref()); + } ComponentDefinedType::FixedSizeList(l) => { encoder.fixed_size_list(l.element.as_ref(), l.elements); } diff --git a/crates/wast/src/component/expand.rs b/crates/wast/src/component/expand.rs index 6110f30754..5c8b68e64b 100644 --- a/crates/wast/src/component/expand.rs +++ b/crates/wast/src/component/expand.rs @@ -559,6 +559,10 @@ impl<'a> Expander<'a> { }) => { self.expand_component_val_ty(t); } + ComponentDefinedType::Map(Map { key: k, value: v }) => { + self.expand_component_val_ty(k); + self.expand_component_val_ty(v); + } ComponentDefinedType::Tuple(t) => { for field in t.fields.iter_mut() { self.expand_component_val_ty(field); diff --git a/crates/wast/src/component/resolve.rs b/crates/wast/src/component/resolve.rs index 15c90e274a..9855493094 100644 --- a/crates/wast/src/component/resolve.rs +++ b/crates/wast/src/component/resolve.rs @@ -566,6 +566,10 @@ impl<'a> Resolver<'a> { }) => { self.component_val_type(t)?; } + ComponentDefinedType::Map(Map { key: k, value: v }) => { + self.component_val_type(k)?; + self.component_val_type(v)?; + } ComponentDefinedType::Tuple(t) => { for field in t.fields.iter_mut() { self.component_val_type(field)?; diff --git a/crates/wast/src/component/types.rs b/crates/wast/src/component/types.rs index dc2246e75e..1771044f6c 100644 --- a/crates/wast/src/component/types.rs +++ b/crates/wast/src/component/types.rs @@ -386,6 +386,7 @@ pub enum ComponentDefinedType<'a> { Record(Record<'a>), Variant(Variant<'a>), List(List<'a>), + Map(Map<'a>), FixedSizeList(FixedSizeList<'a>), Tuple(Tuple<'a>), Flags(Flags<'a>), @@ -407,6 +408,8 @@ impl<'a> ComponentDefinedType<'a> { Ok(Self::Variant(parser.parse()?)) } else if l.peek::()? { parse_list(parser) + } else if l.peek::()? { + Ok(Self::Map(parser.parse()?)) } else if l.peek::()? { Ok(Self::Tuple(parser.parse()?)) } else if l.peek::()? { @@ -451,6 +454,7 @@ impl Peek for ComponentDefinedType<'_> { Some(("record", _)) | Some(("variant", _)) | Some(("list", _)) + | Some(("map", _)) | Some(("tuple", _)) | Some(("flags", _)) | Some(("enum", _)) @@ -587,6 +591,15 @@ pub struct List<'a> { pub element: Box>, } +/// A map type. +#[derive(Debug)] +pub struct Map<'a> { + /// The key type of the map. + pub key: Box>, + /// The value type of the map. + pub value: Box>, +} + /// A fixed size list type. #[derive(Debug)] pub struct FixedSizeList<'a> { @@ -612,6 +625,18 @@ fn parse_list<'a>(parser: Parser<'a>) -> Result> { } } +impl<'a> Parse<'a> for Map<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let key = parser.parse()?; + let value = parser.parse()?; + Ok(Self { + key: Box::new(key), + value: Box::new(value), + }) + } +} + /// A tuple type. #[derive(Debug)] pub struct Tuple<'a> { diff --git a/crates/wast/src/lib.rs b/crates/wast/src/lib.rs index eab30136bf..4558b31b13 100644 --- a/crates/wast/src/lib.rs +++ b/crates/wast/src/lib.rs @@ -533,6 +533,7 @@ pub mod kw { custom_keyword!(option); custom_keyword!(tuple); custom_keyword!(list); + custom_keyword!(map); custom_keyword!(error); custom_keyword!(canon); custom_keyword!(lift); diff --git a/tests/cli/component-model/map.wast b/tests/cli/component-model/map.wast new file mode 100644 index 0000000000..feca7ed5fc --- /dev/null +++ b/tests/cli/component-model/map.wast @@ -0,0 +1,69 @@ +;; RUN: wast % --assert default --snapshot tests/snapshots -f cm-map + +(component + (core module $m + (memory (export "memory") 1) + (func (export "ret-map") (result i32 i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "ret-map") (result (map string u32)) + (canon lift (core func $i "ret-map") (memory $i "memory")) + ) +) + +(component + (core module $m + (func (export "param-map") (param i32 i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "param-map") (param "m" (map string u32)) + (canon lift (core func $i "param-map")) + ) +) + +(component + (type $map-type (map u32 string)) + (func (export "f") (param "x" $map-type)) +) + +(component + (type $nested-map (map string (map string u32))) + (func (export "f") (param "x" $nested-map)) +) + +(component + (type $map-with-list (map string (list u32))) + (func (export "f") (param "x" $map-with-list)) +) + +(component + (type $map-with-option (map u32 (option string))) + (func (export "f") (param "x" $map-with-option)) +) + +(assert_invalid + (component + (import "y" (component $c + (type $t (map string u32)) + (import "x" (type (eq $t))) + )) + + (type $x (map u32 string)) + (instance (instantiate $c (with "x" (type $x)))) + ) + "type mismatch for import `x`") + +(assert_invalid + (component + (import "y" (component $c + (type $t (map string u32)) + (import "x" (type (eq $t))) + )) + + (type $x (list u32)) + (instance (instantiate $c (with "x" (type $x)))) + ) + "type mismatch for import `x`") + diff --git a/tests/cli/missing-features/component-model/map.wast b/tests/cli/missing-features/component-model/map.wast new file mode 100644 index 0000000000..b750734649 --- /dev/null +++ b/tests/cli/missing-features/component-model/map.wast @@ -0,0 +1,39 @@ +;; RUN: wast % --assert default --snapshot tests/snapshots + +(assert_invalid + (component + (core module $m + (memory (export "memory") 1) + (func (export "ret-map") (result i32 i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "ret-map") (result (map string u32)) + (canon lift (core func $i "ret-map") (memory $i "memory")) + ) + ) + "Maps require the component model map feature (at offset 0x54)" +) + +(assert_invalid + (component + (core module $m + (func (export "param-map") (param i32 i32) unreachable) + ) + (core instance $i (instantiate $m)) + + (func (export "param-map") (param "m" (map string u32)) + (canon lift (core func $i "param-map")) + ) + ) + "Maps require the component model map feature" +) + +(assert_invalid + (component + (type $map-type (map u32 string)) + (func (export "f") (param "x" $map-type)) + ) + "Maps require the component model map feature" +) + diff --git a/tests/cli/validate-unknown-features.wat.stderr b/tests/cli/validate-unknown-features.wat.stderr index 6e93f466e6..2c4ca9315e 100644 --- a/tests/cli/validate-unknown-features.wat.stderr +++ b/tests/cli/validate-unknown-features.wat.stderr @@ -1,4 +1,4 @@ error: invalid value 'unknown' for '--features ': unknown feature `unknown` -Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, cm-values, cm-nested-names, cm-async, cm-async-stackful, cm-async-builtins, cm-threading, cm-error-context, cm-fixed-size-list, cm-gc, call-indirect-overlong, bulk-memory-opt, custom-descriptors, compact-imports, mvp, wasm1, wasm2, wasm3, lime1, all +Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, cm-values, cm-nested-names, cm-async, cm-async-stackful, cm-async-builtins, cm-threading, cm-error-context, cm-fixed-size-list, cm-gc, cm-map, call-indirect-overlong, bulk-memory-opt, custom-descriptors, compact-imports, mvp, wasm1, wasm2, wasm3, lime1, all For more information, try '--help'. From 4f3e3865c1ef9e87c630089afd4d14df32c52cf9 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 19 Dec 2025 17:49:14 -0500 Subject: [PATCH 12/14] fix(tests): update WAST files to use imports for map functions and add snapshots --- crates/wit-component/tests/interfaces.rs | 3 +- tests/cli/component-model/map.wast | 14 +++-- .../missing-features/component-model/map.wast | 2 +- .../cli/component-model/map.wast.json | 55 +++++++++++++++++++ .../cli/component-model/map.wast/0.print | 18 ++++++ .../cli/component-model/map.wast/1.print | 24 ++++++++ .../cli/component-model/map.wast/2.print | 5 ++ .../cli/component-model/map.wast/3.print | 6 ++ .../cli/component-model/map.wast/4.print | 6 ++ .../cli/component-model/map.wast/5.print | 6 ++ .../component-model/map.wast.json | 26 +++++++++ 11 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 tests/snapshots/cli/component-model/map.wast.json create mode 100644 tests/snapshots/cli/component-model/map.wast/0.print create mode 100644 tests/snapshots/cli/component-model/map.wast/1.print create mode 100644 tests/snapshots/cli/component-model/map.wast/2.print create mode 100644 tests/snapshots/cli/component-model/map.wast/3.print create mode 100644 tests/snapshots/cli/component-model/map.wast/4.print create mode 100644 tests/snapshots/cli/component-model/map.wast/5.print create mode 100644 tests/snapshots/cli/missing-features/component-model/map.wast.json diff --git a/crates/wit-component/tests/interfaces.rs b/crates/wit-component/tests/interfaces.rs index 2345986557..ad65b4a250 100644 --- a/crates/wit-component/tests/interfaces.rs +++ b/crates/wit-component/tests/interfaces.rs @@ -3,6 +3,7 @@ use libtest_mimic::{Arguments, Trial}; use pretty_assertions::assert_eq; use std::fs; use std::path::Path; +use wasmparser::WasmFeatures; use wit_component::WitPrinter; use wit_parser::{PackageId, Resolve, UnresolvedPackageGroup}; @@ -61,7 +62,7 @@ fn run_test(path: &Path, is_dir: bool) -> Result<()> { let wasm = wit_component::encode(&resolve, package)?; let wat = wasmprinter::print_bytes(&wasm)?; assert_output(&path.with_extension("wat"), &wat)?; - wasmparser::Validator::new() + wasmparser::Validator::new_with_features(WasmFeatures::all()) .validate_all(&wasm) .context("failed to validate wasm output")?; diff --git a/tests/cli/component-model/map.wast b/tests/cli/component-model/map.wast index feca7ed5fc..dc3c620727 100644 --- a/tests/cli/component-model/map.wast +++ b/tests/cli/component-model/map.wast @@ -3,7 +3,7 @@ (component (core module $m (memory (export "memory") 1) - (func (export "ret-map") (result i32 i32) unreachable) + (func (export "ret-map") (result i32) unreachable) ) (core instance $i (instantiate $m)) @@ -14,33 +14,35 @@ (component (core module $m + (memory (export "memory") 1) (func (export "param-map") (param i32 i32) unreachable) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable) ) (core instance $i (instantiate $m)) (func (export "param-map") (param "m" (map string u32)) - (canon lift (core func $i "param-map")) + (canon lift (core func $i "param-map") (memory $i "memory") (realloc (func $i "realloc"))) ) ) (component (type $map-type (map u32 string)) - (func (export "f") (param "x" $map-type)) + (import "f" (func (param "x" $map-type))) ) (component (type $nested-map (map string (map string u32))) - (func (export "f") (param "x" $nested-map)) + (import "f" (func (param "x" $nested-map))) ) (component (type $map-with-list (map string (list u32))) - (func (export "f") (param "x" $map-with-list)) + (import "f" (func (param "x" $map-with-list))) ) (component (type $map-with-option (map u32 (option string))) - (func (export "f") (param "x" $map-with-option)) + (import "f" (func (param "x" $map-with-option))) ) (assert_invalid diff --git a/tests/cli/missing-features/component-model/map.wast b/tests/cli/missing-features/component-model/map.wast index b750734649..8dda8e08cb 100644 --- a/tests/cli/missing-features/component-model/map.wast +++ b/tests/cli/missing-features/component-model/map.wast @@ -32,7 +32,7 @@ (assert_invalid (component (type $map-type (map u32 string)) - (func (export "f") (param "x" $map-type)) + (import "f" (func (param "x" $map-type))) ) "Maps require the component model map feature" ) diff --git a/tests/snapshots/cli/component-model/map.wast.json b/tests/snapshots/cli/component-model/map.wast.json new file mode 100644 index 0000000000..81057a8aa6 --- /dev/null +++ b/tests/snapshots/cli/component-model/map.wast.json @@ -0,0 +1,55 @@ +{ + "source_filename": "tests/cli/component-model/map.wast", + "commands": [ + { + "type": "module", + "line": 3, + "filename": "map.0.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 15, + "filename": "map.1.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 28, + "filename": "map.2.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 33, + "filename": "map.3.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 38, + "filename": "map.4.wasm", + "module_type": "binary" + }, + { + "type": "module", + "line": 43, + "filename": "map.5.wasm", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 49, + "filename": "map.6.wasm", + "module_type": "binary", + "text": "type mismatch for import `x`" + }, + { + "type": "assert_invalid", + "line": 61, + "filename": "map.7.wasm", + "module_type": "binary", + "text": "type mismatch for import `x`" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/cli/component-model/map.wast/0.print b/tests/snapshots/cli/component-model/map.wast/0.print new file mode 100644 index 0000000000..7754933b83 --- /dev/null +++ b/tests/snapshots/cli/component-model/map.wast/0.print @@ -0,0 +1,18 @@ +(component + (core module $m (;0;) + (type (;0;) (func (result i32))) + (memory (;0;) 1) + (export "memory" (memory 0)) + (export "ret-map" (func 0)) + (func (;0;) (type 0) (result i32) + unreachable + ) + ) + (core instance $i (;0;) (instantiate $m)) + (type (;0;) (map string u32)) + (type (;1;) (func (result 0))) + (alias core export $i "ret-map" (core func (;0;))) + (alias core export $i "memory" (core memory (;0;))) + (func (;0;) (type 1) (canon lift (core func 0) (memory 0))) + (export (;1;) "ret-map" (func 0)) +) diff --git a/tests/snapshots/cli/component-model/map.wast/1.print b/tests/snapshots/cli/component-model/map.wast/1.print new file mode 100644 index 0000000000..9d89f975a6 --- /dev/null +++ b/tests/snapshots/cli/component-model/map.wast/1.print @@ -0,0 +1,24 @@ +(component + (core module $m (;0;) + (type (;0;) (func (param i32 i32))) + (type (;1;) (func (param i32 i32 i32 i32) (result i32))) + (memory (;0;) 1) + (export "memory" (memory 0)) + (export "param-map" (func 0)) + (export "realloc" (func 1)) + (func (;0;) (type 0) (param i32 i32) + unreachable + ) + (func (;1;) (type 1) (param i32 i32 i32 i32) (result i32) + unreachable + ) + ) + (core instance $i (;0;) (instantiate $m)) + (type (;0;) (map string u32)) + (type (;1;) (func (param "m" 0))) + (alias core export $i "param-map" (core func (;0;))) + (alias core export $i "memory" (core memory (;0;))) + (alias core export $i "realloc" (core func (;1;))) + (func (;0;) (type 1) (canon lift (core func 0) (memory 0) (realloc 1))) + (export (;1;) "param-map" (func 0)) +) diff --git a/tests/snapshots/cli/component-model/map.wast/2.print b/tests/snapshots/cli/component-model/map.wast/2.print new file mode 100644 index 0000000000..001d2b563e --- /dev/null +++ b/tests/snapshots/cli/component-model/map.wast/2.print @@ -0,0 +1,5 @@ +(component + (type $map-type (;0;) (map u32 string)) + (type (;1;) (func (param "x" $map-type))) + (import "f" (func (;0;) (type 1))) +) diff --git a/tests/snapshots/cli/component-model/map.wast/3.print b/tests/snapshots/cli/component-model/map.wast/3.print new file mode 100644 index 0000000000..c32daa8672 --- /dev/null +++ b/tests/snapshots/cli/component-model/map.wast/3.print @@ -0,0 +1,6 @@ +(component + (type (;0;) (map string u32)) + (type $nested-map (;1;) (map string 0)) + (type (;2;) (func (param "x" $nested-map))) + (import "f" (func (;0;) (type 2))) +) diff --git a/tests/snapshots/cli/component-model/map.wast/4.print b/tests/snapshots/cli/component-model/map.wast/4.print new file mode 100644 index 0000000000..6471ff5237 --- /dev/null +++ b/tests/snapshots/cli/component-model/map.wast/4.print @@ -0,0 +1,6 @@ +(component + (type (;0;) (list u32)) + (type $map-with-list (;1;) (map string 0)) + (type (;2;) (func (param "x" $map-with-list))) + (import "f" (func (;0;) (type 2))) +) diff --git a/tests/snapshots/cli/component-model/map.wast/5.print b/tests/snapshots/cli/component-model/map.wast/5.print new file mode 100644 index 0000000000..0c52e749ed --- /dev/null +++ b/tests/snapshots/cli/component-model/map.wast/5.print @@ -0,0 +1,6 @@ +(component + (type (;0;) (option string)) + (type $map-with-option (;1;) (map u32 0)) + (type (;2;) (func (param "x" $map-with-option))) + (import "f" (func (;0;) (type 2))) +) diff --git a/tests/snapshots/cli/missing-features/component-model/map.wast.json b/tests/snapshots/cli/missing-features/component-model/map.wast.json new file mode 100644 index 0000000000..c237bb80b8 --- /dev/null +++ b/tests/snapshots/cli/missing-features/component-model/map.wast.json @@ -0,0 +1,26 @@ +{ + "source_filename": "tests/cli/missing-features/component-model/map.wast", + "commands": [ + { + "type": "assert_invalid", + "line": 4, + "filename": "map.0.wasm", + "module_type": "binary", + "text": "Maps require the component model map feature (at offset 0x54)" + }, + { + "type": "assert_invalid", + "line": 19, + "filename": "map.1.wasm", + "module_type": "binary", + "text": "Maps require the component model map feature" + }, + { + "type": "assert_invalid", + "line": 33, + "filename": "map.2.wasm", + "module_type": "binary", + "text": "Maps require the component model map feature" + } + ] +} \ No newline at end of file From 23f1581f22b9d7604dac30bf7b821634a651941e Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 22 Dec 2025 12:56:21 -0500 Subject: [PATCH 13/14] fix(wasmparser): update cm_map feature flag to new encoding and adjust documentation --- crates/wasmparser/src/features.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/wasmparser/src/features.rs b/crates/wasmparser/src/features.rs index 3d83150727..70fe3c8f7b 100644 --- a/crates/wasmparser/src/features.rs +++ b/crates/wasmparser/src/features.rs @@ -286,11 +286,6 @@ define_wasm_features! { /// Corresponds to the 🛸 character in /// . pub cm_gc: CM_GC(1 << 33) = false; - /// Support for maps in the component model proposal. - /// - /// Corresponds to the 🗺️ character in - /// . - pub cm_map: CM_MAP(1 << 37) = false; /// Subset of the reference-types WebAssembly proposal which only /// encompasses the leb-encoding of the table immediate to the @@ -311,6 +306,12 @@ define_wasm_features! { // Compact import section proposal. pub compact_imports: COMPACT_IMPORTS(1 << 37) = false; + + /// Support for maps in the component model proposal. + /// + /// Corresponds to the 🗺️ character in + /// . + pub cm_map: CM_MAP(1 << 38) = false; } } From fe58298acafccabd01f637428ca891a2abc95fa5 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 22 Dec 2025 12:59:02 -0500 Subject: [PATCH 14/14] fix(tests): update error message for unknown features in validation test --- tests/cli/validate-unknown-features.wat.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cli/validate-unknown-features.wat.stderr b/tests/cli/validate-unknown-features.wat.stderr index 2c4ca9315e..22b6c7a6f6 100644 --- a/tests/cli/validate-unknown-features.wat.stderr +++ b/tests/cli/validate-unknown-features.wat.stderr @@ -1,4 +1,4 @@ error: invalid value 'unknown' for '--features ': unknown feature `unknown` -Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, cm-values, cm-nested-names, cm-async, cm-async-stackful, cm-async-builtins, cm-threading, cm-error-context, cm-fixed-size-list, cm-gc, cm-map, call-indirect-overlong, bulk-memory-opt, custom-descriptors, compact-imports, mvp, wasm1, wasm2, wasm3, lime1, all +Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, cm-values, cm-nested-names, cm-async, cm-async-stackful, cm-async-builtins, cm-threading, cm-error-context, cm-fixed-size-list, cm-gc, call-indirect-overlong, bulk-memory-opt, custom-descriptors, compact-imports, cm-map, mvp, wasm1, wasm2, wasm3, lime1, all For more information, try '--help'.