From 0a302d822659ca4ad259d8a3b16489dcb9cfe10d Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 10 Mar 2026 23:19:12 -0400 Subject: [PATCH 01/11] feat: add map support across backends Implement map type rendering plus lowering/lifting/deallocation support across the C, C++, C#, Go, MoonBit, and Markdown backends, and add map codegen/runtime tests. This aligns non-Rust generators with core map ABI support and fixes the Go test harness module replacement path needed for map codegen verification. Signed-off-by: Yordis Prieto --- crates/c/src/lib.rs | 107 +++++++++++++++++-- crates/core/src/abi.rs | 103 ++++++++++++++++-- crates/core/src/lib.rs | 12 ++- crates/core/src/types.rs | 6 +- crates/cpp/src/lib.rs | 180 +++++++++++++++++++++++++++++++- crates/csharp/src/function.rs | 166 +++++++++++++++++++++++++++++ crates/csharp/src/interface.rs | 15 +++ crates/go/src/lib.rs | 81 ++++++++++++++ crates/guest-rust/src/rt/mod.rs | 5 + crates/markdown/src/lib.rs | 12 ++- crates/moonbit/src/lib.rs | 134 ++++++++++++++++++++++++ crates/moonbit/src/pkg.rs | 5 + crates/rust/src/bindgen.rs | 127 ++++++++++++++++++++++ crates/rust/src/interface.rs | 51 +++++++++ crates/rust/src/lib.rs | 5 + crates/test/src/go.rs | 2 +- tests/codegen/map.wit | 21 ++++ tests/runtime/map/runner.rs | 35 +++++++ tests/runtime/map/test.rs | 26 +++++ tests/runtime/map/test.wit | 20 ++++ 20 files changed, 1093 insertions(+), 20 deletions(-) create mode 100644 tests/codegen/map.wit create mode 100644 tests/runtime/map/runner.rs create mode 100644 tests/runtime/map/test.rs create mode 100644 tests/runtime/map/test.wit diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 2d53d23dd..3f98742db 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -1088,7 +1088,7 @@ fn is_prim_type_id(resolve: &Resolve, id: TypeId) -> bool { | TypeDefKind::Stream(_) | TypeDefKind::Unknown => false, TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(..) => todo!(), + TypeDefKind::Map(key, value) => is_prim_type(resolve, key) && is_prim_type(resolve, value), } } @@ -1174,7 +1174,12 @@ pub fn push_ty_name(resolve: &Resolve, ty: &Type, src: &mut String) { } TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(..) => todo!(), + TypeDefKind::Map(key, value) => { + src.push_str("map_"); + push_ty_name(resolve, key, src); + src.push_str("_"); + push_ty_name(resolve, value, src); + } } } } @@ -1383,12 +1388,12 @@ impl Return { TypeDefKind::Tuple(_) | TypeDefKind::Record(_) | TypeDefKind::List(_) + | TypeDefKind::Map(_, _) | TypeDefKind::Variant(_) => {} TypeDefKind::Resource => todo!("return_single for resource"), TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(..) => todo!(), } self.retptrs.push(*orig_ty); @@ -1735,6 +1740,23 @@ void __wasm_export_{ns}_{snake}_dtor({ns}_{snake}_t* arg) {{ self.finish_typedef_struct(id); } + fn type_map(&mut self, id: TypeId, _name: &str, key: &Type, value: &Type, docs: &Docs) { + self.src.h_defs("\n"); + self.docs(docs, SourceType::HDefs); + let map_ty = self.r#gen.type_name(&Type::Id(id)); + let entry_ty = format!("{map_ty}_entry_t"); + uwriteln!(self.src.h_defs, "typedef struct {entry_ty} {{"); + self.print_ty(SourceType::HDefs, key); + self.src.h_defs(" key;\n"); + self.print_ty(SourceType::HDefs, value); + self.src.h_defs(" value;\n"); + uwriteln!(self.src.h_defs, "}} {entry_ty};"); + self.start_typedef_struct(id); + uwriteln!(self.src.h_defs, "{entry_ty} *ptr;"); + self.src.h_defs("size_t len;\n"); + self.finish_typedef_struct(id); + } + fn type_fixed_length_list( &mut self, _id: TypeId, @@ -1844,6 +1866,24 @@ impl<'a> wit_bindgen_core::AnonymousTypeGenerator<'a> for InterfaceGenerator<'a> self.print_typedef_target(id); } + fn anonymous_type_map(&mut self, id: TypeId, key: &Type, value: &Type, _docs: &Docs) { + let map_ty = self.r#gen.type_name(&Type::Id(id)); + let entry_ty = format!("{map_ty}_entry_t"); + uwriteln!(self.src.h_defs, "\ntypedef struct {entry_ty} {{"); + self.print_ty(SourceType::HDefs, key); + self.src.h_defs(" key;\n"); + self.print_ty(SourceType::HDefs, value); + self.src.h_defs(" value;\n"); + uwriteln!(self.src.h_defs, "}} {entry_ty};"); + self.src.h_defs("\ntypedef "); + self.src.h_defs("struct {\n"); + uwriteln!(self.src.h_defs, "{entry_ty} *ptr;"); + self.src.h_defs("size_t len;\n"); + self.src.h_defs("}"); + self.src.h_defs(" "); + self.print_typedef_target(id); + } + fn anonymous_type_future(&mut self, id: TypeId, _ty: &Option, _docs: &Docs) { self.src.h_defs("\ntypedef uint32_t "); self.print_typedef_target(id); @@ -2005,6 +2045,21 @@ impl InterfaceGenerator<'_> { uwriteln!(self.src.c_helpers, "}}"); } + TypeDefKind::Map(key, value) => { + self.src.c_helpers("size_t map_len = ptr->len;\n"); + uwriteln!(self.src.c_helpers, "if (map_len > 0) {{"); + let map_ty = self.r#gen.type_name(&Type::Id(id)); + let entry_ty = format!("{map_ty}_entry_t"); + uwriteln!(self.src.c_helpers, "{entry_ty} *map_ptr = ptr->ptr;"); + self.src + .c_helpers("for (size_t i = 0; i < map_len; i++) {\n"); + self.free(key, "&map_ptr[i].key"); + self.free(value, "&map_ptr[i].value"); + self.src.c_helpers("}\n"); + uwriteln!(self.src.c_helpers, "free(map_ptr);"); + uwriteln!(self.src.c_helpers, "}}"); + } + TypeDefKind::Variant(v) => { self.src.c_helpers("switch ((int32_t) ptr->tag) {\n"); for (i, case) in v.cases.iter().enumerate() { @@ -2045,7 +2100,6 @@ impl InterfaceGenerator<'_> { } TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(..) => todo!(), } if c_helpers_body_start == self.src.c_helpers.len() { self.src.c_helpers.as_mut_string().truncate(c_helpers_start); @@ -2739,7 +2793,9 @@ void {name}_return({return_ty}) {{ TypeDefKind::Unknown => false, TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(..) => todo!(), + TypeDefKind::Map(key, value) => { + self.contains_droppable_borrow(key) || self.contains_droppable_borrow(value) + } } } else { false @@ -3609,6 +3665,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!("(uint8_t *) ({}).ptr", operands[0])); results.push(format!("({}).len", operands[0])); } + Instruction::MapLower { .. } => { + let _body = self.blocks.pop().unwrap(); + results.push(format!("(uint8_t *) ({}).ptr", operands[0])); + results.push(format!("({}).len", operands[0])); + } Instruction::ListLift { element, ty, .. } => { self.assert_no_droppable_borrows("list", &Type::Id(*ty)); @@ -3621,7 +3682,19 @@ impl Bindgen for FunctionBindgen<'_, '_> { list_name, elem_name, operands[0], operands[1] )); } + Instruction::MapLift { ty, .. } => { + let _body = self.blocks.pop().unwrap(); + self.assert_no_droppable_borrows("map", &Type::Id(*ty)); + let map_name = self.r#gen.r#gen.type_name(&Type::Id(*ty)); + let entry_name = format!("{map_name}_entry_t"); + results.push(format!( + "({}) {{ ({}*)({}), ({}) }}", + map_name, entry_name, operands[0], operands[1] + )); + } Instruction::IterElem { .. } => results.push("e".to_string()), + Instruction::IterMapKey { .. } => results.push("map_key".to_string()), + Instruction::IterMapValue { .. } => results.push("map_value".to_string()), Instruction::IterBasePointer => results.push("base".to_string()), Instruction::CallWasm { sig, .. } => { @@ -3956,6 +4029,28 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "free({ptr});"); uwriteln!(self.src, "}}"); } + Instruction::GuestDeallocateMap { key, value } => { + let (body, results) = self.blocks.pop().unwrap(); + assert!(results.is_empty()); + let len = self.locals.tmp("len"); + uwriteln!(self.src, "size_t {len} = {};", operands[1]); + uwriteln!(self.src, "if ({len} > 0) {{"); + let ptr = self.locals.tmp("ptr"); + uwriteln!(self.src, "uint8_t *{ptr} = {};", operands[0]); + let i = self.locals.tmp("i"); + uwriteln!(self.src, "for (size_t {i} = 0; {i} < {len}; {i}++) {{"); + let size = self.r#gen.r#gen.sizes.record([*key, *value]).size; + uwriteln!( + self.src, + "uint8_t *base = {ptr} + {i} * {};", + size.format(POINTER_SIZE_EXPRESSION) + ); + uwriteln!(self.src, "(void) base;"); + uwrite!(self.src, "{body}"); + uwriteln!(self.src, "}}"); + uwriteln!(self.src, "free({ptr});"); + uwriteln!(self.src, "}}"); + } Instruction::Flush { amt } => { results.extend(operands.iter().take(*amt).cloned()); @@ -4109,7 +4204,7 @@ pub fn is_arg_by_pointer(resolve: &Resolve, ty: &Type) -> bool { TypeDefKind::Resource => todo!("is_arg_by_pointer for resource"), TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(..) => todo!(), + TypeDefKind::Map(..) => true, }, Type::String => true, _ => false, diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index 7d625f222..f771b6b0a 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -310,6 +310,28 @@ def_instruction! { ty: TypeId, } : [2] => [1], + /// Lowers a map into a canonical pointer/length pair. + /// + /// This operation pops a map value from the stack and pushes pointer + /// and length. A block is popped from the block stack to lower one + /// key/value entry into linear memory. + MapLower { + key: &'a Type, + value: &'a Type, + realloc: Option<&'a str>, + } : [1] => [2], + + /// Lifts a canonical pointer/length pair into a map. + /// + /// This operation consumes pointer and length from the stack. A block + /// is popped from the block stack and must produce key/value for one + /// map entry. + MapLift { + key: &'a Type, + value: &'a Type, + ty: TypeId, + } : [2] => [1], + /// Pops all fields for a fixed list off the stack and then composes them /// into an array. FixedLengthListLift { @@ -349,6 +371,14 @@ def_instruction! { /// This is only used inside of blocks related to lowering lists. IterElem { element: &'a Type } : [0] => [1], + /// Pushes an operand onto the stack representing the current map key + /// for each map iteration. + IterMapKey { key: &'a Type } : [0] => [1], + + /// Pushes an operand onto the stack representing the current map value + /// for each map iteration. + IterMapValue { value: &'a Type } : [0] => [1], + /// Pushes an operand onto the stack representing the base pointer of /// the next element in a list. /// @@ -581,6 +611,17 @@ def_instruction! { element: &'a Type, } : [2] => [0], + /// Used exclusively for guest-code generation this indicates that a + /// map is being deallocated. The ptr/length are on the stack and are + /// popped off and used to deallocate the map entry buffer. + /// + /// This variant also pops a block off the block stack to be used as + /// the body of the deallocation loop over map entries. + GuestDeallocateMap { + key: &'a Type, + value: &'a Type, + } : [2] => [0], + /// Used exclusively for guest-code generation this indicates that /// a variant is being deallocated. The integer discriminant is popped /// off the stack as well as `blocks` number of blocks popped from the @@ -875,7 +916,7 @@ fn needs_deallocate(resolve: &Resolve, ty: &Type, what: Deallocate) -> bool { TypeDefKind::Future(_) | TypeDefKind::Stream(_) => what.handles(), TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(t, _) => needs_deallocate(resolve, t, what), - TypeDefKind::Map(..) => todo!(), + TypeDefKind::Map(_, _) => true, }, Type::Bool @@ -1618,7 +1659,25 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.lower(ty); } } - TypeDefKind::Map(..) => todo!(), + TypeDefKind::Map(key, value) => { + let realloc = self.list_realloc(); + let value_offset = self.bindgen.sizes().field_offsets([key, value])[1].0; + self.push_block(); + self.emit(&IterMapKey { key }); + self.emit(&IterBasePointer); + let key_addr = self.stack.pop().unwrap(); + self.write_to_memory(key, key_addr, Default::default()); + self.emit(&IterMapValue { value }); + self.emit(&IterBasePointer); + let value_addr = self.stack.pop().unwrap(); + self.write_to_memory(value, value_addr, value_offset); + self.finish_block(0); + self.emit(&MapLower { + key, + value, + realloc, + }); + } }, } } @@ -1819,7 +1878,16 @@ impl<'a, B: Bindgen> Generator<'a, B> { id, }); } - TypeDefKind::Map(..) => todo!(), + TypeDefKind::Map(key, value) => { + let value_offset = self.bindgen.sizes().field_offsets([key, value])[1].0; + self.push_block(); + self.emit(&IterBasePointer); + let entry_addr = self.stack.pop().unwrap(); + self.read_from_memory(key, entry_addr.clone(), Default::default()); + self.read_from_memory(value, entry_addr, value_offset); + self.finish_block(2); + self.emit(&MapLift { key, value, ty: id }); + } }, } } @@ -1907,6 +1975,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.write_to_memory(t, addr, offset), TypeDefKind::List(_) => self.write_list_to_memory(ty, addr, offset), + TypeDefKind::Map(_, _) => self.write_list_to_memory(ty, addr, offset), TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::Handle(_) => { self.lower_and_emit(ty, addr, &I32Store { offset }) @@ -2016,7 +2085,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { id, }); } - TypeDefKind::Map(..) => todo!(), }, } } @@ -2115,6 +2183,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::Type(t) => self.read_from_memory(t, addr, offset), TypeDefKind::List(_) => self.read_list_from_memory(ty, addr, offset), + TypeDefKind::Map(_, _) => self.read_list_from_memory(ty, addr, offset), TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::Handle(_) => { self.emit_and_lift(ty, addr, &I32Load { offset }) @@ -2216,7 +2285,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { id, }); } - TypeDefKind::Map(..) => todo!(), }, } } @@ -2339,6 +2407,18 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&Instruction::GuestDeallocateList { element }); } + TypeDefKind::Map(key, value) => { + let value_offset = self.bindgen.sizes().field_offsets([key, value])[1].0; + self.push_block(); + self.emit(&IterBasePointer); + let entry_addr = self.stack.pop().unwrap(); + self.deallocate_indirect(key, entry_addr.clone(), Default::default(), what); + self.deallocate_indirect(value, entry_addr, value_offset, what); + self.finish_block(0); + + self.emit(&Instruction::GuestDeallocateMap { key, value }); + } + TypeDefKind::Handle(Handle::Own(_)) | TypeDefKind::Future(_) | TypeDefKind::Stream(_) @@ -2405,7 +2485,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(..) => todo!(), }, } } @@ -2464,6 +2543,17 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.deallocate(ty, what); } + TypeDefKind::Map(_, _) => { + self.stack.push(addr.clone()); + self.emit(&Instruction::PointerLoad { offset }); + self.stack.push(addr); + self.emit(&Instruction::LengthLoad { + offset: offset + self.bindgen.sizes().align(ty).into(), + }); + + self.deallocate(ty, what); + } + TypeDefKind::Handle(Handle::Own(_)) | TypeDefKind::Future(_) | TypeDefKind::Stream(_) @@ -2527,7 +2617,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::Stream(_) => unreachable!(), TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(_, _) => {} - TypeDefKind::Map(..) => todo!(), }, } } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 15a2ad824..5e124f94d 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -166,6 +166,10 @@ pub trait InterfaceGenerator<'a> { fn type_alias(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); fn type_list(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); fn type_fixed_length_list(&mut self, id: TypeId, name: &str, ty: &Type, size: u32, docs: &Docs); + fn type_map(&mut self, id: TypeId, name: &str, key: &Type, value: &Type, docs: &Docs) { + let _ = (id, name, key, value, docs); + todo!("map types are not yet supported in this backend"); + } fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs); fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs); @@ -203,7 +207,7 @@ where TypeDefKind::FixedLengthList(t, size) => { generator.type_fixed_length_list(id, name, t, *size, &ty.docs) } - TypeDefKind::Map(..) => todo!(), + TypeDefKind::Map(key, value) => generator.type_map(id, name, key, value, &ty.docs), TypeDefKind::Unknown => unreachable!(), } } @@ -217,6 +221,10 @@ pub trait AnonymousTypeGenerator<'a> { fn anonymous_type_result(&mut self, id: TypeId, ty: &Result_, docs: &Docs); fn anonymous_type_list(&mut self, id: TypeId, ty: &Type, docs: &Docs); fn anonymous_type_fixed_length_list(&mut self, id: TypeId, ty: &Type, size: u32, docs: &Docs); + fn anonymous_type_map(&mut self, id: TypeId, key: &Type, value: &Type, docs: &Docs) { + let _ = (id, key, value, docs); + todo!("anonymous map types are not yet supported in this backend"); + } fn anonymous_type_future(&mut self, id: TypeId, ty: &Option, docs: &Docs); fn anonymous_type_stream(&mut self, id: TypeId, ty: &Option, docs: &Docs); fn anonymous_type_type(&mut self, id: TypeId, ty: &Type, docs: &Docs); @@ -242,7 +250,7 @@ pub trait AnonymousTypeGenerator<'a> { TypeDefKind::FixedLengthList(t, size) => { self.anonymous_type_fixed_length_list(id, t, *size, &ty.docs) } - TypeDefKind::Map(..) => todo!(), + TypeDefKind::Map(key, value) => self.anonymous_type_map(id, key, value, &ty.docs), TypeDefKind::Unknown => unreachable!(), } } diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index 5d1fd84a2..e56481092 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -261,7 +261,11 @@ impl Types { TypeDefKind::FixedLengthList(ty, _) => { info = self.type_info(resolve, ty); } - TypeDefKind::Map(..) => todo!(), + TypeDefKind::Map(key, value) => { + info = self.type_info(resolve, key); + info |= self.type_info(resolve, value); + info.has_list = true; + } TypeDefKind::Unknown => unreachable!(), } let prev = self.type_info.insert(ty, info); diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 271c4fe30..f66f7f6b0 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -892,7 +892,7 @@ impl CppInterfaceGenerator<'_> { TypeDefKind::Stream(_) => todo!("generate for stream"), TypeDefKind::Handle(_) => todo!("generate for handle"), TypeDefKind::FixedLengthList(_, _) => todo!(), - TypeDefKind::Map(_, _) => todo!(), + TypeDefKind::Map(key, value) => self.type_map(id, name, key, value, &ty.docs), TypeDefKind::Unknown => unreachable!(), } } @@ -1715,7 +1715,46 @@ impl CppInterfaceGenerator<'_> { self.type_name(ty, from_namespace, flavor) ) } - TypeDefKind::Map(_, _) => todo!(), + TypeDefKind::Map(key, value) => { + let entry_flavor = match flavor { + Flavor::BorrowedArgument | Flavor::Argument(AbiVariant::GuestImport) => { + Flavor::BorrowedArgument + } + _ => Flavor::InStruct, + }; + self.r#gen.dependencies.needs_tuple = true; + let inner = format!( + "std::tuple<{}, {}>", + self.type_name(key, from_namespace, entry_flavor), + self.type_name(value, from_namespace, entry_flavor) + ); + match flavor { + Flavor::BorrowedArgument => { + self.r#gen.dependencies.needs_span = true; + format!("std::span<{inner} const>") + } + Flavor::Argument(var) + if matches!(var, AbiVariant::GuestImport) + || self.r#gen.opts.api_style == APIStyle::Symmetric => + { + self.r#gen.dependencies.needs_span = true; + let constness = if self.r#gen.types.get(*id).has_own_handle { + "" + } else { + " const" + }; + format!("std::span<{inner}{constness}>") + } + Flavor::Argument(AbiVariant::GuestExport) => { + self.r#gen.dependencies.needs_wit = true; + format!("wit::vector<{inner}>") + } + _ => { + self.r#gen.dependencies.needs_wit = true; + format!("wit::vector<{inner}>") + } + } + } TypeDefKind::Unknown => todo!(), }, Type::ErrorContext => todo!(), @@ -2221,6 +2260,10 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> // nothing to do here } + fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) { + // nothing to do here + } + fn type_fixed_length_list( &mut self, _id: TypeId, @@ -2591,6 +2634,43 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } results.push(len); } + abi::Instruction::MapLower { + key, + value, + realloc, + } => { + let tmp = self.tmp(); + let body = self.blocks.pop().unwrap(); + let map = format!("map{tmp}"); + let ptr = format!("ptr{tmp}"); + let len = format!("len{tmp}"); + let size = self.r#gen.sizes.record([*key, *value]).size; + self.push_str(&format!("auto&& {} = {};\n", map, operands[0])); + self.push_str(&format!( + "auto {} = ({})({}.data());\n", + ptr, + self.r#gen.r#gen.opts.ptr_type(), + map + )); + self.push_str(&format!("auto {len} = (size_t)({map}.size());\n")); + self.push_str(&format!("for (size_t i = 0; i < {len}; ++i) {{\n")); + self.push_str(&format!( + "auto base = {ptr} + i * {size};\n", + size = size.format(POINTER_SIZE_EXPRESSION) + )); + self.push_str(&format!("auto&& map_entry = {map}[i];\n")); + self.push_str("auto&& map_key = std::get<0>(map_entry);\n"); + self.push_str("auto&& map_value = std::get<1>(map_entry);\n"); + self.push_str(&format!("{}\n", body.0)); + self.push_str("}\n"); + if realloc.is_none() { + results.push(ptr); + } else { + uwriteln!(self.src, "{}.leak();\n", operands[0]); + results.push(ptr); + } + results.push(len); + } abi::Instruction::ListCanonLift { element, .. } => { let tmp = self.tmp(); let len = format!("len{tmp}"); @@ -2696,6 +2776,77 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(move_if_necessary(&result)); } } + abi::Instruction::MapLift { key, value, .. } => { + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let size = self.r#gen.sizes.record([*key, *value]).size; + let flavor = if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric + && matches!(self.variant, AbiVariant::GuestExport) + { + Flavor::BorrowedArgument + } else { + Flavor::InStruct + }; + self.r#gen.r#gen.dependencies.needs_tuple = true; + let entry_type = format!( + "std::tuple<{}, {}>", + self.r#gen.type_name(key, &self.namespace, flavor), + self.r#gen.type_name(value, &self.namespace, flavor) + ); + let len = format!("len{tmp}"); + let base = format!("base{tmp}"); + let result = format!("result{tmp}"); + self.push_str(&format!( + "auto {base} = {operand0};\n", + operand0 = operands[0] + )); + self.push_str(&format!( + "auto {len} = {operand1};\n", + operand1 = operands[1] + )); + self.push_str(&format!( + r#"auto {result} = wit::vector<{entry_type}>::allocate({len}); + "#, + )); + + if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric + && matches!(self.variant, AbiVariant::GuestExport) + { + assert!(self.needs_dealloc); + self.push_str(&format!("if ({len}>0) _deallocate.push_back({base});\n")); + } + + uwriteln!(self.src, "for (unsigned i=0; i<{len}; ++i) {{"); + uwriteln!( + self.src, + "auto base = {base} + i * {size};", + size = size.format(POINTER_SIZE_EXPRESSION) + ); + uwrite!(self.src, "{}", body.0); + let key_result = move_if_necessary(&body.1[0]); + let value_result = move_if_necessary(&body.1[1]); + uwriteln!( + self.src, + "auto e{tmp} = std::make_tuple({key_result}, {value_result});" + ); + if let Some(code) = self.leak_on_insertion.take() { + assert!(self.needs_dealloc); + uwriteln!(self.src, "{code}"); + } + uwriteln!(self.src, "{result}.initialize(i, std::move(e{tmp}));"); + uwriteln!(self.src, "}}"); + + if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric + && matches!(self.variant, AbiVariant::GuestExport) + { + results.push(format!("{result}.get_const_view()")); + self.leak_on_insertion.replace(format!( + "if ({len}>0) _deallocate.push_back((void*){result}.leak());\n" + )); + } else { + results.push(move_if_necessary(&result)); + } + } abi::Instruction::FixedLengthListLift { element, size, @@ -2784,6 +2935,8 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str("\n}\n}\n"); } abi::Instruction::IterElem { .. } => results.push("iter_elem".to_string()), + abi::Instruction::IterMapKey { .. } => results.push("map_key".to_string()), + abi::Instruction::IterMapValue { .. } => results.push("map_value".to_string()), abi::Instruction::IterBasePointer => results.push("base".to_string()), abi::Instruction::RecordLower { record, .. } => { let op = &operands[0]; @@ -3460,6 +3613,29 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { uwriteln!(self.src, "free((void*) ({ptr}));"); uwriteln!(self.src, "}}"); } + abi::Instruction::GuestDeallocateMap { key, value } => { + let (body, results) = self.blocks.pop().unwrap(); + assert!(results.is_empty()); + let tmp = self.tmp(); + let ptr = self.tempname("ptr", tmp); + let len = self.tempname("len", tmp); + uwriteln!(self.src, "uint8_t* {ptr} = {};", operands[0]); + uwriteln!(self.src, "size_t {len} = {};", operands[1]); + let i = self.tempname("i", tmp); + uwriteln!(self.src, "for (size_t {i} = 0; {i} < {len}; {i}++) {{"); + let size = self.r#gen.sizes.record([*key, *value]).size; + uwriteln!( + self.src, + "uint8_t* base = {ptr} + {i} * {size};", + size = size.format(POINTER_SIZE_EXPRESSION) + ); + uwriteln!(self.src, "(void) base;"); + uwrite!(self.src, "{body}"); + uwriteln!(self.src, "}}"); + uwriteln!(self.src, "if ({len} > 0) {{"); + uwriteln!(self.src, "free((void*) ({ptr}));"); + uwriteln!(self.src, "}}"); + } abi::Instruction::GuestDeallocateVariant { blocks } => { let blocks = self .blocks diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index 8d7ab4a1e..43a135158 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -983,6 +983,81 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!("{list}.Count")); } + Instruction::MapLower { key, value, realloc } => { + let Block { + body, + results: block_results, + element: block_element, + base, + } = self.blocks.pop().unwrap(); + assert!(block_results.is_empty()); + + let map = &operands[0]; + let tuple_ty = format!( + "({}, {})", + self.interface_gen.type_name_with_qualifier(key, true), + self.interface_gen.type_name_with_qualifier(value, true) + ); + let layout = self.interface_gen.csharp_gen.sizes.record([*key, *value]); + let size = layout.size.size_wasm32(); + let index = self.locals.tmp("index"); + + let address = self.locals.tmp("address"); + let buffer_size = self.locals.tmp("bufferSize"); + let align = layout.align.align_wasm32(); + + let (array_size, element_type) = crate::world_generator::dotnet_aligned_array( + size, + align, + ); + let ret_area = self.locals.tmp("retArea"); + + match realloc { + None => { + self.needs_cleanup = true; + uwrite!( + self.src, + " + void* {address}; + if (({size} * {map}.Count) < 1024) {{ + var {ret_area} = stackalloc {element_type}[({array_size}*{map}.Count)+1]; + {address} = (void*)(((int){ret_area}) + ({align} - 1) & -{align}); + }} + else + {{ + var {buffer_size} = {size} * (nuint){map}.Count; + {address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align}); + cleanups.Add(() => global::System.Runtime.InteropServices.NativeMemory.AlignedFree({address})); + }} + " + ); + } + Some(_) => { + uwrite!( + self.src, + " + var {buffer_size} = {size} * (nuint){map}.Count; + void* {address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align}); + " + ); + } + } + + uwrite!( + self.src, + " + for (int {index} = 0; {index} < {map}.Count; ++{index}) {{ + {tuple_ty} {block_element} = {map}[{index}]; + int {base} = (int){address} + ({index} * {size}); + {body} + }} + " + ); + + results.push(format!("(int){address}")); + results.push(format!("{map}.Count")); + } + Instruction::ListLift { element, .. } => { let Block { body, @@ -1021,10 +1096,61 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(array); } + Instruction::MapLift { key, value, .. } => { + let Block { + body, + results: block_results, + base, + .. + } = self.blocks.pop().unwrap(); + let [map_key, map_value] = &block_results[..] else { + todo!("result count == {}", block_results.len()) + }; + let address = &operands[0]; + let length = &operands[1]; + let map = self.locals.tmp("map"); + let tuple_ty = format!( + "({}, {})", + self.interface_gen.type_name_with_qualifier(key, true), + self.interface_gen.type_name_with_qualifier(value, true) + ); + let layout = self.interface_gen.csharp_gen.sizes.record([*key, *value]); + let size = layout.size.size_wasm32(); + let index = self.locals.tmp("index"); + + uwrite!( + self.src, + " + var {map} = new global::System.Collections.Generic.List<{tuple_ty}>({length}); + for (int {index} = 0; {index} < {length}; ++{index}) {{ + nint {base} = {address} + ({index} * {size}); + {body} + {map}.Add(({map_key}, {map_value})); + }} + + if ({length} > 0) {{ + global::System.Runtime.InteropServices.NativeMemory.Free((void*){address}); + }} + " + ); + + results.push(map); + } + Instruction::IterElem { .. } => { results.push(self.block_storage.last().unwrap().element.clone()) } + Instruction::IterMapKey { .. } => results.push(format!( + "{}.Item1", + self.block_storage.last().unwrap().element + )), + + Instruction::IterMapValue { .. } => results.push(format!( + "{}.Item2", + self.block_storage.last().unwrap().element + )), + Instruction::IterBasePointer => { results.push(self.block_storage.last().unwrap().base.clone()) } @@ -1281,6 +1407,46 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#, operands[0]); } + Instruction::GuestDeallocateMap { key, value } => { + let Block { + body, + results: block_results, + base, + .. + } = self.blocks.pop().unwrap(); + assert!(block_results.is_empty()); + + let address = &operands[0]; + let length = &operands[1]; + let size = self + .interface_gen + .csharp_gen + .sizes + .record([*key, *value]) + .size + .size_wasm32(); + + if !body.trim().is_empty() { + let index = self.locals.tmp("index"); + + uwrite!( + self.src, + " + for (int {index} = 0; {index} < {length}; ++{index}) {{ + int {base} = (int){address} + ({index} * {size}); + {body} + }} + " + ); + } + + uwriteln!( + self.src, + r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#, + operands[0] + ); + } + Instruction::HandleLower { handle, .. diff --git a/crates/csharp/src/interface.rs b/crates/csharp/src/interface.rs index 49d55cd8c..1efb79b62 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -134,6 +134,9 @@ impl InterfaceGenerator<'_> { TypeDefKind::Option(t) => self.type_option(type_id, typedef_name, t, &type_def.docs), TypeDefKind::Record(t) => self.type_record(type_id, typedef_name, t, &type_def.docs), TypeDefKind::List(t) => self.type_list(type_id, typedef_name, t, &type_def.docs), + TypeDefKind::Map(key, value) => { + self.type_map(type_id, typedef_name, key, value, &type_def.docs) + } TypeDefKind::Variant(t) => self.type_variant(type_id, typedef_name, t, &type_def.docs), TypeDefKind::Result(t) => self.type_result(type_id, typedef_name, t, &type_def.docs), TypeDefKind::Handle(_) => { @@ -1033,6 +1036,7 @@ var {async_status_var} = {raw_name}({wasm_params}); TypeDefKind::Option(_ty) => "".to_owned(), TypeDefKind::Result(_result) => "".to_owned(), TypeDefKind::List(_list) => "".to_owned(), + TypeDefKind::Map(_, _) => "".to_owned(), TypeDefKind::Tuple(_tuple) => "".to_owned(), TypeDefKind::Type(inner_type) => self.global_if_user_type(inner_type), _ => "global::".to_owned(), @@ -1109,6 +1113,13 @@ var {async_status_var} = {raw_name}({wasm_params}); ) } } + TypeDefKind::Map(key, value) => { + format!( + "global::System.Collections.Generic.List<({}, {})>", + self.type_name_with_qualifier(key, qualifier), + self.type_name_with_qualifier(value, qualifier) + ) + } TypeDefKind::Tuple(tuple) => { let count = tuple.types.len(); self.csharp_gen.tuple_counts.insert(count); @@ -1740,6 +1751,10 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> { self.type_name(&Type::Id(id)); } + fn type_map(&mut self, id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) { + self.type_name(&Type::Id(id)); + } + fn type_fixed_length_list( &mut self, _id: TypeId, diff --git a/crates/go/src/lib.rs b/crates/go/src/lib.rs index 1550c0c9a..7f6e95fc0 100644 --- a/crates/go/src/lib.rs +++ b/crates/go/src/lib.rs @@ -28,6 +28,8 @@ const POINTER_SIZE_EXPRESSION: &str = "4"; const VARIANT_PAYLOAD_NAME: &str = "payload"; const ITER_BASE_POINTER: &str = "base"; const ITER_ELEMENT: &str = "element"; +const ITER_MAP_KEY: &str = "mapKey"; +const ITER_MAP_VALUE: &str = "mapValue"; const IMPORT_RETURN_AREA: &str = "returnArea"; const EXPORT_RETURN_AREA: &str = "exportReturnArea"; const SYNC_EXPORT_PINNER: &str = "syncExportPinner"; @@ -348,6 +350,13 @@ impl Go { .join(", "); format!("witTypes.Tuple{count}[{types}]") } + TypeDefKind::Map(key, value) => { + imports.insert(remote_pkg("types")); + self.tuples.insert(2); + let key = self.type_name(resolve, *key, local, in_import, imports); + let value = self.type_name(resolve, *value, local, in_import, imports); + format!("[]witTypes.Tuple2[{key}, {value}]") + } TypeDefKind::Future(ty) => { self.need_future = true; imports.insert(remote_pkg("types")); @@ -1735,6 +1744,35 @@ for index, {ITER_ELEMENT} := range {slice} {{ {ITER_BASE_POINTER} := unsafe.Add({result}, index * {size}) {body} }} +" + ); + results.push(format!("uintptr({result})")); + results.push(length); + } + Instruction::MapLower { key, value, .. } => { + self.need_unsafe = true; + self.need_pinner = true; + self.imports.insert(remote_pkg("runtime")); + let (body, body_results) = self.blocks.pop().unwrap(); + assert!(body_results.is_empty()); + let map = &operands[0]; + let slice = self.locals.tmp("slice"); + let result = self.locals.tmp("result"); + let length = self.locals.tmp("length"); + let layout = self.generator.sizes.record([*key, *value]); + let size = layout.size.format(POINTER_SIZE_EXPRESSION); + let align = layout.align.format(POINTER_SIZE_EXPRESSION); + uwriteln!( + self.src, + "{slice} := {map} +{length} := uint32(len({slice})) +{result} := witRuntime.Allocate({PINNER}, uintptr({length} * {size}), {align}) +for index, {ITER_ELEMENT} := range {slice} {{ + {ITER_MAP_KEY} := {ITER_ELEMENT}.F0 + {ITER_MAP_VALUE} := {ITER_ELEMENT}.F1 + {ITER_BASE_POINTER} := unsafe.Add({result}, index * {size}) + {body} +}} " ); results.push(format!("uintptr({result})")); @@ -1761,6 +1799,33 @@ for index := 0; index < int({length}); index++ {{ {body} {result} = append({result}, {body_result}) }} +" + ); + results.push(result); + } + Instruction::MapLift { key, value, .. } => { + self.need_unsafe = true; + self.imports.insert(remote_pkg("types")); + self.generator.tuples.insert(2); + let (body, body_results) = self.blocks.pop().unwrap(); + let [map_key, map_value] = &body_results[..] else { + panic!("expected two map block results"); + }; + let pointer = &operands[0]; + let length = &operands[1]; + let result = self.locals.tmp("result"); + let layout = self.generator.sizes.record([*key, *value]); + let size = layout.size.format(POINTER_SIZE_EXPRESSION); + let key_type = self.type_name(resolve, **key); + let value_type = self.type_name(resolve, **value); + uwriteln!( + self.src, + "{result} := make([]witTypes.Tuple2[{key_type}, {value_type}], 0, {length}) +for index := 0; index < int({length}); index++ {{ + {ITER_BASE_POINTER} := unsafe.Add(unsafe.Pointer({pointer}), index * {size}) + {body} + {result} = append({result}, witTypes.Tuple2[{key_type}, {value_type}]{{{map_key}, {map_value}}}) +}} " ); results.push(result); @@ -2364,6 +2429,8 @@ default: } Instruction::VariantPayloadName => results.push(VARIANT_PAYLOAD_NAME.into()), Instruction::IterElem { .. } => results.push(ITER_ELEMENT.into()), + Instruction::IterMapKey { .. } => results.push(ITER_MAP_KEY.into()), + Instruction::IterMapValue { .. } => results.push(ITER_MAP_VALUE.into()), Instruction::IterBasePointer => results.push(ITER_BASE_POINTER.into()), Instruction::I32Const { val } => results.push(format!("int32({val})")), Instruction::ConstZero { tys } => { @@ -2483,6 +2550,10 @@ lifters = append(lifters, func() {{ Instruction::GuestDeallocate { .. } => { // Nothing to do here; should be handled when calling `pinner.Unpin()` } + Instruction::GuestDeallocateMap { .. } => { + let _ = self.blocks.pop().unwrap(); + // Nothing to do here; deallocation is managed by pinner cleanup. + } _ => unimplemented!("{instruction:?}"), } } @@ -2907,6 +2978,13 @@ const ( uwriteln!(self.src, "{docs}type {name} = []{ty}"); } + fn type_map(&mut self, id: TypeId, name: &str, _key: &Type, _value: &Type, docs: &Docs) { + let name = name.to_upper_camel_case(); + let ty = self.type_name(self.resolve, Type::Id(id)); + let docs = format_docs(docs); + uwriteln!(self.src, "{docs}type {name} = {ty}"); + } + fn type_fixed_length_list(&mut self, _: TypeId, name: &str, ty: &Type, size: u32, docs: &Docs) { let name = name.to_upper_camel_case(); let ty = self.type_name(self.resolve, *ty); @@ -3109,6 +3187,9 @@ fn any(resolve: &Resolve, ty: Type, fun: &dyn Fn(Type) -> bool) -> bool { .or_else(|| result.err.map(|ty| any(resolve, ty, fun))) .unwrap_or(false), TypeDefKind::Tuple(tuple) => tuple.types.iter().any(|ty| any(resolve, *ty, fun)), + TypeDefKind::Map(key, value) => { + any(resolve, *key, fun) || any(resolve, *value, fun) + } TypeDefKind::Future(ty) | TypeDefKind::Stream(ty) => { ty.map(|ty| any(resolve, ty, fun)).unwrap_or(false) } diff --git a/crates/guest-rust/src/rt/mod.rs b/crates/guest-rust/src/rt/mod.rs index a85a37f12..096bb9c79 100644 --- a/crates/guest-rust/src/rt/mod.rs +++ b/crates/guest-rust/src/rt/mod.rs @@ -67,6 +67,11 @@ pub mod bitflags { pub use crate::bitflags; } +#[cfg(feature = "std")] +pub type Map = std::collections::HashMap; +#[cfg(not(feature = "std"))] +pub type Map = alloc::collections::BTreeMap; + /// For more information about this see `./ci/rebuild-libwit-bindgen-cabi.sh`. #[cfg(not(target_env = "p2"))] mod wit_bindgen_cabi_realloc; diff --git a/crates/markdown/src/lib.rs b/crates/markdown/src/lib.rs index cfc2ed3d5..76fe72cfe 100644 --- a/crates/markdown/src/lib.rs +++ b/crates/markdown/src/lib.rs @@ -420,7 +420,13 @@ impl InterfaceGenerator<'_> { } TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(..) => todo!(), + TypeDefKind::Map(key, value) => { + self.push_str("map<"); + self.print_ty(key); + self.push_str(", "); + self.print_ty(value); + self.push_str(">"); + } } } } @@ -650,6 +656,10 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { self.type_alias(id, name, &Type::Id(id), docs); } + fn type_map(&mut self, id: TypeId, name: &str, _key: &Type, _value: &Type, docs: &Docs) { + self.type_alias(id, name, &Type::Id(id), docs); + } + fn type_fixed_length_list( &mut self, id: TypeId, diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 7f8edbc6c..ad66e80f1 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1319,6 +1319,10 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { // Not needed. They will become `Array[T]` or `FixedArray[T]` in Moonbit } + fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) { + // Not needed. They will become `Array[(K, V)]` in Moonbit + } + fn type_fixed_length_list( &mut self, _id: TypeId, @@ -2199,6 +2203,57 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } + Instruction::MapLower { + key, + value, + realloc, + } => { + let Block { + body, + results: block_results, + } = self.blocks.pop().unwrap(); + assert!(block_results.is_empty()); + + let op = &operands[0]; + let layout = self.r#gen.r#gen.sizes.record([*key, *value]); + let size = layout.size.size_wasm32(); + let address = self.locals.tmp("address"); + let key_ty = self + .r#gen + .r#gen + .pkg_resolver + .type_name(self.r#gen.name, key); + let value_ty = self + .r#gen + .r#gen + .pkg_resolver + .type_name(self.r#gen.name, value); + let tuple_ty = format!("({key_ty}, {value_ty})"); + let index = self.locals.tmp("index"); + + self.r#gen.ffi_imports.insert(ffi::MALLOC); + uwrite!( + self.src, + " + let {address} = mbt_ffi_malloc(({op}).length() * {size}); + for {index} = 0; {index} < ({op}).length(); {index} = {index} + 1 {{ + let iter_elem : {tuple_ty} = ({op})[({index})] + let iter_map_key = (iter_elem).0 + let iter_map_value = (iter_elem).1 + let iter_base = {address} + ({index} * {size}); + {body} + }} + ", + ); + + results.push(address.clone()); + results.push(format!("({op}).length()")); + + if realloc.is_none() { + self.cleanup.push(Cleanup { address }); + } + } + Instruction::ListLift { element, .. } => { let Block { body, @@ -2238,8 +2293,55 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(array); } + Instruction::MapLift { key, value, .. } => { + let Block { + body, + results: block_results, + } = self.blocks.pop().unwrap(); + let [map_key, map_value] = &block_results[..] else { + todo!("result count == {}", block_results.len()) + }; + let address = &operands[0]; + let length = &operands[1]; + let array = self.locals.tmp("array"); + let key_ty = self + .r#gen + .r#gen + .pkg_resolver + .type_name(self.r#gen.name, key); + let value_ty = self + .r#gen + .r#gen + .pkg_resolver + .type_name(self.r#gen.name, value); + let tuple_ty = format!("({key_ty}, {value_ty})"); + let layout = self.r#gen.r#gen.sizes.record([*key, *value]); + let size = layout.size.size_wasm32(); + let index = self.locals.tmp("index"); + + self.r#gen.ffi_imports.insert(ffi::FREE); + uwrite!( + self.src, + " + let {array} : Array[{tuple_ty}] = []; + for {index} = 0; {index} < ({length}); {index} = {index} + 1 {{ + let iter_base = ({address}) + ({index} * {size}) + {body} + {array}.push(({map_key}, {map_value})) + }} + mbt_ffi_free({address}) + ", + ); + + results.push(array); + } + Instruction::IterElem { .. } => results.push("iter_elem".into()), + Instruction::IterMapKey { .. } => results.push("iter_map_key".into()), + + Instruction::IterMapValue { .. } => results.push("iter_map_value".into()), + Instruction::IterBasePointer => results.push("iter_base".into()), Instruction::CallWasm { sig, .. } => { @@ -2624,6 +2726,38 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "mbt_ffi_free({address})",); } + Instruction::GuestDeallocateMap { key, value } => { + let Block { body, results, .. } = self.blocks.pop().unwrap(); + assert!(results.is_empty()); + + let address = &operands[0]; + let length = &operands[1]; + let size = self + .r#gen + .r#gen + .sizes + .record([*key, *value]) + .size + .size_wasm32(); + + if !body.trim().is_empty() { + let index = self.locals.tmp("index"); + + uwrite!( + self.src, + " + for {index} = 0; {index} < ({length}); {index} = {index} + 1 {{ + let iter_base = ({address}) + ({index} * {size}) + {body} + }} + " + ); + } + + self.r#gen.ffi_imports.insert(ffi::FREE); + uwriteln!(self.src, "mbt_ffi_free({address})",); + } + Instruction::Flush { amt } => { results.extend(operands.iter().take(*amt).cloned()); } diff --git a/crates/moonbit/src/pkg.rs b/crates/moonbit/src/pkg.rs index e7c1a1057..0f6d9eb23 100644 --- a/crates/moonbit/src/pkg.rs +++ b/crates/moonbit/src/pkg.rs @@ -217,6 +217,11 @@ impl PkgResolver { .join(", ") ) } + TypeDefKind::Map(key, value) => { + let key = self.type_name(this, &key); + let value = self.type_name(this, &value); + format!("Array[({key}, {value})]") + } TypeDefKind::Option(ty) => { format!("{}?", self.type_name(this, &ty)) } diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index cd76e0b75..01f96ff7f 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -160,6 +160,10 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { } } + fn map_entry_layout(&self, key: &Type, value: &Type) -> ElementInfo { + self.r#gen.sizes.record([key, value]) + } + fn typename_lower(&self, id: TypeId) -> String { let owned = self.always_owned || match self.lift_lower() { @@ -759,6 +763,55 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(len); } + Instruction::MapLower { + key, + value, + realloc, + } => { + let alloc = self.r#gen.path_to_std_alloc_module(); + let rt = self.r#gen.r#gen.runtime_path().to_string(); + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let map = format!("map{tmp}"); + let result = format!("result{tmp}"); + let layout = format!("layout{tmp}"); + let len = format!("len{tmp}"); + let cleanup = format!("_cleanup{tmp}"); + self.push_str(&format!( + "let {map} = {operand0};\n", + operand0 = operands[0] + )); + self.push_str(&format!("let {len} = {map}.len();\n")); + let entry = self.map_entry_layout(key, value); + self.push_str(&format!( + "let {layout} = {alloc}::Layout::from_size_align({len} * {}, {}).unwrap();\n", + entry.size.format(POINTER_SIZE_EXPRESSION), + entry.align.format(POINTER_SIZE_EXPRESSION), + )); + self.push_str(&format!( + "let ({result}, {cleanup}) = {rt}::Cleanup::new({layout});" + )); + if realloc.is_none() { + self.cleanup(&cleanup); + } else { + uwriteln!( + self.src, + "if let Some(cleanup) = {cleanup} {{ cleanup.forget(); }}" + ); + } + self.push_str(&format!( + "for (i, (map_key, map_value)) in {map}.into_iter().enumerate() {{\n" + )); + self.push_str(&format!( + "let base = {result}.add(i * {});\n", + entry.size.format(POINTER_SIZE_EXPRESSION) + )); + self.push_str(&body); + self.push_str("\n}\n"); + results.push(format!("{result}")); + results.push(len); + } + Instruction::FixedLengthListLowerToMemory { element, size: _, @@ -816,8 +869,47 @@ impl Bindgen for FunctionBindgen<'_, '_> { )); } + Instruction::MapLift { key, value, .. } => { + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let entry = self.map_entry_layout(key, value); + let len = format!("len{tmp}"); + let base = format!("base{tmp}"); + let result = format!("result{tmp}"); + let map = self.r#gen.path_to_map(); + self.push_str(&format!( + "let {base} = {operand0};\n", + operand0 = operands[0] + )); + self.push_str(&format!( + "let {len} = {operand1};\n", + operand1 = operands[1] + )); + self.push_str(&format!("let mut {result} = {map}::new();\n")); + uwriteln!(self.src, "for i in 0..{len} {{"); + uwriteln!( + self.src, + "let base = {base}.add(i * {size});", + size = entry.size.format(POINTER_SIZE_EXPRESSION), + ); + uwriteln!(self.src, "let (map_key, map_value) = {body};"); + uwriteln!(self.src, "{result}.insert(map_key, map_value);"); + uwriteln!(self.src, "}}"); + results.push(result); + let dealloc = self.r#gen.path_to_cabi_dealloc(); + self.push_str(&format!( + "{dealloc}({base}, {len} * {size}, {align});\n", + size = entry.size.format(POINTER_SIZE_EXPRESSION), + align = entry.align.format(POINTER_SIZE_EXPRESSION), + )); + } + Instruction::IterElem { .. } => results.push("e".to_string()), + Instruction::IterMapKey { .. } => results.push("map_key".to_string()), + + Instruction::IterMapValue { .. } => results.push("map_value".to_string()), + Instruction::IterBasePointer => results.push("base".to_string()), Instruction::CallWasm { name, sig, .. } => { @@ -1201,6 +1293,41 @@ impl Bindgen for FunctionBindgen<'_, '_> { )); } + Instruction::GuestDeallocateMap { key, value } => { + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let entry = self.map_entry_layout(key, value); + let len = format!("len{tmp}"); + let base = format!("base{tmp}"); + self.push_str(&format!( + "let {base} = {operand0};\n", + operand0 = operands[0] + )); + self.push_str(&format!( + "let {len} = {operand1};\n", + operand1 = operands[1] + )); + + if body != "()" { + self.push_str("for i in 0.."); + self.push_str(&len); + self.push_str(" {\n"); + self.push_str("let base = "); + self.push_str(&base); + self.push_str(".add(i * "); + self.push_str(&entry.size.format(POINTER_SIZE_EXPRESSION)); + self.push_str(");\n"); + self.push_str(&body); + self.push_str("\n}\n"); + } + let dealloc = self.r#gen.path_to_cabi_dealloc(); + self.push_str(&format!( + "{dealloc}({base}, {len} * {size}, {align});\n", + size = entry.size.format(POINTER_SIZE_EXPRESSION), + align = entry.align.format(POINTER_SIZE_EXPRESSION) + )); + } + Instruction::DropHandle { .. } => { uwriteln!(self.src, "let _ = {};", operands[0]); } diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 4f4f00471..3145f6802 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -1786,6 +1786,12 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) return; } } + TypeDefKind::Map(key, value) => { + if mode.lifetime.is_some() { + self.print_map(key, value, mode); + return; + } + } _ => {} } } @@ -1931,6 +1937,32 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) } } + fn print_map(&mut self, key: &Type, value: &Type, mode: TypeMode) { + let key_mode = self.filter_mode(key, mode); + let value_mode = self.filter_mode(value, mode); + if mode.lists_borrowed { + let lifetime = mode.lifetime.unwrap(); + self.push_str("&"); + if lifetime != "'_" { + self.push_str(lifetime); + self.push_str(" "); + } + self.push_str("[("); + self.print_ty(key, key_mode); + self.push_str(", "); + self.print_ty(value, value_mode); + self.push_str(")]"); + } else { + let path = self.path_to_map(); + self.push_str(&path); + self.push_str("::<"); + self.print_ty(key, key_mode); + self.push_str(", "); + self.print_ty(value, value_mode); + self.push_str(">"); + } + } + fn print_generics(&mut self, lifetime: Option<&str>) { if lifetime.is_none() { return; @@ -2557,6 +2589,10 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) self.path_from_runtime_module(RuntimeItem::VecType, "Vec") } + pub fn path_to_map(&mut self) -> String { + self.path_from_runtime_module(RuntimeItem::MapType, "Map") + } + pub fn path_to_string(&mut self) -> String { self.path_from_runtime_module(RuntimeItem::StringType, "String") } @@ -2909,6 +2945,17 @@ impl<'a> {camel}Borrow<'a>{{ } } + fn type_map(&mut self, id: TypeId, _name: &str, key: &Type, value: &Type, docs: &Docs) { + for (name, mode) in self.modes_of(id) { + self.rustdoc(docs); + self.push_str(&format!("pub type {name}")); + self.print_generics(mode.lifetime); + self.push_str(" = "); + self.print_map(key, value, mode); + self.push_str(";\n"); + } + } + fn type_fixed_length_list( &mut self, id: TypeId, @@ -3049,6 +3096,10 @@ impl<'a, 'b> wit_bindgen_core::AnonymousTypeGenerator<'a> for AnonTypeGenerator< self.interface.print_list(ty, self.mode) } + fn anonymous_type_map(&mut self, _id: TypeId, key: &Type, value: &Type, _docs: &Docs) { + self.interface.print_map(key, value, self.mode); + } + fn anonymous_type_future(&mut self, _id: TypeId, ty: &Option, _docs: &Docs) { let async_support = self.interface.r#gen.async_support_path(); let mode = TypeMode { diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 3df749648..3ff32473e 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -98,6 +98,7 @@ impl TypeGeneration { #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] enum RuntimeItem { AllocCrate, + MapType, StringType, StdAllocModule, VecType, @@ -557,6 +558,10 @@ pub mod wit_stream {{ RuntimeItem::AllocCrate => { uwriteln!(self.src, "extern crate alloc as alloc_crate;"); } + RuntimeItem::MapType => { + let rt = self.runtime_path().to_string(); + uwriteln!(self.src, "pub use {rt}::Map;"); + } RuntimeItem::StdAllocModule => { self.rt_module.insert(RuntimeItem::AllocCrate); uwriteln!(self.src, "pub use alloc_crate::alloc;"); diff --git a/crates/test/src/go.rs b/crates/test/src/go.rs index 99d8487a8..83060b3a9 100644 --- a/crates/test/src/go.rs +++ b/crates/test/src/go.rs @@ -161,7 +161,7 @@ fn replace_bindings_go_mod(runner: &Runner, bindings_dir: &Path) -> Result<()> { super::write_if_different( &bindings_dir.join("go.mod"), format!( - "module wit_component\n\ngo 1.25\n\nreplace go.bytecodealliance.org => {}", + "module wit_component\n\ngo 1.25\n\nreplace go.bytecodealliance.org/pkg => {}", go_package_path.display() ), )?; diff --git a/tests/codegen/map.wit b/tests/codegen/map.wit new file mode 100644 index 000000000..3f74e635e --- /dev/null +++ b/tests/codegen/map.wit @@ -0,0 +1,21 @@ +package foo:foo; + +interface map-types { + type names-by-id = map; + type ids-by-name = map; + type bytes-by-name = map>; + + record wrapped-map { + values: names-by-id, + } + + named-roundtrip: func(a: names-by-id) -> ids-by-name; + inline-roundtrip: func(a: map, b: map>) -> tuple, map>>; + nested-roundtrip: func(a: bytes-by-name) -> bytes-by-name; + wrapped-roundtrip: func(a: wrapped-map) -> wrapped-map; +} + +world map-world { + import map-types; + export map-types; +} diff --git a/tests/runtime/map/runner.rs b/tests/runtime/map/runner.rs new file mode 100644 index 000000000..d503f58d6 --- /dev/null +++ b/tests/runtime/map/runner.rs @@ -0,0 +1,35 @@ +//@ wasmtime-flags = '-Wcomponent-model-map' + +include!(env!("BINDINGS")); + +use test::maps::to_test::*; + +struct Component; + +export!(Component); + +impl Guest for Component { + fn run() { + let ids_by_name = named_roundtrip(&[ + (1, "one".to_string()), + (1, "uno".to_string()), + (2, "two".to_string()), + ]); + assert_eq!(ids_by_name.get("uno"), Some(&1)); + assert_eq!(ids_by_name.get("two"), Some(&2)); + assert_eq!(ids_by_name.get("one"), None); + + let bytes_by_name = bytes_roundtrip(&[ + ("hello".to_string(), b"world".to_vec()), + ("bin".to_string(), vec![0u8, 1, 2]), + ]); + assert_eq!( + bytes_by_name.get("hello").map(Vec::as_slice), + Some(b"world".as_slice()) + ); + assert_eq!( + bytes_by_name.get("bin").map(Vec::as_slice), + Some([0u8, 1, 2].as_slice()) + ); + } +} diff --git a/tests/runtime/map/test.rs b/tests/runtime/map/test.rs new file mode 100644 index 000000000..5890dafdf --- /dev/null +++ b/tests/runtime/map/test.rs @@ -0,0 +1,26 @@ +include!(env!("BINDINGS")); + +use crate::exports::test::maps::to_test::{BytesByName, IdsByName, NamesById}; + +struct Component; + +export!(Component); + +impl exports::test::maps::to_test::Guest for Component { + fn named_roundtrip(a: NamesById) -> IdsByName { + assert_eq!(a.get(&1).map(String::as_str), Some("uno")); + assert_eq!(a.get(&2).map(String::as_str), Some("two")); + + let mut result = IdsByName::new(); + for (id, name) in a { + result.insert(name, id); + } + result + } + + fn bytes_roundtrip(a: BytesByName) -> BytesByName { + assert_eq!(a.get("hello").map(Vec::as_slice), Some(b"world".as_slice())); + assert_eq!(a.get("bin").map(Vec::as_slice), Some([0u8, 1, 2].as_slice())); + a + } +} diff --git a/tests/runtime/map/test.wit b/tests/runtime/map/test.wit new file mode 100644 index 000000000..364695e21 --- /dev/null +++ b/tests/runtime/map/test.wit @@ -0,0 +1,20 @@ +package test:maps; + +interface to-test { + type names-by-id = map; + type ids-by-name = map; + type bytes-by-name = map>; + + named-roundtrip: func(a: names-by-id) -> ids-by-name; + bytes-roundtrip: func(a: bytes-by-name) -> bytes-by-name; +} + +world test { + export to-test; +} + +world runner { + import to-test; + + export run: func(); +} From 8ac1f50da35022612547cd7efc242b8d5cf10350 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 24 Mar 2026 11:34:22 -0400 Subject: [PATCH 02/11] revert: remove map support from non-Rust backends Defer map codegen for MoonBit, Go, C#, C++, and C to future PRs that include runtime tests and review from language-specific maintainers. Only todo!() stubs for the new MapLower/MapLift/ IterMapKey/IterMapValue/GuestDeallocateMap instruction variants are kept so exhaustive matches compile. --- crates/c/src/lib.rs | 107 ++----------------- crates/cpp/src/lib.rs | 187 ++------------------------------- crates/csharp/src/function.rs | 171 +----------------------------- crates/csharp/src/interface.rs | 15 --- crates/go/src/lib.rs | 81 -------------- crates/moonbit/src/lib.rs | 142 ++----------------------- crates/moonbit/src/pkg.rs | 5 - 7 files changed, 28 insertions(+), 680 deletions(-) diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 3f98742db..2d53d23dd 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -1088,7 +1088,7 @@ fn is_prim_type_id(resolve: &Resolve, id: TypeId) -> bool { | TypeDefKind::Stream(_) | TypeDefKind::Unknown => false, TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(key, value) => is_prim_type(resolve, key) && is_prim_type(resolve, value), + TypeDefKind::Map(..) => todo!(), } } @@ -1174,12 +1174,7 @@ pub fn push_ty_name(resolve: &Resolve, ty: &Type, src: &mut String) { } TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(key, value) => { - src.push_str("map_"); - push_ty_name(resolve, key, src); - src.push_str("_"); - push_ty_name(resolve, value, src); - } + TypeDefKind::Map(..) => todo!(), } } } @@ -1388,12 +1383,12 @@ impl Return { TypeDefKind::Tuple(_) | TypeDefKind::Record(_) | TypeDefKind::List(_) - | TypeDefKind::Map(_, _) | TypeDefKind::Variant(_) => {} TypeDefKind::Resource => todo!("return_single for resource"), TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(..) => todo!(), + TypeDefKind::Map(..) => todo!(), } self.retptrs.push(*orig_ty); @@ -1740,23 +1735,6 @@ void __wasm_export_{ns}_{snake}_dtor({ns}_{snake}_t* arg) {{ self.finish_typedef_struct(id); } - fn type_map(&mut self, id: TypeId, _name: &str, key: &Type, value: &Type, docs: &Docs) { - self.src.h_defs("\n"); - self.docs(docs, SourceType::HDefs); - let map_ty = self.r#gen.type_name(&Type::Id(id)); - let entry_ty = format!("{map_ty}_entry_t"); - uwriteln!(self.src.h_defs, "typedef struct {entry_ty} {{"); - self.print_ty(SourceType::HDefs, key); - self.src.h_defs(" key;\n"); - self.print_ty(SourceType::HDefs, value); - self.src.h_defs(" value;\n"); - uwriteln!(self.src.h_defs, "}} {entry_ty};"); - self.start_typedef_struct(id); - uwriteln!(self.src.h_defs, "{entry_ty} *ptr;"); - self.src.h_defs("size_t len;\n"); - self.finish_typedef_struct(id); - } - fn type_fixed_length_list( &mut self, _id: TypeId, @@ -1866,24 +1844,6 @@ impl<'a> wit_bindgen_core::AnonymousTypeGenerator<'a> for InterfaceGenerator<'a> self.print_typedef_target(id); } - fn anonymous_type_map(&mut self, id: TypeId, key: &Type, value: &Type, _docs: &Docs) { - let map_ty = self.r#gen.type_name(&Type::Id(id)); - let entry_ty = format!("{map_ty}_entry_t"); - uwriteln!(self.src.h_defs, "\ntypedef struct {entry_ty} {{"); - self.print_ty(SourceType::HDefs, key); - self.src.h_defs(" key;\n"); - self.print_ty(SourceType::HDefs, value); - self.src.h_defs(" value;\n"); - uwriteln!(self.src.h_defs, "}} {entry_ty};"); - self.src.h_defs("\ntypedef "); - self.src.h_defs("struct {\n"); - uwriteln!(self.src.h_defs, "{entry_ty} *ptr;"); - self.src.h_defs("size_t len;\n"); - self.src.h_defs("}"); - self.src.h_defs(" "); - self.print_typedef_target(id); - } - fn anonymous_type_future(&mut self, id: TypeId, _ty: &Option, _docs: &Docs) { self.src.h_defs("\ntypedef uint32_t "); self.print_typedef_target(id); @@ -2045,21 +2005,6 @@ impl InterfaceGenerator<'_> { uwriteln!(self.src.c_helpers, "}}"); } - TypeDefKind::Map(key, value) => { - self.src.c_helpers("size_t map_len = ptr->len;\n"); - uwriteln!(self.src.c_helpers, "if (map_len > 0) {{"); - let map_ty = self.r#gen.type_name(&Type::Id(id)); - let entry_ty = format!("{map_ty}_entry_t"); - uwriteln!(self.src.c_helpers, "{entry_ty} *map_ptr = ptr->ptr;"); - self.src - .c_helpers("for (size_t i = 0; i < map_len; i++) {\n"); - self.free(key, "&map_ptr[i].key"); - self.free(value, "&map_ptr[i].value"); - self.src.c_helpers("}\n"); - uwriteln!(self.src.c_helpers, "free(map_ptr);"); - uwriteln!(self.src.c_helpers, "}}"); - } - TypeDefKind::Variant(v) => { self.src.c_helpers("switch ((int32_t) ptr->tag) {\n"); for (i, case) in v.cases.iter().enumerate() { @@ -2100,6 +2045,7 @@ impl InterfaceGenerator<'_> { } TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(..) => todo!(), + TypeDefKind::Map(..) => todo!(), } if c_helpers_body_start == self.src.c_helpers.len() { self.src.c_helpers.as_mut_string().truncate(c_helpers_start); @@ -2793,9 +2739,7 @@ void {name}_return({return_ty}) {{ TypeDefKind::Unknown => false, TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(key, value) => { - self.contains_droppable_borrow(key) || self.contains_droppable_borrow(value) - } + TypeDefKind::Map(..) => todo!(), } } else { false @@ -3665,11 +3609,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!("(uint8_t *) ({}).ptr", operands[0])); results.push(format!("({}).len", operands[0])); } - Instruction::MapLower { .. } => { - let _body = self.blocks.pop().unwrap(); - results.push(format!("(uint8_t *) ({}).ptr", operands[0])); - results.push(format!("({}).len", operands[0])); - } Instruction::ListLift { element, ty, .. } => { self.assert_no_droppable_borrows("list", &Type::Id(*ty)); @@ -3682,19 +3621,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { list_name, elem_name, operands[0], operands[1] )); } - Instruction::MapLift { ty, .. } => { - let _body = self.blocks.pop().unwrap(); - self.assert_no_droppable_borrows("map", &Type::Id(*ty)); - let map_name = self.r#gen.r#gen.type_name(&Type::Id(*ty)); - let entry_name = format!("{map_name}_entry_t"); - results.push(format!( - "({}) {{ ({}*)({}), ({}) }}", - map_name, entry_name, operands[0], operands[1] - )); - } Instruction::IterElem { .. } => results.push("e".to_string()), - Instruction::IterMapKey { .. } => results.push("map_key".to_string()), - Instruction::IterMapValue { .. } => results.push("map_value".to_string()), Instruction::IterBasePointer => results.push("base".to_string()), Instruction::CallWasm { sig, .. } => { @@ -4029,28 +3956,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "free({ptr});"); uwriteln!(self.src, "}}"); } - Instruction::GuestDeallocateMap { key, value } => { - let (body, results) = self.blocks.pop().unwrap(); - assert!(results.is_empty()); - let len = self.locals.tmp("len"); - uwriteln!(self.src, "size_t {len} = {};", operands[1]); - uwriteln!(self.src, "if ({len} > 0) {{"); - let ptr = self.locals.tmp("ptr"); - uwriteln!(self.src, "uint8_t *{ptr} = {};", operands[0]); - let i = self.locals.tmp("i"); - uwriteln!(self.src, "for (size_t {i} = 0; {i} < {len}; {i}++) {{"); - let size = self.r#gen.r#gen.sizes.record([*key, *value]).size; - uwriteln!( - self.src, - "uint8_t *base = {ptr} + {i} * {};", - size.format(POINTER_SIZE_EXPRESSION) - ); - uwriteln!(self.src, "(void) base;"); - uwrite!(self.src, "{body}"); - uwriteln!(self.src, "}}"); - uwriteln!(self.src, "free({ptr});"); - uwriteln!(self.src, "}}"); - } Instruction::Flush { amt } => { results.extend(operands.iter().take(*amt).cloned()); @@ -4204,7 +4109,7 @@ pub fn is_arg_by_pointer(resolve: &Resolve, ty: &Type) -> bool { TypeDefKind::Resource => todo!("is_arg_by_pointer for resource"), TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(..) => true, + TypeDefKind::Map(..) => todo!(), }, Type::String => true, _ => false, diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index f66f7f6b0..c99a472d8 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -892,7 +892,7 @@ impl CppInterfaceGenerator<'_> { TypeDefKind::Stream(_) => todo!("generate for stream"), TypeDefKind::Handle(_) => todo!("generate for handle"), TypeDefKind::FixedLengthList(_, _) => todo!(), - TypeDefKind::Map(key, value) => self.type_map(id, name, key, value, &ty.docs), + TypeDefKind::Map(_, _) => todo!(), TypeDefKind::Unknown => unreachable!(), } } @@ -1715,46 +1715,7 @@ impl CppInterfaceGenerator<'_> { self.type_name(ty, from_namespace, flavor) ) } - TypeDefKind::Map(key, value) => { - let entry_flavor = match flavor { - Flavor::BorrowedArgument | Flavor::Argument(AbiVariant::GuestImport) => { - Flavor::BorrowedArgument - } - _ => Flavor::InStruct, - }; - self.r#gen.dependencies.needs_tuple = true; - let inner = format!( - "std::tuple<{}, {}>", - self.type_name(key, from_namespace, entry_flavor), - self.type_name(value, from_namespace, entry_flavor) - ); - match flavor { - Flavor::BorrowedArgument => { - self.r#gen.dependencies.needs_span = true; - format!("std::span<{inner} const>") - } - Flavor::Argument(var) - if matches!(var, AbiVariant::GuestImport) - || self.r#gen.opts.api_style == APIStyle::Symmetric => - { - self.r#gen.dependencies.needs_span = true; - let constness = if self.r#gen.types.get(*id).has_own_handle { - "" - } else { - " const" - }; - format!("std::span<{inner}{constness}>") - } - Flavor::Argument(AbiVariant::GuestExport) => { - self.r#gen.dependencies.needs_wit = true; - format!("wit::vector<{inner}>") - } - _ => { - self.r#gen.dependencies.needs_wit = true; - format!("wit::vector<{inner}>") - } - } - } + TypeDefKind::Map(_, _) => todo!(), TypeDefKind::Unknown => todo!(), }, Type::ErrorContext => todo!(), @@ -2260,10 +2221,6 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> // nothing to do here } - fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) { - // nothing to do here - } - fn type_fixed_length_list( &mut self, _id: TypeId, @@ -2634,43 +2591,6 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } results.push(len); } - abi::Instruction::MapLower { - key, - value, - realloc, - } => { - let tmp = self.tmp(); - let body = self.blocks.pop().unwrap(); - let map = format!("map{tmp}"); - let ptr = format!("ptr{tmp}"); - let len = format!("len{tmp}"); - let size = self.r#gen.sizes.record([*key, *value]).size; - self.push_str(&format!("auto&& {} = {};\n", map, operands[0])); - self.push_str(&format!( - "auto {} = ({})({}.data());\n", - ptr, - self.r#gen.r#gen.opts.ptr_type(), - map - )); - self.push_str(&format!("auto {len} = (size_t)({map}.size());\n")); - self.push_str(&format!("for (size_t i = 0; i < {len}; ++i) {{\n")); - self.push_str(&format!( - "auto base = {ptr} + i * {size};\n", - size = size.format(POINTER_SIZE_EXPRESSION) - )); - self.push_str(&format!("auto&& map_entry = {map}[i];\n")); - self.push_str("auto&& map_key = std::get<0>(map_entry);\n"); - self.push_str("auto&& map_value = std::get<1>(map_entry);\n"); - self.push_str(&format!("{}\n", body.0)); - self.push_str("}\n"); - if realloc.is_none() { - results.push(ptr); - } else { - uwriteln!(self.src, "{}.leak();\n", operands[0]); - results.push(ptr); - } - results.push(len); - } abi::Instruction::ListCanonLift { element, .. } => { let tmp = self.tmp(); let len = format!("len{tmp}"); @@ -2776,77 +2696,6 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(move_if_necessary(&result)); } } - abi::Instruction::MapLift { key, value, .. } => { - let body = self.blocks.pop().unwrap(); - let tmp = self.tmp(); - let size = self.r#gen.sizes.record([*key, *value]).size; - let flavor = if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric - && matches!(self.variant, AbiVariant::GuestExport) - { - Flavor::BorrowedArgument - } else { - Flavor::InStruct - }; - self.r#gen.r#gen.dependencies.needs_tuple = true; - let entry_type = format!( - "std::tuple<{}, {}>", - self.r#gen.type_name(key, &self.namespace, flavor), - self.r#gen.type_name(value, &self.namespace, flavor) - ); - let len = format!("len{tmp}"); - let base = format!("base{tmp}"); - let result = format!("result{tmp}"); - self.push_str(&format!( - "auto {base} = {operand0};\n", - operand0 = operands[0] - )); - self.push_str(&format!( - "auto {len} = {operand1};\n", - operand1 = operands[1] - )); - self.push_str(&format!( - r#"auto {result} = wit::vector<{entry_type}>::allocate({len}); - "#, - )); - - if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric - && matches!(self.variant, AbiVariant::GuestExport) - { - assert!(self.needs_dealloc); - self.push_str(&format!("if ({len}>0) _deallocate.push_back({base});\n")); - } - - uwriteln!(self.src, "for (unsigned i=0; i<{len}; ++i) {{"); - uwriteln!( - self.src, - "auto base = {base} + i * {size};", - size = size.format(POINTER_SIZE_EXPRESSION) - ); - uwrite!(self.src, "{}", body.0); - let key_result = move_if_necessary(&body.1[0]); - let value_result = move_if_necessary(&body.1[1]); - uwriteln!( - self.src, - "auto e{tmp} = std::make_tuple({key_result}, {value_result});" - ); - if let Some(code) = self.leak_on_insertion.take() { - assert!(self.needs_dealloc); - uwriteln!(self.src, "{code}"); - } - uwriteln!(self.src, "{result}.initialize(i, std::move(e{tmp}));"); - uwriteln!(self.src, "}}"); - - if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric - && matches!(self.variant, AbiVariant::GuestExport) - { - results.push(format!("{result}.get_const_view()")); - self.leak_on_insertion.replace(format!( - "if ({len}>0) _deallocate.push_back((void*){result}.leak());\n" - )); - } else { - results.push(move_if_necessary(&result)); - } - } abi::Instruction::FixedLengthListLift { element, size, @@ -2935,8 +2784,6 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str("\n}\n}\n"); } abi::Instruction::IterElem { .. } => results.push("iter_elem".to_string()), - abi::Instruction::IterMapKey { .. } => results.push("map_key".to_string()), - abi::Instruction::IterMapValue { .. } => results.push("map_value".to_string()), abi::Instruction::IterBasePointer => results.push("base".to_string()), abi::Instruction::RecordLower { record, .. } => { let op = &operands[0]; @@ -3613,29 +3460,6 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { uwriteln!(self.src, "free((void*) ({ptr}));"); uwriteln!(self.src, "}}"); } - abi::Instruction::GuestDeallocateMap { key, value } => { - let (body, results) = self.blocks.pop().unwrap(); - assert!(results.is_empty()); - let tmp = self.tmp(); - let ptr = self.tempname("ptr", tmp); - let len = self.tempname("len", tmp); - uwriteln!(self.src, "uint8_t* {ptr} = {};", operands[0]); - uwriteln!(self.src, "size_t {len} = {};", operands[1]); - let i = self.tempname("i", tmp); - uwriteln!(self.src, "for (size_t {i} = 0; {i} < {len}; {i}++) {{"); - let size = self.r#gen.sizes.record([*key, *value]).size; - uwriteln!( - self.src, - "uint8_t* base = {ptr} + {i} * {size};", - size = size.format(POINTER_SIZE_EXPRESSION) - ); - uwriteln!(self.src, "(void) base;"); - uwrite!(self.src, "{body}"); - uwriteln!(self.src, "}}"); - uwriteln!(self.src, "if ({len} > 0) {{"); - uwriteln!(self.src, "free((void*) ({ptr}));"); - uwriteln!(self.src, "}}"); - } abi::Instruction::GuestDeallocateVariant { blocks } => { let blocks = self .blocks @@ -3679,6 +3503,13 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } abi::Instruction::AsyncTaskReturn { .. } => todo!(), abi::Instruction::DropHandle { .. } => todo!(), + abi::Instruction::MapLower { .. } + | abi::Instruction::MapLift { .. } + | abi::Instruction::IterMapKey { .. } + | abi::Instruction::IterMapValue { .. } + | abi::Instruction::GuestDeallocateMap { .. } => { + todo!("map types are not yet supported in this backend") + } } } diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index 43a135158..badb8c42e 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -983,81 +983,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!("{list}.Count")); } - Instruction::MapLower { key, value, realloc } => { - let Block { - body, - results: block_results, - element: block_element, - base, - } = self.blocks.pop().unwrap(); - assert!(block_results.is_empty()); - - let map = &operands[0]; - let tuple_ty = format!( - "({}, {})", - self.interface_gen.type_name_with_qualifier(key, true), - self.interface_gen.type_name_with_qualifier(value, true) - ); - let layout = self.interface_gen.csharp_gen.sizes.record([*key, *value]); - let size = layout.size.size_wasm32(); - let index = self.locals.tmp("index"); - - let address = self.locals.tmp("address"); - let buffer_size = self.locals.tmp("bufferSize"); - let align = layout.align.align_wasm32(); - - let (array_size, element_type) = crate::world_generator::dotnet_aligned_array( - size, - align, - ); - let ret_area = self.locals.tmp("retArea"); - - match realloc { - None => { - self.needs_cleanup = true; - uwrite!( - self.src, - " - void* {address}; - if (({size} * {map}.Count) < 1024) {{ - var {ret_area} = stackalloc {element_type}[({array_size}*{map}.Count)+1]; - {address} = (void*)(((int){ret_area}) + ({align} - 1) & -{align}); - }} - else - {{ - var {buffer_size} = {size} * (nuint){map}.Count; - {address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align}); - cleanups.Add(() => global::System.Runtime.InteropServices.NativeMemory.AlignedFree({address})); - }} - " - ); - } - Some(_) => { - uwrite!( - self.src, - " - var {buffer_size} = {size} * (nuint){map}.Count; - void* {address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align}); - " - ); - } - } - - uwrite!( - self.src, - " - for (int {index} = 0; {index} < {map}.Count; ++{index}) {{ - {tuple_ty} {block_element} = {map}[{index}]; - int {base} = (int){address} + ({index} * {size}); - {body} - }} - " - ); - - results.push(format!("(int){address}")); - results.push(format!("{map}.Count")); - } - Instruction::ListLift { element, .. } => { let Block { body, @@ -1096,61 +1021,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(array); } - Instruction::MapLift { key, value, .. } => { - let Block { - body, - results: block_results, - base, - .. - } = self.blocks.pop().unwrap(); - let [map_key, map_value] = &block_results[..] else { - todo!("result count == {}", block_results.len()) - }; - let address = &operands[0]; - let length = &operands[1]; - let map = self.locals.tmp("map"); - let tuple_ty = format!( - "({}, {})", - self.interface_gen.type_name_with_qualifier(key, true), - self.interface_gen.type_name_with_qualifier(value, true) - ); - let layout = self.interface_gen.csharp_gen.sizes.record([*key, *value]); - let size = layout.size.size_wasm32(); - let index = self.locals.tmp("index"); - - uwrite!( - self.src, - " - var {map} = new global::System.Collections.Generic.List<{tuple_ty}>({length}); - for (int {index} = 0; {index} < {length}; ++{index}) {{ - nint {base} = {address} + ({index} * {size}); - {body} - {map}.Add(({map_key}, {map_value})); - }} - - if ({length} > 0) {{ - global::System.Runtime.InteropServices.NativeMemory.Free((void*){address}); - }} - " - ); - - results.push(map); - } - Instruction::IterElem { .. } => { results.push(self.block_storage.last().unwrap().element.clone()) } - Instruction::IterMapKey { .. } => results.push(format!( - "{}.Item1", - self.block_storage.last().unwrap().element - )), - - Instruction::IterMapValue { .. } => results.push(format!( - "{}.Item2", - self.block_storage.last().unwrap().element - )), - Instruction::IterBasePointer => { results.push(self.block_storage.last().unwrap().base.clone()) } @@ -1407,46 +1281,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#, operands[0]); } - Instruction::GuestDeallocateMap { key, value } => { - let Block { - body, - results: block_results, - base, - .. - } = self.blocks.pop().unwrap(); - assert!(block_results.is_empty()); - - let address = &operands[0]; - let length = &operands[1]; - let size = self - .interface_gen - .csharp_gen - .sizes - .record([*key, *value]) - .size - .size_wasm32(); - - if !body.trim().is_empty() { - let index = self.locals.tmp("index"); - - uwrite!( - self.src, - " - for (int {index} = 0; {index} < {length}; ++{index}) {{ - int {base} = (int){address} + ({index} * {size}); - {body} - }} - " - ); - } - - uwriteln!( - self.src, - r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#, - operands[0] - ); - } - Instruction::HandleLower { handle, .. @@ -1634,6 +1468,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { | Instruction::FixedLengthListLower { .. } | Instruction::FixedLengthListLowerToMemory { .. } | Instruction::FixedLengthListLiftFromMemory { .. } + | Instruction::MapLower { .. } + | Instruction::MapLift { .. } + | Instruction::IterMapKey { .. } + | Instruction::IterMapValue { .. } + | Instruction::GuestDeallocateMap { .. } => { dbg!(inst); todo!() diff --git a/crates/csharp/src/interface.rs b/crates/csharp/src/interface.rs index 1efb79b62..49d55cd8c 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -134,9 +134,6 @@ impl InterfaceGenerator<'_> { TypeDefKind::Option(t) => self.type_option(type_id, typedef_name, t, &type_def.docs), TypeDefKind::Record(t) => self.type_record(type_id, typedef_name, t, &type_def.docs), TypeDefKind::List(t) => self.type_list(type_id, typedef_name, t, &type_def.docs), - TypeDefKind::Map(key, value) => { - self.type_map(type_id, typedef_name, key, value, &type_def.docs) - } TypeDefKind::Variant(t) => self.type_variant(type_id, typedef_name, t, &type_def.docs), TypeDefKind::Result(t) => self.type_result(type_id, typedef_name, t, &type_def.docs), TypeDefKind::Handle(_) => { @@ -1036,7 +1033,6 @@ var {async_status_var} = {raw_name}({wasm_params}); TypeDefKind::Option(_ty) => "".to_owned(), TypeDefKind::Result(_result) => "".to_owned(), TypeDefKind::List(_list) => "".to_owned(), - TypeDefKind::Map(_, _) => "".to_owned(), TypeDefKind::Tuple(_tuple) => "".to_owned(), TypeDefKind::Type(inner_type) => self.global_if_user_type(inner_type), _ => "global::".to_owned(), @@ -1113,13 +1109,6 @@ var {async_status_var} = {raw_name}({wasm_params}); ) } } - TypeDefKind::Map(key, value) => { - format!( - "global::System.Collections.Generic.List<({}, {})>", - self.type_name_with_qualifier(key, qualifier), - self.type_name_with_qualifier(value, qualifier) - ) - } TypeDefKind::Tuple(tuple) => { let count = tuple.types.len(); self.csharp_gen.tuple_counts.insert(count); @@ -1751,10 +1740,6 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> { self.type_name(&Type::Id(id)); } - fn type_map(&mut self, id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) { - self.type_name(&Type::Id(id)); - } - fn type_fixed_length_list( &mut self, _id: TypeId, diff --git a/crates/go/src/lib.rs b/crates/go/src/lib.rs index 7f6e95fc0..1550c0c9a 100644 --- a/crates/go/src/lib.rs +++ b/crates/go/src/lib.rs @@ -28,8 +28,6 @@ const POINTER_SIZE_EXPRESSION: &str = "4"; const VARIANT_PAYLOAD_NAME: &str = "payload"; const ITER_BASE_POINTER: &str = "base"; const ITER_ELEMENT: &str = "element"; -const ITER_MAP_KEY: &str = "mapKey"; -const ITER_MAP_VALUE: &str = "mapValue"; const IMPORT_RETURN_AREA: &str = "returnArea"; const EXPORT_RETURN_AREA: &str = "exportReturnArea"; const SYNC_EXPORT_PINNER: &str = "syncExportPinner"; @@ -350,13 +348,6 @@ impl Go { .join(", "); format!("witTypes.Tuple{count}[{types}]") } - TypeDefKind::Map(key, value) => { - imports.insert(remote_pkg("types")); - self.tuples.insert(2); - let key = self.type_name(resolve, *key, local, in_import, imports); - let value = self.type_name(resolve, *value, local, in_import, imports); - format!("[]witTypes.Tuple2[{key}, {value}]") - } TypeDefKind::Future(ty) => { self.need_future = true; imports.insert(remote_pkg("types")); @@ -1744,35 +1735,6 @@ for index, {ITER_ELEMENT} := range {slice} {{ {ITER_BASE_POINTER} := unsafe.Add({result}, index * {size}) {body} }} -" - ); - results.push(format!("uintptr({result})")); - results.push(length); - } - Instruction::MapLower { key, value, .. } => { - self.need_unsafe = true; - self.need_pinner = true; - self.imports.insert(remote_pkg("runtime")); - let (body, body_results) = self.blocks.pop().unwrap(); - assert!(body_results.is_empty()); - let map = &operands[0]; - let slice = self.locals.tmp("slice"); - let result = self.locals.tmp("result"); - let length = self.locals.tmp("length"); - let layout = self.generator.sizes.record([*key, *value]); - let size = layout.size.format(POINTER_SIZE_EXPRESSION); - let align = layout.align.format(POINTER_SIZE_EXPRESSION); - uwriteln!( - self.src, - "{slice} := {map} -{length} := uint32(len({slice})) -{result} := witRuntime.Allocate({PINNER}, uintptr({length} * {size}), {align}) -for index, {ITER_ELEMENT} := range {slice} {{ - {ITER_MAP_KEY} := {ITER_ELEMENT}.F0 - {ITER_MAP_VALUE} := {ITER_ELEMENT}.F1 - {ITER_BASE_POINTER} := unsafe.Add({result}, index * {size}) - {body} -}} " ); results.push(format!("uintptr({result})")); @@ -1799,33 +1761,6 @@ for index := 0; index < int({length}); index++ {{ {body} {result} = append({result}, {body_result}) }} -" - ); - results.push(result); - } - Instruction::MapLift { key, value, .. } => { - self.need_unsafe = true; - self.imports.insert(remote_pkg("types")); - self.generator.tuples.insert(2); - let (body, body_results) = self.blocks.pop().unwrap(); - let [map_key, map_value] = &body_results[..] else { - panic!("expected two map block results"); - }; - let pointer = &operands[0]; - let length = &operands[1]; - let result = self.locals.tmp("result"); - let layout = self.generator.sizes.record([*key, *value]); - let size = layout.size.format(POINTER_SIZE_EXPRESSION); - let key_type = self.type_name(resolve, **key); - let value_type = self.type_name(resolve, **value); - uwriteln!( - self.src, - "{result} := make([]witTypes.Tuple2[{key_type}, {value_type}], 0, {length}) -for index := 0; index < int({length}); index++ {{ - {ITER_BASE_POINTER} := unsafe.Add(unsafe.Pointer({pointer}), index * {size}) - {body} - {result} = append({result}, witTypes.Tuple2[{key_type}, {value_type}]{{{map_key}, {map_value}}}) -}} " ); results.push(result); @@ -2429,8 +2364,6 @@ default: } Instruction::VariantPayloadName => results.push(VARIANT_PAYLOAD_NAME.into()), Instruction::IterElem { .. } => results.push(ITER_ELEMENT.into()), - Instruction::IterMapKey { .. } => results.push(ITER_MAP_KEY.into()), - Instruction::IterMapValue { .. } => results.push(ITER_MAP_VALUE.into()), Instruction::IterBasePointer => results.push(ITER_BASE_POINTER.into()), Instruction::I32Const { val } => results.push(format!("int32({val})")), Instruction::ConstZero { tys } => { @@ -2550,10 +2483,6 @@ lifters = append(lifters, func() {{ Instruction::GuestDeallocate { .. } => { // Nothing to do here; should be handled when calling `pinner.Unpin()` } - Instruction::GuestDeallocateMap { .. } => { - let _ = self.blocks.pop().unwrap(); - // Nothing to do here; deallocation is managed by pinner cleanup. - } _ => unimplemented!("{instruction:?}"), } } @@ -2978,13 +2907,6 @@ const ( uwriteln!(self.src, "{docs}type {name} = []{ty}"); } - fn type_map(&mut self, id: TypeId, name: &str, _key: &Type, _value: &Type, docs: &Docs) { - let name = name.to_upper_camel_case(); - let ty = self.type_name(self.resolve, Type::Id(id)); - let docs = format_docs(docs); - uwriteln!(self.src, "{docs}type {name} = {ty}"); - } - fn type_fixed_length_list(&mut self, _: TypeId, name: &str, ty: &Type, size: u32, docs: &Docs) { let name = name.to_upper_camel_case(); let ty = self.type_name(self.resolve, *ty); @@ -3187,9 +3109,6 @@ fn any(resolve: &Resolve, ty: Type, fun: &dyn Fn(Type) -> bool) -> bool { .or_else(|| result.err.map(|ty| any(resolve, ty, fun))) .unwrap_or(false), TypeDefKind::Tuple(tuple) => tuple.types.iter().any(|ty| any(resolve, *ty, fun)), - TypeDefKind::Map(key, value) => { - any(resolve, *key, fun) || any(resolve, *value, fun) - } TypeDefKind::Future(ty) | TypeDefKind::Stream(ty) => { ty.map(|ty| any(resolve, ty, fun)).unwrap_or(false) } diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index ad66e80f1..45e08cbb5 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1319,10 +1319,6 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { // Not needed. They will become `Array[T]` or `FixedArray[T]` in Moonbit } - fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) { - // Not needed. They will become `Array[(K, V)]` in Moonbit - } - fn type_fixed_length_list( &mut self, _id: TypeId, @@ -2203,57 +2199,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - Instruction::MapLower { - key, - value, - realloc, - } => { - let Block { - body, - results: block_results, - } = self.blocks.pop().unwrap(); - assert!(block_results.is_empty()); - - let op = &operands[0]; - let layout = self.r#gen.r#gen.sizes.record([*key, *value]); - let size = layout.size.size_wasm32(); - let address = self.locals.tmp("address"); - let key_ty = self - .r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, key); - let value_ty = self - .r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, value); - let tuple_ty = format!("({key_ty}, {value_ty})"); - let index = self.locals.tmp("index"); - - self.r#gen.ffi_imports.insert(ffi::MALLOC); - uwrite!( - self.src, - " - let {address} = mbt_ffi_malloc(({op}).length() * {size}); - for {index} = 0; {index} < ({op}).length(); {index} = {index} + 1 {{ - let iter_elem : {tuple_ty} = ({op})[({index})] - let iter_map_key = (iter_elem).0 - let iter_map_value = (iter_elem).1 - let iter_base = {address} + ({index} * {size}); - {body} - }} - ", - ); - - results.push(address.clone()); - results.push(format!("({op}).length()")); - - if realloc.is_none() { - self.cleanup.push(Cleanup { address }); - } - } - Instruction::ListLift { element, .. } => { let Block { body, @@ -2293,55 +2238,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(array); } - Instruction::MapLift { key, value, .. } => { - let Block { - body, - results: block_results, - } = self.blocks.pop().unwrap(); - let [map_key, map_value] = &block_results[..] else { - todo!("result count == {}", block_results.len()) - }; - let address = &operands[0]; - let length = &operands[1]; - let array = self.locals.tmp("array"); - let key_ty = self - .r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, key); - let value_ty = self - .r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, value); - let tuple_ty = format!("({key_ty}, {value_ty})"); - let layout = self.r#gen.r#gen.sizes.record([*key, *value]); - let size = layout.size.size_wasm32(); - let index = self.locals.tmp("index"); - - self.r#gen.ffi_imports.insert(ffi::FREE); - uwrite!( - self.src, - " - let {array} : Array[{tuple_ty}] = []; - for {index} = 0; {index} < ({length}); {index} = {index} + 1 {{ - let iter_base = ({address}) + ({index} * {size}) - {body} - {array}.push(({map_key}, {map_value})) - }} - mbt_ffi_free({address}) - ", - ); - - results.push(array); - } - Instruction::IterElem { .. } => results.push("iter_elem".into()), - Instruction::IterMapKey { .. } => results.push("iter_map_key".into()), - - Instruction::IterMapValue { .. } => results.push("iter_map_value".into()), - Instruction::IterBasePointer => results.push("iter_base".into()), Instruction::CallWasm { sig, .. } => { @@ -2726,38 +2624,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "mbt_ffi_free({address})",); } - Instruction::GuestDeallocateMap { key, value } => { - let Block { body, results, .. } = self.blocks.pop().unwrap(); - assert!(results.is_empty()); - - let address = &operands[0]; - let length = &operands[1]; - let size = self - .r#gen - .r#gen - .sizes - .record([*key, *value]) - .size - .size_wasm32(); - - if !body.trim().is_empty() { - let index = self.locals.tmp("index"); - - uwrite!( - self.src, - " - for {index} = 0; {index} < ({length}); {index} = {index} + 1 {{ - let iter_base = ({address}) + ({index} * {size}) - {body} - }} - " - ); - } - - self.r#gen.ffi_imports.insert(ffi::FREE); - uwriteln!(self.src, "mbt_ffi_free({address})",); - } - Instruction::Flush { amt } => { results.extend(operands.iter().take(*amt).cloned()); } @@ -2941,6 +2807,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!("FixedArray::from_array({array}[:])")); } + + Instruction::MapLower { .. } + | Instruction::MapLift { .. } + | Instruction::IterMapKey { .. } + | Instruction::IterMapValue { .. } + | Instruction::GuestDeallocateMap { .. } => { + todo!("map types are not yet supported in this backend") + } } } diff --git a/crates/moonbit/src/pkg.rs b/crates/moonbit/src/pkg.rs index 0f6d9eb23..e7c1a1057 100644 --- a/crates/moonbit/src/pkg.rs +++ b/crates/moonbit/src/pkg.rs @@ -217,11 +217,6 @@ impl PkgResolver { .join(", ") ) } - TypeDefKind::Map(key, value) => { - let key = self.type_name(this, &key); - let value = self.type_name(this, &value); - format!("Array[({key}, {value})]") - } TypeDefKind::Option(ty) => { format!("{}?", self.type_name(this, &ty)) } From 90c5adb6be122d28249a6ae6cd4fdac6cae594a3 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 24 Mar 2026 12:50:22 -0400 Subject: [PATCH 03/11] revert: drop unrelated go.mod replace path fix This change was a fix for a latent bug from the vanity imports migration but is unrelated to map support. Removing to keep the PR focused on Rust. --- crates/test/src/go.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/test/src/go.rs b/crates/test/src/go.rs index 83060b3a9..99d8487a8 100644 --- a/crates/test/src/go.rs +++ b/crates/test/src/go.rs @@ -161,7 +161,7 @@ fn replace_bindings_go_mod(runner: &Runner, bindings_dir: &Path) -> Result<()> { super::write_if_different( &bindings_dir.join("go.mod"), format!( - "module wit_component\n\ngo 1.25\n\nreplace go.bytecodealliance.org/pkg => {}", + "module wit_component\n\ngo 1.25\n\nreplace go.bytecodealliance.org => {}", go_package_path.display() ), )?; From 5d1a0557283fbbac32c0096008a16a52a1a38000 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 28 Mar 2026 01:37:55 -0400 Subject: [PATCH 04/11] refactor: clean up map type support across backends - Make `InterfaceGenerator::type_map` a required trait method instead of providing a default `todo!()` impl, so missing implementations are caught at compile time rather than runtime. - Remove `RuntimeItem::MapType` indirection in the Rust backend; reference `{rt}::Map` directly instead of emitting a `pub use` re-export. - Fix borrowed map rendering to use `&Map` instead of the incorrect `&[(K, V)]` slice syntax that was copy-pasted from lists. - Add explanatory comments on map ABI methods that reuse list read/write helpers (maps share the list> memory layout). - Add explicit `type_map` todo stubs to C, C++, C#, Go, and MoonBit backends. --- crates/c/src/lib.rs | 4 ++++ crates/core/src/abi.rs | 2 ++ crates/core/src/lib.rs | 5 +---- crates/cpp/src/lib.rs | 11 +++++++++++ crates/csharp/src/interface.rs | 4 ++++ crates/go/src/lib.rs | 4 ++++ crates/moonbit/src/lib.rs | 4 ++++ crates/rust/src/bindgen.rs | 2 +- crates/rust/src/interface.rs | 33 ++++++++++++--------------------- crates/rust/src/lib.rs | 5 ----- 10 files changed, 43 insertions(+), 31 deletions(-) diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 2d53d23dd..74963dc68 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -1746,6 +1746,10 @@ void __wasm_export_{ns}_{snake}_dtor({ns}_{snake}_t* arg) {{ todo!("named fixed-length list types are not yet supported in the C backend") } + fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) { + todo!("map types are not yet supported in the C backend") + } + fn type_future(&mut self, id: TypeId, _name: &str, _ty: &Option, docs: &Docs) { self.src.h_defs("\n"); self.docs(docs, SourceType::HDefs); diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index f771b6b0a..3b86429fe 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -1975,6 +1975,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.write_to_memory(t, addr, offset), TypeDefKind::List(_) => self.write_list_to_memory(ty, addr, offset), + // Maps have the same linear memory layout as list>. TypeDefKind::Map(_, _) => self.write_list_to_memory(ty, addr, offset), TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::Handle(_) => { @@ -2183,6 +2184,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::Type(t) => self.read_from_memory(t, addr, offset), TypeDefKind::List(_) => self.read_list_from_memory(ty, addr, offset), + // Maps have the same linear memory layout as list>. TypeDefKind::Map(_, _) => self.read_list_from_memory(ty, addr, offset), TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::Handle(_) => { diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 5e124f94d..0fc9cf476 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -166,10 +166,7 @@ pub trait InterfaceGenerator<'a> { fn type_alias(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); fn type_list(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); fn type_fixed_length_list(&mut self, id: TypeId, name: &str, ty: &Type, size: u32, docs: &Docs); - fn type_map(&mut self, id: TypeId, name: &str, key: &Type, value: &Type, docs: &Docs) { - let _ = (id, name, key, value, docs); - todo!("map types are not yet supported in this backend"); - } + fn type_map(&mut self, id: TypeId, name: &str, key: &Type, value: &Type, docs: &Docs); fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs); fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs); fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs); diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index c99a472d8..e64a5326e 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -2232,6 +2232,17 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> todo!("named fixed-length list types are not yet supported in the C++ backend") } + fn type_map( + &mut self, + _id: TypeId, + _name: &str, + _key: &wit_bindgen_core::wit_parser::Type, + _value: &wit_bindgen_core::wit_parser::Type, + _docs: &wit_bindgen_core::wit_parser::Docs, + ) { + todo!("map types are not yet supported in the C++ backend") + } + fn type_builtin( &mut self, _id: TypeId, diff --git a/crates/csharp/src/interface.rs b/crates/csharp/src/interface.rs index 49d55cd8c..d8d491d71 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -1751,6 +1751,10 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> { todo!("named fixed-length list types are not yet supported in the C# backend") } + fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) { + todo!("map types are not yet supported in the C# backend") + } + fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) { unimplemented!(); } diff --git a/crates/go/src/lib.rs b/crates/go/src/lib.rs index 1550c0c9a..b93b17c4d 100644 --- a/crates/go/src/lib.rs +++ b/crates/go/src/lib.rs @@ -2914,6 +2914,10 @@ const ( uwriteln!(self.src, "{docs}type {name} = [{size}]{ty}"); } + fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) { + todo!("map types are not yet supported in the Go backend") + } + fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { _ = (id, name, ty, docs); todo!() diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 45e08cbb5..f24ac232d 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1330,6 +1330,10 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { // Not needed. They will become `FixedArray[T]` in Moonbit } + fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) { + todo!("map types are not yet supported in the MoonBit backend") + } + fn type_future(&mut self, _id: TypeId, _name: &str, _ty: &Option, _docs: &Docs) { unimplemented!() // Not needed } diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 01f96ff7f..6a73cafc2 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -876,7 +876,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let len = format!("len{tmp}"); let base = format!("base{tmp}"); let result = format!("result{tmp}"); - let map = self.r#gen.path_to_map(); + let map = format!("{}::Map", self.r#gen.r#gen.runtime_path()); self.push_str(&format!( "let {base} = {operand0};\n", operand0 = operands[0] diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 3145f6802..c02c730d5 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -1766,10 +1766,11 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) } fn print_ty(&mut self, ty: &Type, mode: TypeMode) { - // If we have a typedef of a string or a list, the typedef is an alias - // for `String` or `Vec`. If this is a borrow, instead of borrowing - // them as `&String` or `&Vec`, use `&str` or `&[T]` so that callers - // don't need to create owned copies. + // If we have a typedef of a string, list, or map, the typedef is an + // alias for `String`, `Vec`, or `Map`. If this is a borrow, + // instead of borrowing them as `&String` or `&Vec`, use `&str` or + // `&[T]` so that callers don't need to create owned copies. Maps are + // borrowed as `&Map`. if let Type::Id(id) = ty { let id = dealias(self.resolve, *id); let typedef = &self.resolve.types[id]; @@ -1947,20 +1948,14 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) self.push_str(lifetime); self.push_str(" "); } - self.push_str("[("); - self.print_ty(key, key_mode); - self.push_str(", "); - self.print_ty(value, value_mode); - self.push_str(")]"); - } else { - let path = self.path_to_map(); - self.push_str(&path); - self.push_str("::<"); - self.print_ty(key, key_mode); - self.push_str(", "); - self.print_ty(value, value_mode); - self.push_str(">"); } + let path = format!("{}::Map", self.r#gen.runtime_path()); + self.push_str(&path); + self.push_str("::<"); + self.print_ty(key, key_mode); + self.push_str(", "); + self.print_ty(value, value_mode); + self.push_str(">"); } fn print_generics(&mut self, lifetime: Option<&str>) { @@ -2589,10 +2584,6 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) self.path_from_runtime_module(RuntimeItem::VecType, "Vec") } - pub fn path_to_map(&mut self) -> String { - self.path_from_runtime_module(RuntimeItem::MapType, "Map") - } - pub fn path_to_string(&mut self) -> String { self.path_from_runtime_module(RuntimeItem::StringType, "String") } diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 3ff32473e..3df749648 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -98,7 +98,6 @@ impl TypeGeneration { #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] enum RuntimeItem { AllocCrate, - MapType, StringType, StdAllocModule, VecType, @@ -558,10 +557,6 @@ pub mod wit_stream {{ RuntimeItem::AllocCrate => { uwriteln!(self.src, "extern crate alloc as alloc_crate;"); } - RuntimeItem::MapType => { - let rt = self.runtime_path().to_string(); - uwriteln!(self.src, "pub use {rt}::Map;"); - } RuntimeItem::StdAllocModule => { self.rt_module.insert(RuntimeItem::AllocCrate); uwriteln!(self.src, "pub use alloc_crate::alloc;"); From c08547bf0fc3f5fcee03be33399aa16baaaa02e0 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 28 Mar 2026 02:39:22 -0400 Subject: [PATCH 05/11] Address PR review: make anonymous_type_map required and fix runner test - Make `anonymous_type_map` a required method on `AnonymousTypeGenerator` trait (no default impl), consistent with all other methods in the trait. - Add explicit `anonymous_type_map` todo!() implementation to C backend. - Fix map runner test to construct proper Map types instead of passing slice literals, matching the generated `&Map` parameter signatures. --- crates/c/src/lib.rs | 10 ++++++++++ crates/core/src/lib.rs | 5 +---- tests/runtime/map/runner.rs | 18 +++++++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 74963dc68..871484442 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -1871,6 +1871,16 @@ impl<'a> wit_bindgen_core::AnonymousTypeGenerator<'a> for InterfaceGenerator<'a> ) { todo!("print_anonymous_type for fixed length list"); } + + fn anonymous_type_map( + &mut self, + _id: TypeId, + _key: &Type, + _value: &Type, + _docs: &Docs, + ) { + todo!("anonymous map types are not yet supported in the C backend"); + } } pub enum CTypeNameInfo<'a> { diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 0fc9cf476..b85754160 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -218,10 +218,7 @@ pub trait AnonymousTypeGenerator<'a> { fn anonymous_type_result(&mut self, id: TypeId, ty: &Result_, docs: &Docs); fn anonymous_type_list(&mut self, id: TypeId, ty: &Type, docs: &Docs); fn anonymous_type_fixed_length_list(&mut self, id: TypeId, ty: &Type, size: u32, docs: &Docs); - fn anonymous_type_map(&mut self, id: TypeId, key: &Type, value: &Type, docs: &Docs) { - let _ = (id, key, value, docs); - todo!("anonymous map types are not yet supported in this backend"); - } + fn anonymous_type_map(&mut self, id: TypeId, key: &Type, value: &Type, docs: &Docs); fn anonymous_type_future(&mut self, id: TypeId, ty: &Option, docs: &Docs); fn anonymous_type_stream(&mut self, id: TypeId, ty: &Option, docs: &Docs); fn anonymous_type_type(&mut self, id: TypeId, ty: &Type, docs: &Docs); diff --git a/tests/runtime/map/runner.rs b/tests/runtime/map/runner.rs index d503f58d6..a38ba1663 100644 --- a/tests/runtime/map/runner.rs +++ b/tests/runtime/map/runner.rs @@ -10,19 +10,19 @@ export!(Component); impl Guest for Component { fn run() { - let ids_by_name = named_roundtrip(&[ - (1, "one".to_string()), - (1, "uno".to_string()), - (2, "two".to_string()), - ]); + let mut input = NamesById::new(); + input.insert(1, "one".to_string()); + input.insert(1, "uno".to_string()); + input.insert(2, "two".to_string()); + let ids_by_name = named_roundtrip(&input); assert_eq!(ids_by_name.get("uno"), Some(&1)); assert_eq!(ids_by_name.get("two"), Some(&2)); assert_eq!(ids_by_name.get("one"), None); - let bytes_by_name = bytes_roundtrip(&[ - ("hello".to_string(), b"world".to_vec()), - ("bin".to_string(), vec![0u8, 1, 2]), - ]); + let mut bytes_input = BytesByName::new(); + bytes_input.insert("hello".to_string(), b"world".to_vec()); + bytes_input.insert("bin".to_string(), vec![0u8, 1, 2]); + let bytes_by_name = bytes_roundtrip(&bytes_input); assert_eq!( bytes_by_name.get("hello").map(Vec::as_slice), Some(b"world".as_slice()) From a3fb574a0ee6fc66fd7cd2535b17fd83c0fd9d9c Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 28 Mar 2026 02:53:20 -0400 Subject: [PATCH 06/11] Mark map.wit codegen test as expected failure for unsupported backends The map.wit codegen test hits todo!() panics in backends that don't yet implement map support (C, C++, C#, Go, MoonBit). Add map.wit to each backend's should_fail_verify so CI treats these as expected failures. --- crates/test/src/c.rs | 4 +++- crates/test/src/cpp.rs | 2 +- crates/test/src/csharp.rs | 1 + crates/test/src/go.rs | 1 + crates/test/src/moonbit.rs | 5 ++--- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/test/src/c.rs b/crates/test/src/c.rs index a34f2fc55..523984d01 100644 --- a/crates/test/src/c.rs +++ b/crates/test/src/c.rs @@ -56,7 +56,9 @@ impl LanguageMethods for C { config: &crate::config::WitConfig, _args: &[String], ) -> bool { - config.error_context || name.starts_with("named-fixed-length-list.wit") + config.error_context + || name.starts_with("named-fixed-length-list.wit") + || name == "map.wit" } fn codegen_test_variants(&self) -> &[(&str, &[&str])] { diff --git a/crates/test/src/cpp.rs b/crates/test/src/cpp.rs index 0456ec1bd..33751f60a 100644 --- a/crates/test/src/cpp.rs +++ b/crates/test/src/cpp.rs @@ -46,7 +46,7 @@ impl LanguageMethods for Cpp { _args: &[String], ) -> bool { return match name { - "issue1514-6.wit" | "named-fixed-length-list.wit" => true, + "issue1514-6.wit" | "named-fixed-length-list.wit" | "map.wit" => true, _ => false, } || config.async_; } diff --git a/crates/test/src/csharp.rs b/crates/test/src/csharp.rs index 8e11ca7f7..a4d017e18 100644 --- a/crates/test/src/csharp.rs +++ b/crates/test/src/csharp.rs @@ -49,6 +49,7 @@ impl LanguageMethods for Csharp { | "import-export-resource.wit" | "issue-1433.wit" | "named-fixed-length-list.wit" + | "map.wit" ) } diff --git a/crates/test/src/go.rs b/crates/test/src/go.rs index 99d8487a8..4feb51ddd 100644 --- a/crates/test/src/go.rs +++ b/crates/test/src/go.rs @@ -30,6 +30,7 @@ impl LanguageMethods for Go { config.error_context || name == "async-trait-function.wit" || name == "named-fixed-length-list.wit" + || name == "map.wit" } fn default_bindgen_args_for_codegen(&self) -> &[&str] { diff --git a/crates/test/src/moonbit.rs b/crates/test/src/moonbit.rs index e22d9489d..41d93a56e 100644 --- a/crates/test/src/moonbit.rs +++ b/crates/test/src/moonbit.rs @@ -98,9 +98,8 @@ impl LanguageMethods for MoonBit { config: &crate::config::WitConfig, _args: &[String], ) -> bool { - // async-resource-func actually works, but most other async tests - // fail during codegen or verification - config.async_ && name != "async-resource-func.wit" + name == "map.wit" + || (config.async_ && name != "async-resource-func.wit") } fn verify(&self, runner: &Runner, verify: &crate::Verify) -> anyhow::Result<()> { From f5ba29d72c39163a9b4cc9b82adced171417c4f6 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 28 Mar 2026 03:04:55 -0400 Subject: [PATCH 07/11] Fix rustfmt and use starts_with for C map.wit codegen variants - Run cargo fmt to fix formatting in c/lib.rs, test/c.rs, test/moonbit.rs. - Use starts_with("map.wit") for the C backend's should_fail_verify to catch all codegen test variants (no-sig-flattening, autodrop, async). --- crates/c/src/lib.rs | 8 +------- crates/test/src/c.rs | 2 +- crates/test/src/moonbit.rs | 3 +-- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 871484442..96345c210 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -1872,13 +1872,7 @@ impl<'a> wit_bindgen_core::AnonymousTypeGenerator<'a> for InterfaceGenerator<'a> todo!("print_anonymous_type for fixed length list"); } - fn anonymous_type_map( - &mut self, - _id: TypeId, - _key: &Type, - _value: &Type, - _docs: &Docs, - ) { + fn anonymous_type_map(&mut self, _id: TypeId, _key: &Type, _value: &Type, _docs: &Docs) { todo!("anonymous map types are not yet supported in the C backend"); } } diff --git a/crates/test/src/c.rs b/crates/test/src/c.rs index 523984d01..7ca7e49b9 100644 --- a/crates/test/src/c.rs +++ b/crates/test/src/c.rs @@ -58,7 +58,7 @@ impl LanguageMethods for C { ) -> bool { config.error_context || name.starts_with("named-fixed-length-list.wit") - || name == "map.wit" + || name.starts_with("map.wit") } fn codegen_test_variants(&self) -> &[(&str, &[&str])] { diff --git a/crates/test/src/moonbit.rs b/crates/test/src/moonbit.rs index 41d93a56e..b4a96e844 100644 --- a/crates/test/src/moonbit.rs +++ b/crates/test/src/moonbit.rs @@ -98,8 +98,7 @@ impl LanguageMethods for MoonBit { config: &crate::config::WitConfig, _args: &[String], ) -> bool { - name == "map.wit" - || (config.async_ && name != "async-resource-func.wit") + name == "map.wit" || (config.async_ && name != "async-resource-func.wit") } fn verify(&self, runner: &Runner, verify: &crate::Verify) -> anyhow::Result<()> { From a814c1468a4216f3dbfa8070c37bad35a508bfe4 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 28 Mar 2026 11:51:44 -0400 Subject: [PATCH 08/11] Add comment back Signed-off-by: Yordis Prieto --- crates/test/src/moonbit.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/test/src/moonbit.rs b/crates/test/src/moonbit.rs index b4a96e844..d6263a0fd 100644 --- a/crates/test/src/moonbit.rs +++ b/crates/test/src/moonbit.rs @@ -98,6 +98,8 @@ impl LanguageMethods for MoonBit { config: &crate::config::WitConfig, _args: &[String], ) -> bool { + // async-resource-func actually works, but most other async tests + // fail during codegen or verification name == "map.wit" || (config.async_ && name != "async-resource-func.wit") } From 43a7fd619d627385598eb9227cf3dc898d07e7ad Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sun, 29 Mar 2026 16:53:03 -0400 Subject: [PATCH 09/11] Expand map test coverage with additional runtime and codegen tests Add runtime tests for empty maps, option values, maps inside records, inline anonymous map types, and a 100-entry stress test. Expand codegen tests with option values, nested maps, record values, bool keys, and char keys. --- tests/codegen/map.wit | 12 ++++ tests/runtime/map/runner.rs | 107 +++++++++++++++++++++++++++++------- tests/runtime/map/test.rs | 47 +++++++++++++++- tests/runtime/map/test.wit | 10 ++++ 4 files changed, 152 insertions(+), 24 deletions(-) diff --git a/tests/codegen/map.wit b/tests/codegen/map.wit index 3f74e635e..df8c8ae12 100644 --- a/tests/codegen/map.wit +++ b/tests/codegen/map.wit @@ -4,15 +4,27 @@ interface map-types { type names-by-id = map; type ids-by-name = map; type bytes-by-name = map>; + type optional-values = map>; + type nested-map = map>; record wrapped-map { values: names-by-id, } + record map-value-record { + x: u32, + y: string, + } + named-roundtrip: func(a: names-by-id) -> ids-by-name; inline-roundtrip: func(a: map, b: map>) -> tuple, map>>; nested-roundtrip: func(a: bytes-by-name) -> bytes-by-name; wrapped-roundtrip: func(a: wrapped-map) -> wrapped-map; + option-map-roundtrip: func(a: optional-values) -> optional-values; + nested-map-roundtrip: func(a: nested-map) -> nested-map; + record-value-roundtrip: func(a: map) -> map; + bool-key-roundtrip: func(a: map) -> map; + char-key-roundtrip: func(a: map) -> map; } world map-world { diff --git a/tests/runtime/map/runner.rs b/tests/runtime/map/runner.rs index a38ba1663..9b1cd764e 100644 --- a/tests/runtime/map/runner.rs +++ b/tests/runtime/map/runner.rs @@ -10,26 +10,91 @@ export!(Component); impl Guest for Component { fn run() { - let mut input = NamesById::new(); - input.insert(1, "one".to_string()); - input.insert(1, "uno".to_string()); - input.insert(2, "two".to_string()); - let ids_by_name = named_roundtrip(&input); - assert_eq!(ids_by_name.get("uno"), Some(&1)); - assert_eq!(ids_by_name.get("two"), Some(&2)); - assert_eq!(ids_by_name.get("one"), None); - - let mut bytes_input = BytesByName::new(); - bytes_input.insert("hello".to_string(), b"world".to_vec()); - bytes_input.insert("bin".to_string(), vec![0u8, 1, 2]); - let bytes_by_name = bytes_roundtrip(&bytes_input); - assert_eq!( - bytes_by_name.get("hello").map(Vec::as_slice), - Some(b"world".as_slice()) - ); - assert_eq!( - bytes_by_name.get("bin").map(Vec::as_slice), - Some([0u8, 1, 2].as_slice()) - ); + test_named_roundtrip(); + test_bytes_roundtrip(); + test_empty_roundtrip(); + test_option_roundtrip(); + test_record_roundtrip(); + test_inline_roundtrip(); + test_large_map(); + } +} + +fn test_named_roundtrip() { + let mut input = NamesById::new(); + input.insert(1, "one".to_string()); + input.insert(1, "uno".to_string()); + input.insert(2, "two".to_string()); + let ids_by_name = named_roundtrip(&input); + assert_eq!(ids_by_name.get("uno"), Some(&1)); + assert_eq!(ids_by_name.get("two"), Some(&2)); + assert_eq!(ids_by_name.get("one"), None); +} + +fn test_bytes_roundtrip() { + let mut bytes_input = BytesByName::new(); + bytes_input.insert("hello".to_string(), b"world".to_vec()); + bytes_input.insert("bin".to_string(), vec![0u8, 1, 2]); + let bytes_by_name = bytes_roundtrip(&bytes_input); + assert_eq!( + bytes_by_name.get("hello").map(Vec::as_slice), + Some(b"world".as_slice()) + ); + assert_eq!( + bytes_by_name.get("bin").map(Vec::as_slice), + Some([0u8, 1, 2].as_slice()) + ); +} + +fn test_empty_roundtrip() { + let empty = NamesById::new(); + let result = empty_roundtrip(&empty); + assert!(result.is_empty()); +} + +fn test_option_roundtrip() { + let mut input = wit_bindgen::rt::Map::new(); + input.insert("some".to_string(), Some(42)); + input.insert("none".to_string(), None); + let result = option_roundtrip(&input); + assert_eq!(result.len(), 2); + assert_eq!(result.get("some"), Some(&Some(42))); + assert_eq!(result.get("none"), Some(&None)); +} + +fn test_record_roundtrip() { + let mut values = NamesById::new(); + values.insert(10, "ten".to_string()); + values.insert(20, "twenty".to_string()); + let entry = LabeledEntry { + label: "test-label".to_string(), + values, + }; + let result = record_roundtrip(&entry); + assert_eq!(result.label, "test-label"); + assert_eq!(result.values.len(), 2); + assert_eq!(result.values.get(&10).map(String::as_str), Some("ten")); + assert_eq!(result.values.get(&20).map(String::as_str), Some("twenty")); +} + +fn test_inline_roundtrip() { + let mut input = wit_bindgen::rt::Map::new(); + input.insert(1, "one".to_string()); + input.insert(2, "two".to_string()); + let result = inline_roundtrip(&input); + assert_eq!(result.len(), 2); + assert_eq!(result.get("one"), Some(&1)); + assert_eq!(result.get("two"), Some(&2)); +} + +fn test_large_map() { + let mut input = NamesById::new(); + for i in 0..100 { + input.insert(i, format!("value-{i}")); + } + let result = large_roundtrip(&input); + assert_eq!(result.len(), 100); + for i in 0..100 { + assert_eq!(result.get(&i).map(String::as_str), Some(format!("value-{i}").as_str())); } } diff --git a/tests/runtime/map/test.rs b/tests/runtime/map/test.rs index 5890dafdf..001cc5334 100644 --- a/tests/runtime/map/test.rs +++ b/tests/runtime/map/test.rs @@ -1,6 +1,6 @@ include!(env!("BINDINGS")); -use crate::exports::test::maps::to_test::{BytesByName, IdsByName, NamesById}; +use crate::exports::test::maps::to_test::{BytesByName, IdsByName, LabeledEntry, NamesById}; struct Component; @@ -19,8 +19,49 @@ impl exports::test::maps::to_test::Guest for Component { } fn bytes_roundtrip(a: BytesByName) -> BytesByName { - assert_eq!(a.get("hello").map(Vec::as_slice), Some(b"world".as_slice())); - assert_eq!(a.get("bin").map(Vec::as_slice), Some([0u8, 1, 2].as_slice())); + assert_eq!( + a.get("hello").map(Vec::as_slice), + Some(b"world".as_slice()) + ); + assert_eq!( + a.get("bin").map(Vec::as_slice), + Some([0u8, 1, 2].as_slice()) + ); + a + } + + fn empty_roundtrip(a: NamesById) -> NamesById { + assert!(a.is_empty()); + a + } + + fn option_roundtrip( + a: wit_bindgen::rt::Map>, + ) -> wit_bindgen::rt::Map> { + assert_eq!(a.get("some"), Some(&Some(42))); + assert_eq!(a.get("none"), Some(&None)); + a + } + + fn record_roundtrip(a: LabeledEntry) -> LabeledEntry { + assert_eq!(a.label, "test-label"); + assert_eq!(a.values.len(), 2); + assert_eq!(a.values.get(&10).map(String::as_str), Some("ten")); + assert_eq!(a.values.get(&20).map(String::as_str), Some("twenty")); + a + } + + fn inline_roundtrip( + a: wit_bindgen::rt::Map, + ) -> wit_bindgen::rt::Map { + let mut result = wit_bindgen::rt::Map::new(); + for (k, v) in a { + result.insert(v, k); + } + result + } + + fn large_roundtrip(a: NamesById) -> NamesById { a } } diff --git a/tests/runtime/map/test.wit b/tests/runtime/map/test.wit index 364695e21..e9590174e 100644 --- a/tests/runtime/map/test.wit +++ b/tests/runtime/map/test.wit @@ -5,8 +5,18 @@ interface to-test { type ids-by-name = map; type bytes-by-name = map>; + record labeled-entry { + label: string, + values: names-by-id, + } + named-roundtrip: func(a: names-by-id) -> ids-by-name; bytes-roundtrip: func(a: bytes-by-name) -> bytes-by-name; + empty-roundtrip: func(a: names-by-id) -> names-by-id; + option-roundtrip: func(a: map>) -> map>; + record-roundtrip: func(a: labeled-entry) -> labeled-entry; + inline-roundtrip: func(a: map) -> map; + large-roundtrip: func(a: names-by-id) -> names-by-id; } world test { From ee6a8b0d3af3ec4e7eeb32a48aea0171c0f655ac Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sun, 29 Mar 2026 16:57:56 -0400 Subject: [PATCH 10/11] Add map tests for nested maps, multi-param, variant, and result types Exercise additional ABI code paths: nested map> with recursive lower/lift blocks, multiple map parameters with independent ptr/len pairs, map as a variant arm payload, map inside result, and tuple-with-map in codegen. --- tests/codegen/map.wit | 8 ++++ tests/runtime/map/runner.rs | 78 ++++++++++++++++++++++++++++++++++++- tests/runtime/map/test.rs | 34 +++++++++++++++- tests/runtime/map/test.wit | 9 +++++ 4 files changed, 127 insertions(+), 2 deletions(-) diff --git a/tests/codegen/map.wit b/tests/codegen/map.wit index df8c8ae12..8af94b652 100644 --- a/tests/codegen/map.wit +++ b/tests/codegen/map.wit @@ -16,6 +16,11 @@ interface map-types { y: string, } + variant map-or-string { + as-map(names-by-id), + as-string(string), + } + named-roundtrip: func(a: names-by-id) -> ids-by-name; inline-roundtrip: func(a: map, b: map>) -> tuple, map>>; nested-roundtrip: func(a: bytes-by-name) -> bytes-by-name; @@ -25,6 +30,9 @@ interface map-types { record-value-roundtrip: func(a: map) -> map; bool-key-roundtrip: func(a: map) -> map; char-key-roundtrip: func(a: map) -> map; + variant-roundtrip: func(a: map-or-string) -> map-or-string; + result-roundtrip: func(a: result) -> result; + tuple-with-map: func(a: tuple) -> tuple; } world map-world { diff --git a/tests/runtime/map/runner.rs b/tests/runtime/map/runner.rs index 9b1cd764e..e86e93cca 100644 --- a/tests/runtime/map/runner.rs +++ b/tests/runtime/map/runner.rs @@ -17,6 +17,10 @@ impl Guest for Component { test_record_roundtrip(); test_inline_roundtrip(); test_large_map(); + test_multi_param_roundtrip(); + test_nested_roundtrip(); + test_variant_roundtrip(); + test_result_roundtrip(); } } @@ -95,6 +99,78 @@ fn test_large_map() { let result = large_roundtrip(&input); assert_eq!(result.len(), 100); for i in 0..100 { - assert_eq!(result.get(&i).map(String::as_str), Some(format!("value-{i}").as_str())); + assert_eq!( + result.get(&i).map(String::as_str), + Some(format!("value-{i}").as_str()), + ); + } +} + +fn test_multi_param_roundtrip() { + let mut names = NamesById::new(); + names.insert(1, "one".to_string()); + names.insert(2, "two".to_string()); + let mut bytes = BytesByName::new(); + bytes.insert("key".to_string(), vec![42u8]); + let (ids, bytes_out) = multi_param_roundtrip(&names, &bytes); + assert_eq!(ids.len(), 2); + assert_eq!(ids.get("one"), Some(&1)); + assert_eq!(ids.get("two"), Some(&2)); + assert_eq!(bytes_out.len(), 1); + assert_eq!( + bytes_out.get("key").map(Vec::as_slice), + Some([42u8].as_slice()), + ); +} + +fn test_nested_roundtrip() { + let mut inner_a = wit_bindgen::rt::Map::new(); + inner_a.insert(1, "one".to_string()); + inner_a.insert(2, "two".to_string()); + let mut inner_b = wit_bindgen::rt::Map::new(); + inner_b.insert(10, "ten".to_string()); + let mut outer = wit_bindgen::rt::Map::new(); + outer.insert("group-a".to_string(), inner_a); + outer.insert("group-b".to_string(), inner_b); + let result = nested_roundtrip(&outer); + assert_eq!(result.len(), 2); + let ra = result.get("group-a").unwrap(); + assert_eq!(ra.get(&1).map(String::as_str), Some("one")); + assert_eq!(ra.get(&2).map(String::as_str), Some("two")); + let rb = result.get("group-b").unwrap(); + assert_eq!(rb.get(&10).map(String::as_str), Some("ten")); +} + +fn test_variant_roundtrip() { + let mut map = NamesById::new(); + map.insert(1, "one".to_string()); + let as_map = variant_roundtrip(&MapOrString::AsMap(map)); + match &as_map { + MapOrString::AsMap(m) => { + assert_eq!(m.get(&1).map(String::as_str), Some("one")); + } + MapOrString::AsString(_) => panic!("expected AsMap"), + } + + let as_str = variant_roundtrip(&MapOrString::AsString("hello".to_string())); + match &as_str { + MapOrString::AsString(s) => assert_eq!(s, "hello"), + MapOrString::AsMap(_) => panic!("expected AsString"), + } +} + +fn test_result_roundtrip() { + let mut map = NamesById::new(); + map.insert(5, "five".to_string()); + let ok_result = result_roundtrip(Ok(&map)); + match &ok_result { + Ok(m) => assert_eq!(m.get(&5).map(String::as_str), Some("five")), + Err(_) => panic!("expected Ok"), + } + + let err_result = result_roundtrip(Err("bad input")); + match &err_result { + Err(e) => assert_eq!(e, "bad input"), + Ok(_) => panic!("expected Err"), } } diff --git a/tests/runtime/map/test.rs b/tests/runtime/map/test.rs index 001cc5334..5bf6d1d5c 100644 --- a/tests/runtime/map/test.rs +++ b/tests/runtime/map/test.rs @@ -1,6 +1,8 @@ include!(env!("BINDINGS")); -use crate::exports::test::maps::to_test::{BytesByName, IdsByName, LabeledEntry, NamesById}; +use crate::exports::test::maps::to_test::{ + BytesByName, IdsByName, LabeledEntry, MapOrString, NamesById, +}; struct Component; @@ -64,4 +66,34 @@ impl exports::test::maps::to_test::Guest for Component { fn large_roundtrip(a: NamesById) -> NamesById { a } + + fn multi_param_roundtrip(a: NamesById, b: BytesByName) -> (IdsByName, BytesByName) { + assert_eq!(a.len(), 2); + assert_eq!(b.len(), 1); + let mut ids = IdsByName::new(); + for (id, name) in a { + ids.insert(name, id); + } + (ids, b) + } + + fn nested_roundtrip( + a: wit_bindgen::rt::Map>, + ) -> wit_bindgen::rt::Map> { + assert_eq!(a.len(), 2); + let inner = a.get("group-a").unwrap(); + assert_eq!(inner.get(&1).map(String::as_str), Some("one")); + assert_eq!(inner.get(&2).map(String::as_str), Some("two")); + let inner2 = a.get("group-b").unwrap(); + assert_eq!(inner2.get(&10).map(String::as_str), Some("ten")); + a + } + + fn variant_roundtrip(a: MapOrString) -> MapOrString { + a + } + + fn result_roundtrip(a: Result) -> Result { + a + } } diff --git a/tests/runtime/map/test.wit b/tests/runtime/map/test.wit index e9590174e..3e7df8f57 100644 --- a/tests/runtime/map/test.wit +++ b/tests/runtime/map/test.wit @@ -10,6 +10,11 @@ interface to-test { values: names-by-id, } + variant map-or-string { + as-map(names-by-id), + as-string(string), + } + named-roundtrip: func(a: names-by-id) -> ids-by-name; bytes-roundtrip: func(a: bytes-by-name) -> bytes-by-name; empty-roundtrip: func(a: names-by-id) -> names-by-id; @@ -17,6 +22,10 @@ interface to-test { record-roundtrip: func(a: labeled-entry) -> labeled-entry; inline-roundtrip: func(a: map) -> map; large-roundtrip: func(a: names-by-id) -> names-by-id; + multi-param-roundtrip: func(a: names-by-id, b: bytes-by-name) -> tuple; + nested-roundtrip: func(a: map>) -> map>; + variant-roundtrip: func(a: map-or-string) -> map-or-string; + result-roundtrip: func(a: result) -> result; } world test { From a9a8dd635ed124075f63d43ce2cecf8c96ee2666 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sun, 29 Mar 2026 17:40:26 -0400 Subject: [PATCH 11/11] Add tuple-with-map and single-entry map runtime tests Exercise map inside an anonymous tuple (filter_mode_preserve_top path) and the single-entry boundary case. --- tests/runtime/map/runner.rs | 22 ++++++++++++++++++++++ tests/runtime/map/test.rs | 12 ++++++++++++ tests/runtime/map/test.wit | 2 ++ 3 files changed, 36 insertions(+) diff --git a/tests/runtime/map/runner.rs b/tests/runtime/map/runner.rs index e86e93cca..a39014bab 100644 --- a/tests/runtime/map/runner.rs +++ b/tests/runtime/map/runner.rs @@ -21,6 +21,8 @@ impl Guest for Component { test_nested_roundtrip(); test_variant_roundtrip(); test_result_roundtrip(); + test_tuple_roundtrip(); + test_single_entry_roundtrip(); } } @@ -174,3 +176,23 @@ fn test_result_roundtrip() { Ok(_) => panic!("expected Err"), } } + +fn test_tuple_roundtrip() { + let mut map = NamesById::new(); + map.insert(7, "seven".to_string()); + let (result_map, result_num) = tuple_roundtrip((&map, 42)); + assert_eq!(result_map.len(), 1); + assert_eq!(result_map.get(&7).map(String::as_str), Some("seven")); + assert_eq!(result_num, 42); +} + +fn test_single_entry_roundtrip() { + let mut input = NamesById::new(); + input.insert(99, "ninety-nine".to_string()); + let result = single_entry_roundtrip(&input); + assert_eq!(result.len(), 1); + assert_eq!( + result.get(&99).map(String::as_str), + Some("ninety-nine"), + ); +} diff --git a/tests/runtime/map/test.rs b/tests/runtime/map/test.rs index 5bf6d1d5c..efee67c86 100644 --- a/tests/runtime/map/test.rs +++ b/tests/runtime/map/test.rs @@ -96,4 +96,16 @@ impl exports::test::maps::to_test::Guest for Component { fn result_roundtrip(a: Result) -> Result { a } + + fn tuple_roundtrip(a: (NamesById, u64)) -> (NamesById, u64) { + assert_eq!(a.0.len(), 1); + assert_eq!(a.0.get(&7).map(String::as_str), Some("seven")); + assert_eq!(a.1, 42); + a + } + + fn single_entry_roundtrip(a: NamesById) -> NamesById { + assert_eq!(a.len(), 1); + a + } } diff --git a/tests/runtime/map/test.wit b/tests/runtime/map/test.wit index 3e7df8f57..4822d3426 100644 --- a/tests/runtime/map/test.wit +++ b/tests/runtime/map/test.wit @@ -26,6 +26,8 @@ interface to-test { nested-roundtrip: func(a: map>) -> map>; variant-roundtrip: func(a: map-or-string) -> map-or-string; result-roundtrip: func(a: result) -> result; + tuple-roundtrip: func(a: tuple) -> tuple; + single-entry-roundtrip: func(a: names-by-id) -> names-by-id; } world test {