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 a98a4fc4d..156c2fa80 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 4da5eaefa..5ab62ade2 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(_) => { @@ -1017,6 +1020,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(), @@ -1093,6 +1097,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); @@ -1714,6 +1725,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(); +}