diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 2d53d23dd..96345c210 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); @@ -1867,6 +1871,10 @@ 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/abi.rs b/crates/core/src/abi.rs index 7d625f222..3b86429fe 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,8 @@ 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(_) => { self.lower_and_emit(ty, addr, &I32Store { offset }) @@ -2016,7 +2086,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { id, }); } - TypeDefKind::Map(..) => todo!(), }, } } @@ -2115,6 +2184,8 @@ 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(_) => { self.emit_and_lift(ty, addr, &I32Load { offset }) @@ -2216,7 +2287,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { id, }); } - TypeDefKind::Map(..) => todo!(), }, } } @@ -2339,6 +2409,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 +2487,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::Unknown => unreachable!(), TypeDefKind::FixedLengthList(..) => todo!(), - TypeDefKind::Map(..) => todo!(), }, } } @@ -2464,6 +2545,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 +2619,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..b85754160 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -166,6 +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); 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 +204,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 +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); 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 +244,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..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, @@ -3503,6 +3514,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 8d7ab4a1e..badb8c42e 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -1468,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 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/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..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 } @@ -2807,6 +2811,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/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index cd76e0b75..6a73cafc2 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 = format!("{}::Map", self.r#gen.r#gen.runtime_path()); + 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..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]; @@ -1786,6 +1787,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 +1938,26 @@ 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(" "); + } + } + 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>) { if lifetime.is_none() { return; @@ -2909,6 +2936,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 +3087,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/test/src/c.rs b/crates/test/src/c.rs index a34f2fc55..7ca7e49b9 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.starts_with("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..d6263a0fd 100644 --- a/crates/test/src/moonbit.rs +++ b/crates/test/src/moonbit.rs @@ -100,7 +100,7 @@ impl LanguageMethods for MoonBit { ) -> 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<()> { diff --git a/tests/codegen/map.wit b/tests/codegen/map.wit new file mode 100644 index 000000000..8af94b652 --- /dev/null +++ b/tests/codegen/map.wit @@ -0,0 +1,41 @@ +package foo:foo; + +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, + } + + 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; + 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; + 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 { + 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..a39014bab --- /dev/null +++ b/tests/runtime/map/runner.rs @@ -0,0 +1,198 @@ +//@ wasmtime-flags = '-Wcomponent-model-map' + +include!(env!("BINDINGS")); + +use test::maps::to_test::*; + +struct Component; + +export!(Component); + +impl Guest for Component { + fn run() { + test_named_roundtrip(); + test_bytes_roundtrip(); + test_empty_roundtrip(); + test_option_roundtrip(); + test_record_roundtrip(); + test_inline_roundtrip(); + test_large_map(); + test_multi_param_roundtrip(); + test_nested_roundtrip(); + test_variant_roundtrip(); + test_result_roundtrip(); + test_tuple_roundtrip(); + test_single_entry_roundtrip(); + } +} + +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()), + ); + } +} + +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"), + } +} + +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 new file mode 100644 index 000000000..efee67c86 --- /dev/null +++ b/tests/runtime/map/test.rs @@ -0,0 +1,111 @@ +include!(env!("BINDINGS")); + +use crate::exports::test::maps::to_test::{ + BytesByName, IdsByName, LabeledEntry, MapOrString, 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 + } + + 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 + } + + 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 + } + + 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 new file mode 100644 index 000000000..4822d3426 --- /dev/null +++ b/tests/runtime/map/test.wit @@ -0,0 +1,41 @@ +package test:maps; + +interface to-test { + type names-by-id = map; + type ids-by-name = map; + type bytes-by-name = map>; + + record labeled-entry { + label: string, + 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; + 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; + 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; + tuple-roundtrip: func(a: tuple) -> tuple; + single-entry-roundtrip: func(a: names-by-id) -> names-by-id; +} + +world test { + export to-test; +} + +world runner { + import to-test; + + export run: func(); +}