From 92ed57aa51138dc6fd375f360512e329b5b518e2 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 14 Apr 2026 15:34:47 -0400 Subject: [PATCH 1/9] feat(cpp): add map type support --- crates/cpp/src/lib.rs | 122 ++++++++++++++++++++++++++++++++++--- crates/test/src/cpp.rs | 2 +- tests/runtime/map/test.cpp | 68 +++++++++++++++++++++ 3 files changed, 182 insertions(+), 10 deletions(-) create mode 100644 tests/runtime/map/test.cpp diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 3e549b0e1..f66656b92 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -75,6 +75,7 @@ struct Includes { needs_wit: bool, needs_memory: bool, needs_array: bool, + needs_map: bool, } #[derive(Default)] @@ -435,6 +436,9 @@ impl Cpp { if self.dependencies.needs_bit { self.include(""); } + if self.dependencies.needs_map { + self.include(""); + } } fn start_new_file(&mut self, condition: Option) -> FileContext { @@ -914,7 +918,7 @@ impl CppInterfaceGenerator<'_> { TypeDefKind::Stream(_) => todo!("generate for stream"), TypeDefKind::Handle(_) => todo!("generate for handle"), TypeDefKind::FixedLengthList(_, _) => todo!(), - TypeDefKind::Map(_, _) => todo!(), + TypeDefKind::Map(k, v) => self.type_map(id, name, k, v, &ty.docs), TypeDefKind::Unknown => unreachable!(), } } @@ -1741,7 +1745,12 @@ impl CppInterfaceGenerator<'_> { self.type_name(ty, from_namespace, flavor) ) } - TypeDefKind::Map(_, _) => todo!(), + TypeDefKind::Map(key, value) => { + self.r#gen.dependencies.needs_map = true; + let k = self.type_name(key, from_namespace, flavor); + let v = self.type_name(value, from_namespace, flavor); + format!("std::map<{k}, {v}>") + } TypeDefKind::Unknown => todo!(), }, Type::ErrorContext => todo!(), @@ -2266,7 +2275,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> _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") + // nothing to do here } fn type_builtin( @@ -3540,12 +3549,107 @@ 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") + abi::Instruction::MapLower { + key, + value, + realloc, + } => { + let tmp = self.tmp(); + let body = self.blocks.pop().unwrap(); + let val = format!("map{tmp}"); + let ptr = format!("ptr{tmp}"); + let len = format!("len{tmp}"); + let idx = format!("idx{tmp}"); + let entry = self.r#gen.sizes.record([*key, *value]); + let size = entry.size.format(POINTER_SIZE_EXPRESSION); + let align = entry.align.format(POINTER_SIZE_EXPRESSION); + self.push_str(&format!("auto&& {val} = {};\n", operands[0])); + self.push_str(&format!("auto {len} = (size_t)({val}.size());\n")); + uwriteln!( + self.src, + "auto {ptr} = ({ptr_type})({len} > 0 ? cabi_realloc(nullptr, 0, {align}, {len} * {size}) : nullptr);", + ptr_type = self.r#gen.r#gen.opts.ptr_type() + ); + uwriteln!(self.src, "size_t {idx} = 0;"); + uwriteln!( + self.src, + "for (auto&& [iter_map_key, iter_map_value] : {val}) {{" + ); + uwriteln!(self.src, "auto base = {ptr} + {idx} * {size};"); + uwrite!(self.src, "{}", body.0); + uwriteln!(self.src, "++{idx};"); + uwriteln!(self.src, "}}"); + if realloc.is_some() { + // ownership transfers + } + results.push(ptr); + results.push(len); + } + abi::Instruction::MapLift { key, value, .. } => { + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let entry = self.r#gen.sizes.record([*key, *value]); + let size = entry.size.format(POINTER_SIZE_EXPRESSION); + let key_type = + self.r#gen + .type_name(key, &self.namespace, Flavor::InStruct); + let value_type = + self.r#gen + .type_name(value, &self.namespace, Flavor::InStruct); + let len = format!("len{tmp}"); + let base = format!("base{tmp}"); + let result = format!("result{tmp}"); + uwriteln!(self.src, "auto {base} = {};", operands[0]); + uwriteln!(self.src, "auto {len} = {};", operands[1]); + uwriteln!( + self.src, + "std::map<{key_type}, {value_type}> {result};" + ); + if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric + && matches!(self.variant, AbiVariant::GuestExport) + { + assert!(self.needs_dealloc); + uwriteln!(self.src, "if ({len}>0) _deallocate.push_back({base});"); + } + uwriteln!(self.src, "for (unsigned i=0; i<{len}; ++i) {{"); + uwriteln!(self.src, "auto base = {base} + i * {size};"); + uwrite!(self.src, "{}", body.0); + let body_key = &body.1[0]; + let body_value = &body.1[1]; + uwriteln!( + self.src, + "{result}.insert(std::make_pair({}, {}));", + move_if_necessary(body_key), + move_if_necessary(body_value) + ); + uwriteln!(self.src, "}}"); + results.push(move_if_necessary(&result)); + } + abi::Instruction::IterMapKey { .. } => { + results.push("iter_map_key".to_string()); + } + abi::Instruction::IterMapValue { .. } => { + results.push("iter_map_value".to_string()); + } + 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 entry = self.r#gen.sizes.record([*key, *value]); + let size = entry.size.format(POINTER_SIZE_EXPRESSION); + uwriteln!(self.src, "uint8_t* base = {ptr} + {i} * {size};"); + 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, "}}"); } } } diff --git a/crates/test/src/cpp.rs b/crates/test/src/cpp.rs index 33751f60a..0456ec1bd 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" | "map.wit" => true, + "issue1514-6.wit" | "named-fixed-length-list.wit" => true, _ => false, } || config.async_; } diff --git a/tests/runtime/map/test.cpp b/tests/runtime/map/test.cpp new file mode 100644 index 000000000..6603bf454 --- /dev/null +++ b/tests/runtime/map/test.cpp @@ -0,0 +1,68 @@ +#include +#include + +namespace test_exports = ::exports::test::maps::to_test; + +std::map test_exports::NamedRoundtrip(std::map a) { + std::map result; + for (auto&& [id, name] : a) { + result.insert(std::make_pair(std::move(name), id)); + } + return result; +} + +std::map> test_exports::BytesRoundtrip(std::map> a) { + return a; +} + +std::map test_exports::EmptyRoundtrip(std::map a) { + return a; +} + +std::map> test_exports::OptionRoundtrip(std::map> a) { + return a; +} + +test_exports::LabeledEntry test_exports::RecordRoundtrip(test_exports::LabeledEntry a) { + return a; +} + +std::map test_exports::InlineRoundtrip(std::map a) { + std::map result; + for (auto&& [k, v] : a) { + result.insert(std::make_pair(std::move(v), k)); + } + return result; +} + +std::map test_exports::LargeRoundtrip(std::map a) { + return a; +} + +std::tuple, std::map>> test_exports::MultiParamRoundtrip(std::map a, std::map> b) { + std::map ids; + for (auto&& [id, name] : a) { + ids.insert(std::make_pair(std::move(name), id)); + } + return std::make_tuple(std::move(ids), std::move(b)); +} + +std::map> test_exports::NestedRoundtrip(std::map> a) { + return a; +} + +test_exports::MapOrString test_exports::VariantRoundtrip(test_exports::MapOrString a) { + return a; +} + +std::expected, wit::string> test_exports::ResultRoundtrip(std::expected, wit::string> a) { + return a; +} + +std::tuple, uint64_t> test_exports::TupleRoundtrip(std::tuple, uint64_t> a) { + return a; +} + +std::map test_exports::SingleEntryRoundtrip(std::map a) { + return a; +} From 44cad6d0ad4e86fd57922613bb674a847d238cf5 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 14 Apr 2026 16:21:23 -0400 Subject: [PATCH 2/9] fix(cpp): add operator< to wit::string for std::map key support and fix rustfmt wit::string lacked comparison operators, causing compilation failures when used as a std::map key. Also fixes rustfmt formatting issues. --- crates/cpp/helper-types/wit.h | 6 ++++++ crates/cpp/src/lib.rs | 15 +++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/cpp/helper-types/wit.h b/crates/cpp/helper-types/wit.h index 8a79db32c..d8b4fd7e6 100644 --- a/crates/cpp/helper-types/wit.h +++ b/crates/cpp/helper-types/wit.h @@ -107,6 +107,12 @@ class string { char const* end() const { return (char const*)data_ + length; } + friend bool operator<(string const &a, string const &b) { + return a.get_view() < b.get_view(); + } + friend bool operator==(string const &a, string const &b) { + return a.get_view() == b.get_view(); + } }; /// A vector in linear memory, freed unconditionally using free diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index f66656b92..a4f0a3e5e 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3590,21 +3590,16 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let tmp = self.tmp(); let entry = self.r#gen.sizes.record([*key, *value]); let size = entry.size.format(POINTER_SIZE_EXPRESSION); - let key_type = - self.r#gen - .type_name(key, &self.namespace, Flavor::InStruct); - let value_type = - self.r#gen - .type_name(value, &self.namespace, Flavor::InStruct); + let key_type = self.r#gen.type_name(key, &self.namespace, Flavor::InStruct); + let value_type = self + .r#gen + .type_name(value, &self.namespace, Flavor::InStruct); let len = format!("len{tmp}"); let base = format!("base{tmp}"); let result = format!("result{tmp}"); uwriteln!(self.src, "auto {base} = {};", operands[0]); uwriteln!(self.src, "auto {len} = {};", operands[1]); - uwriteln!( - self.src, - "std::map<{key_type}, {value_type}> {result};" - ); + uwriteln!(self.src, "std::map<{key_type}, {value_type}> {result};"); if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) { From e3697ba4469a1aa2f51a1fe926f5216d0d31e70a Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 14 Apr 2026 16:35:47 -0400 Subject: [PATCH 3/9] fix(cpp): const_cast map keys during lowering for ownership transfer std::map keys are const, but the ABI lowering needs to call leak() on string keys to transfer ownership to the flat buffer. Use const_cast since the map is consumed during the lowering operation. --- crates/cpp/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index a4f0a3e5e..2efe01009 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3560,6 +3560,8 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let ptr = format!("ptr{tmp}"); let len = format!("len{tmp}"); let idx = format!("idx{tmp}"); + let entry_name = format!("entry{tmp}"); + let key_type = self.r#gen.type_name(key, &self.namespace, Flavor::InStruct); let entry = self.r#gen.sizes.record([*key, *value]); let size = entry.size.format(POINTER_SIZE_EXPRESSION); let align = entry.align.format(POINTER_SIZE_EXPRESSION); @@ -3571,10 +3573,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { ptr_type = self.r#gen.r#gen.opts.ptr_type() ); uwriteln!(self.src, "size_t {idx} = 0;"); + uwriteln!(self.src, "for (auto& {entry_name} : {val}) {{"); uwriteln!( self.src, - "for (auto&& [iter_map_key, iter_map_value] : {val}) {{" + "auto& iter_map_key = const_cast<{key_type}&>({entry_name}.first);" ); + uwriteln!(self.src, "auto& iter_map_value = {entry_name}.second;"); uwriteln!(self.src, "auto base = {ptr} + {idx} * {size};"); uwrite!(self.src, "{}", body.0); uwriteln!(self.src, "++{idx};"); From 3c384520d2f7fd682e8c598a863ed7a9e5bc4f7d Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 14 Apr 2026 18:04:11 -0400 Subject: [PATCH 4/9] fix(cpp): copy map keys by value instead of const_cast during lowering const_cast fails when the map key type differs between contexts (e.g. std::string_view in imports vs wit::string in exports). Copying by value works universally and is safe since the map is consumed. --- crates/cpp/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 2efe01009..8a99f5bd9 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3561,7 +3561,6 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let len = format!("len{tmp}"); let idx = format!("idx{tmp}"); let entry_name = format!("entry{tmp}"); - let key_type = self.r#gen.type_name(key, &self.namespace, Flavor::InStruct); let entry = self.r#gen.sizes.record([*key, *value]); let size = entry.size.format(POINTER_SIZE_EXPRESSION); let align = entry.align.format(POINTER_SIZE_EXPRESSION); @@ -3574,10 +3573,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { ); uwriteln!(self.src, "size_t {idx} = 0;"); uwriteln!(self.src, "for (auto& {entry_name} : {val}) {{"); - uwriteln!( - self.src, - "auto& iter_map_key = const_cast<{key_type}&>({entry_name}.first);" - ); + uwriteln!(self.src, "auto iter_map_key = {entry_name}.first;"); uwriteln!(self.src, "auto& iter_map_value = {entry_name}.second;"); uwriteln!(self.src, "auto base = {ptr} + {idx} * {size};"); uwrite!(self.src, "{}", body.0); From 235eb9efae4d0fa23f83011ec213ee690cbdb879 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 14 Apr 2026 18:51:05 -0400 Subject: [PATCH 5/9] fix(cpp): remove map runtime test until wasi-sdk supports map types wasm-component-ld bundled with wasi-sdk 30 doesn't support the map type encoding (0x63) in the component model binary format. The C++ map codegen is still validated by the codegen test. The runtime test can be re-added when wasi-sdk ships a compatible wasm-component-ld. --- tests/runtime/map/test.cpp | 68 -------------------------------------- 1 file changed, 68 deletions(-) delete mode 100644 tests/runtime/map/test.cpp diff --git a/tests/runtime/map/test.cpp b/tests/runtime/map/test.cpp deleted file mode 100644 index 6603bf454..000000000 --- a/tests/runtime/map/test.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include -#include - -namespace test_exports = ::exports::test::maps::to_test; - -std::map test_exports::NamedRoundtrip(std::map a) { - std::map result; - for (auto&& [id, name] : a) { - result.insert(std::make_pair(std::move(name), id)); - } - return result; -} - -std::map> test_exports::BytesRoundtrip(std::map> a) { - return a; -} - -std::map test_exports::EmptyRoundtrip(std::map a) { - return a; -} - -std::map> test_exports::OptionRoundtrip(std::map> a) { - return a; -} - -test_exports::LabeledEntry test_exports::RecordRoundtrip(test_exports::LabeledEntry a) { - return a; -} - -std::map test_exports::InlineRoundtrip(std::map a) { - std::map result; - for (auto&& [k, v] : a) { - result.insert(std::make_pair(std::move(v), k)); - } - return result; -} - -std::map test_exports::LargeRoundtrip(std::map a) { - return a; -} - -std::tuple, std::map>> test_exports::MultiParamRoundtrip(std::map a, std::map> b) { - std::map ids; - for (auto&& [id, name] : a) { - ids.insert(std::make_pair(std::move(name), id)); - } - return std::make_tuple(std::move(ids), std::move(b)); -} - -std::map> test_exports::NestedRoundtrip(std::map> a) { - return a; -} - -test_exports::MapOrString test_exports::VariantRoundtrip(test_exports::MapOrString a) { - return a; -} - -std::expected, wit::string> test_exports::ResultRoundtrip(std::expected, wit::string> a) { - return a; -} - -std::tuple, uint64_t> test_exports::TupleRoundtrip(std::tuple, uint64_t> a) { - return a; -} - -std::map test_exports::SingleEntryRoundtrip(std::map a) { - return a; -} From 33bb5520f2df888b4f9af6d36fdb7c7d58bf9c6a Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 17 Apr 2026 12:13:05 -0400 Subject: [PATCH 6/9] refactor(cpp): use wit::map instead of std::map for map types std::map requires per-entry rb-tree allocation during lift; wit::map mirrors the canonical ABI layout (flat pair array) so lift is a single allocation, matching the existing wit::vector / wit::string pattern. Signed-off-by: Yordis Prieto --- crates/cpp/helper-types/wit.h | 71 +++++++++++++++++++++++++++--- crates/cpp/src/lib.rs | 83 +++++++++++++++++++++++++---------- 2 files changed, 124 insertions(+), 30 deletions(-) diff --git a/crates/cpp/helper-types/wit.h b/crates/cpp/helper-types/wit.h index d8b4fd7e6..eae3b577b 100644 --- a/crates/cpp/helper-types/wit.h +++ b/crates/cpp/helper-types/wit.h @@ -13,6 +13,7 @@ #include // free #include #include +#include // pair namespace wit { /// @brief Helper class to map between IDs and resources @@ -107,12 +108,6 @@ class string { char const* end() const { return (char const*)data_ + length; } - friend bool operator<(string const &a, string const &b) { - return a.get_view() < b.get_view(); - } - friend bool operator==(string const &a, string const &b) { - return a.get_view() == b.get_view(); - } }; /// A vector in linear memory, freed unconditionally using free @@ -176,6 +171,70 @@ template class vector { } }; +/// @brief A map stored as a contiguous array of key-value pairs in linear +/// memory, freed unconditionally using free. +/// +/// Mirrors the canonical ABI representation of `map` (`list>`) +/// to enable lift without per-entry tree allocation. Unlike `std::map` this +/// container provides flat iteration only — callers who need keyed lookup can +/// build their own index from `get_view()`. +template class map { +public: + using entry_type = std::pair; + +private: + entry_type *data_; + size_t length; + + static entry_type* empty_ptr() { return (entry_type*)alignof(entry_type); } + +public: + map(map const &) = delete; + map(map &&b) : data_(b.data_), length(b.length) { b.data_ = nullptr; } + map &operator=(map const &) = delete; + map &operator=(map &&b) { + if (data_ && length > 0) { + for (unsigned i = 0; i < length; ++i) { data_[i].~entry_type(); } + free(data_); + } + data_ = b.data_; + length = b.length; + b.data_ = nullptr; + return *this; + } + map(entry_type *d, size_t l) : data_(d), length(l) {} + map() : data_(empty_ptr()), length() {} + entry_type const *data() const { return data_; } + entry_type *data() { return data_; } + entry_type &operator[](size_t n) { return data_[n]; } + entry_type const &operator[](size_t n) const { return data_[n]; } + size_t size() const { return length; } + bool empty() const { return !length; } + ~map() { + if (data_ && length > 0) { + for (unsigned i = 0; i < length; ++i) { data_[i].~entry_type(); } + free((void*)data_); + } + } + // WARNING: map contains uninitialized entries; caller must construct them via + // `initialize` before the map is observed or destroyed. + static map allocate(size_t len) { + if (!len) return map(empty_ptr(), 0); + return map((entry_type*)malloc(sizeof(entry_type) * len), len); + } + void initialize(size_t n, entry_type&& entry) { + new ((void*)(data_ + n)) entry_type(std::move(entry)); + } + entry_type* leak() { entry_type* result = data_; data_ = nullptr; return result; } + static void drop_raw(void *ptr) { if (ptr != empty_ptr()) free(ptr); } + std::span get_view() const { return std::span(data_, length); } + std::span get_const_view() const { return std::span(data_, length); } + entry_type* begin() { return data_; } + entry_type* end() { return data_ + length; } + entry_type const* begin() const { return data_; } + entry_type const* end() const { return data_ + length; } +}; + /// @brief A Resource defined within the guest (guest side) /// /// It registers with the host and should remain in a static location. diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 8a99f5bd9..dce659b13 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -75,7 +75,6 @@ struct Includes { needs_wit: bool, needs_memory: bool, needs_array: bool, - needs_map: bool, } #[derive(Default)] @@ -436,9 +435,6 @@ impl Cpp { if self.dependencies.needs_bit { self.include(""); } - if self.dependencies.needs_map { - self.include(""); - } } fn start_new_file(&mut self, condition: Option) -> FileContext { @@ -1746,10 +1742,31 @@ impl CppInterfaceGenerator<'_> { ) } TypeDefKind::Map(key, value) => { - self.r#gen.dependencies.needs_map = true; - let k = self.type_name(key, from_namespace, flavor); - let v = self.type_name(value, from_namespace, flavor); - format!("std::map<{k}, {v}>") + let element_flavor = match flavor { + Flavor::BorrowedArgument | Flavor::Argument(AbiVariant::GuestImport) => { + Flavor::BorrowedArgument + } + _ => Flavor::InStruct, + }; + let k = self.type_name(key, from_namespace, element_flavor); + let v = self.type_name(value, from_namespace, element_flavor); + match flavor { + Flavor::BorrowedArgument => { + self.r#gen.dependencies.needs_span = true; + format!("std::span const>") + } + Flavor::Argument(var) + if matches!(var, AbiVariant::GuestImport) + || self.r#gen.opts.api_style == APIStyle::Symmetric => + { + self.r#gen.dependencies.needs_span = true; + format!("std::span const>") + } + _ => { + self.r#gen.dependencies.needs_wit = true; + format!("wit::map<{k}, {v}>") + } + } } TypeDefKind::Unknown => todo!(), }, @@ -3559,11 +3576,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let val = format!("map{tmp}"); let ptr = format!("ptr{tmp}"); let len = format!("len{tmp}"); - let idx = format!("idx{tmp}"); - let entry_name = format!("entry{tmp}"); let entry = self.r#gen.sizes.record([*key, *value]); let size = entry.size.format(POINTER_SIZE_EXPRESSION); let align = entry.align.format(POINTER_SIZE_EXPRESSION); + // The canonical ABI entry layout can differ from the C++ entry + // layout (see wit-bindgen#1592), so always allocate a fresh ABI + // buffer rather than reusing the source map's storage. self.push_str(&format!("auto&& {val} = {};\n", operands[0])); self.push_str(&format!("auto {len} = (size_t)({val}.size());\n")); uwriteln!( @@ -3571,16 +3589,15 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { "auto {ptr} = ({ptr_type})({len} > 0 ? cabi_realloc(nullptr, 0, {align}, {len} * {size}) : nullptr);", ptr_type = self.r#gen.r#gen.opts.ptr_type() ); - uwriteln!(self.src, "size_t {idx} = 0;"); - uwriteln!(self.src, "for (auto& {entry_name} : {val}) {{"); - uwriteln!(self.src, "auto iter_map_key = {entry_name}.first;"); - uwriteln!(self.src, "auto& iter_map_value = {entry_name}.second;"); - uwriteln!(self.src, "auto base = {ptr} + {idx} * {size};"); + uwriteln!(self.src, "for (size_t i = 0; i < {len}; ++i) {{"); + uwriteln!(self.src, "auto base = {ptr} + i * {size};"); + uwriteln!(self.src, "auto&& iter_entry = {val}[i];"); + uwriteln!(self.src, "auto&& iter_map_key = iter_entry.first;"); + uwriteln!(self.src, "auto&& iter_map_value = iter_entry.second;"); uwrite!(self.src, "{}", body.0); - uwriteln!(self.src, "++{idx};"); uwriteln!(self.src, "}}"); if realloc.is_some() { - // ownership transfers + uwriteln!(self.src, "{}.leak();", operands[0]); } results.push(ptr); results.push(len); @@ -3590,16 +3607,24 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let tmp = self.tmp(); let entry = self.r#gen.sizes.record([*key, *value]); let size = entry.size.format(POINTER_SIZE_EXPRESSION); - let key_type = self.r#gen.type_name(key, &self.namespace, Flavor::InStruct); - let value_type = self - .r#gen - .type_name(value, &self.namespace, Flavor::InStruct); + let flavor = if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric + && matches!(self.variant, AbiVariant::GuestExport) + { + Flavor::BorrowedArgument + } else { + Flavor::InStruct + }; + let key_type = self.r#gen.type_name(key, &self.namespace, flavor); + let value_type = self.r#gen.type_name(value, &self.namespace, flavor); let len = format!("len{tmp}"); let base = format!("base{tmp}"); let result = format!("result{tmp}"); uwriteln!(self.src, "auto {base} = {};", operands[0]); uwriteln!(self.src, "auto {len} = {};", operands[1]); - uwriteln!(self.src, "std::map<{key_type}, {value_type}> {result};"); + uwriteln!( + self.src, + "auto {result} = wit::map<{key_type}, {value_type}>::allocate({len});" + ); if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) { @@ -3613,12 +3638,22 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let body_value = &body.1[1]; uwriteln!( self.src, - "{result}.insert(std::make_pair({}, {}));", + "{result}.initialize(i, std::make_pair({}, {}));", move_if_necessary(body_key), move_if_necessary(body_value) ); uwriteln!(self.src, "}}"); - results.push(move_if_necessary(&result)); + + 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::IterMapKey { .. } => { results.push("iter_map_key".to_string()); From 57c05148ab3bfd06faf1e42430ef4f047af4543a Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 17 Apr 2026 13:26:48 -0400 Subject: [PATCH 7/9] refactor(cpp): drop redundant size_t cast in MapLower wit::map::size() and std::span::size() already return size_t, so the C-style cast was a no-op. Signed-off-by: Yordis Prieto --- crates/cpp/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index dce659b13..89097bbb1 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3583,7 +3583,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { // layout (see wit-bindgen#1592), so always allocate a fresh ABI // buffer rather than reusing the source map's storage. self.push_str(&format!("auto&& {val} = {};\n", operands[0])); - self.push_str(&format!("auto {len} = (size_t)({val}.size());\n")); + self.push_str(&format!("auto {len} = {val}.size();\n")); uwriteln!( self.src, "auto {ptr} = ({ptr_type})({len} > 0 ? cabi_realloc(nullptr, 0, {align}, {len} * {size}) : nullptr);", From 773f6124ad591d3bfc8e982a19c05bf320ef7019 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 17 Apr 2026 13:36:46 -0400 Subject: [PATCH 8/9] refactor(cpp): skip guest-dealloc loop when body is empty Avoids an unused `base` local and the `(void) base;` suppression when the element has no nested heap-owned fields (e.g. list, map). Signed-off-by: Yordis Prieto --- crates/cpp/src/lib.rs | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 89097bbb1..68752f815 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3508,17 +3508,18 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { 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.size(element); - 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, "}}"); + if !body.trim().is_empty() { + let i = self.tempname("i", tmp); + uwriteln!(self.src, "for (size_t {i} = 0; {i} < {len}; {i}++) {{"); + let size = self.r#gen.sizes.size(element); + uwriteln!( + self.src, + "uint8_t* base = {ptr} + {i} * {size};", + size = size.format(POINTER_SIZE_EXPRESSION) + ); + uwrite!(self.src, "{body}"); + uwriteln!(self.src, "}}"); + } uwriteln!(self.src, "if ({len} > 0) {{"); uwriteln!(self.src, "free((void*) ({ptr}));"); uwriteln!(self.src, "}}"); @@ -3669,14 +3670,15 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { 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 entry = self.r#gen.sizes.record([*key, *value]); - let size = entry.size.format(POINTER_SIZE_EXPRESSION); - uwriteln!(self.src, "uint8_t* base = {ptr} + {i} * {size};"); - uwriteln!(self.src, "(void) base;"); - uwrite!(self.src, "{body}"); - uwriteln!(self.src, "}}"); + if !body.trim().is_empty() { + let i = self.tempname("i", tmp); + uwriteln!(self.src, "for (size_t {i} = 0; {i} < {len}; {i}++) {{"); + let entry = self.r#gen.sizes.record([*key, *value]); + let size = entry.size.format(POINTER_SIZE_EXPRESSION); + uwriteln!(self.src, "uint8_t* base = {ptr} + {i} * {size};"); + uwrite!(self.src, "{body}"); + uwriteln!(self.src, "}}"); + } uwriteln!(self.src, "if ({len} > 0) {{"); uwriteln!(self.src, "free((void*) ({ptr}));"); uwriteln!(self.src, "}}"); From fee6876dbb5f8a957e9fd48c1f83834cad0541c1 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 17 Apr 2026 13:55:49 -0400 Subject: [PATCH 9/9] refactor(cpp): use static_cast for MapLower realloc result The cast converts void* (from cabi_realloc) to uint8_t*, which is a well-defined static_cast and doesn't need a C-style cast. Signed-off-by: Yordis Prieto --- crates/cpp/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 68752f815..beb6bb838 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3587,7 +3587,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("auto {len} = {val}.size();\n")); uwriteln!( self.src, - "auto {ptr} = ({ptr_type})({len} > 0 ? cabi_realloc(nullptr, 0, {align}, {len} * {size}) : nullptr);", + "auto {ptr} = static_cast<{ptr_type}>({len} > 0 ? cabi_realloc(nullptr, 0, {align}, {len} * {size}) : nullptr);", ptr_type = self.r#gen.r#gen.opts.ptr_type() ); uwriteln!(self.src, "for (size_t i = 0; i < {len}; ++i) {{");