From b63ae05d813e245b1df14ff7a179f1528d519459 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 14 Apr 2026 15:34:47 -0400 Subject: [PATCH 01/16] 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 0348ff7f9..5a74570af 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( @@ -3544,12 +3553,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 4946a1fef..bf49ad3a8 100644 --- a/crates/test/src/cpp.rs +++ b/crates/test/src/cpp.rs @@ -50,7 +50,7 @@ impl LanguageMethods for Cpp { return false; } 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 ff03a839495dbaae689d08e62f79103f50d64906 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 14 Apr 2026 16:21:23 -0400 Subject: [PATCH 02/16] 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 5a74570af..7dfb37d7e 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3594,21 +3594,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 0b40d7557a437d8c715ca4fa78e35336c60ef0da Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 14 Apr 2026 16:35:47 -0400 Subject: [PATCH 03/16] 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 7dfb37d7e..e6c8ad126 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3564,6 +3564,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); @@ -3575,10 +3577,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 5d74a5692619c3153bd8e490c16a2cce9282db3a Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 14 Apr 2026 18:04:11 -0400 Subject: [PATCH 04/16] 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 e6c8ad126..9cc297441 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3565,7 +3565,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); @@ -3578,10 +3577,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 a544ff5e33d90cb1108106751eb3dceef7aacda2 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 14 Apr 2026 18:51:05 -0400 Subject: [PATCH 05/16] 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 c4e6904788146b58043dac877c6dd4d2e3b7220b Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 17 Apr 2026 12:13:05 -0400 Subject: [PATCH 06/16] 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 9cc297441..48fa4e6be 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!(), }, @@ -3563,11 +3580,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!( @@ -3575,16 +3593,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); @@ -3594,16 +3611,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) { @@ -3617,12 +3642,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 26218fcb894be7329a061bce8555d7381f2da979 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 17 Apr 2026 13:26:48 -0400 Subject: [PATCH 07/16] 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 48fa4e6be..287432cf2 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> { // 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 f8b0e855a5446058c9a2ec0d188a2adcdc306f76 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 17 Apr 2026 13:36:46 -0400 Subject: [PATCH 08/16] 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 | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 287432cf2..5aa843ca0 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3512,17 +3512,19 @@ 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) + ); + 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, "}}"); @@ -3673,14 +3675,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 ce318888a6a239a74823377fbfe5b659255ad9d9 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 17 Apr 2026 13:55:49 -0400 Subject: [PATCH 09/16] 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 5aa843ca0..3c074e12b 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3592,7 +3592,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) {{"); From 86a4a4f8050b10b2bf5d28a4c61a8ce695e32022 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 28 Apr 2026 00:42:39 -0400 Subject: [PATCH 10/16] fix(cpp): zero length when moving wit::map Leaves the moved-from map in a fully consistent state so any later inspection of size()/empty() reflects the empty invariant, not just the destructor's short-circuit on a null data_ pointer. Signed-off-by: Yordis Prieto --- crates/cpp/helper-types/wit.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/cpp/helper-types/wit.h b/crates/cpp/helper-types/wit.h index eae3b577b..26a615ef0 100644 --- a/crates/cpp/helper-types/wit.h +++ b/crates/cpp/helper-types/wit.h @@ -190,7 +190,10 @@ template class map { public: map(map const &) = delete; - map(map &&b) : data_(b.data_), length(b.length) { b.data_ = nullptr; } + map(map &&b) : data_(b.data_), length(b.length) { + b.data_ = nullptr; + b.length = 0; + } map &operator=(map const &) = delete; map &operator=(map &&b) { if (data_ && length > 0) { @@ -200,6 +203,7 @@ template class map { data_ = b.data_; length = b.length; b.data_ = nullptr; + b.length = 0; return *this; } map(entry_type *d, size_t l) : data_(d), length(l) {} From a448c412b3c5ed98a5c9ffb03dcaa1aa3002441a Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 28 Apr 2026 00:43:36 -0400 Subject: [PATCH 11/16] style(cpp): break long single-line methods in wit::map Signed-off-by: Yordis Prieto --- crates/cpp/helper-types/wit.h | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/cpp/helper-types/wit.h b/crates/cpp/helper-types/wit.h index 26a615ef0..a9096f38a 100644 --- a/crates/cpp/helper-types/wit.h +++ b/crates/cpp/helper-types/wit.h @@ -229,14 +229,24 @@ template class map { 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; } + 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) From 3fec7b11ccda23e9075748ba35ec078e883fe080 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 28 Apr 2026 00:55:30 -0400 Subject: [PATCH 12/16] refactor(cpp): drop wit::map operator[] Index-based subscript on a map is a footgun: integral keys would silently compile while doing positional access. Iteration via range-for and pointer access via data() are sufficient for codegen and don't carry the same expectation mismatch. Signed-off-by: Yordis Prieto --- crates/cpp/helper-types/wit.h | 2 -- crates/cpp/src/lib.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/cpp/helper-types/wit.h b/crates/cpp/helper-types/wit.h index a9096f38a..0bb4b8626 100644 --- a/crates/cpp/helper-types/wit.h +++ b/crates/cpp/helper-types/wit.h @@ -210,8 +210,6 @@ template class map { 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() { diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 3c074e12b..45665a114 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3597,7 +3597,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { ); 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_entry = {val}.data()[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); From 1a46573e4e7f7bdead5ac73d04e3e97029dec3cc Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 28 Apr 2026 01:01:14 -0400 Subject: [PATCH 13/16] refactor(cpp): drop std::span accessors from wit::map A span of pairs is a vector-shaped view that doesn't fit a map abstraction; codegen sites that need a flat pair span build it explicitly from data() and size() at the call site. Signed-off-by: Yordis Prieto --- crates/cpp/helper-types/wit.h | 8 +------- crates/cpp/src/lib.rs | 5 ++++- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/cpp/helper-types/wit.h b/crates/cpp/helper-types/wit.h index 0bb4b8626..d0b09d997 100644 --- a/crates/cpp/helper-types/wit.h +++ b/crates/cpp/helper-types/wit.h @@ -177,7 +177,7 @@ template class vector { /// 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()`. +/// build their own index from `data()`/`size()` or by ranging over entries. template class map { public: using entry_type = std::pair; @@ -235,12 +235,6 @@ template class map { 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_; } diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 45665a114..310ab3fc1 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3653,7 +3653,10 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) { - results.push(format!("{result}.get_const_view()")); + self.r#gen.r#gen.dependencies.needs_span = true; + results.push(format!( + "std::span const>({result}.data(), {result}.size())" + )); self.leak_on_insertion.replace(format!( "if ({len}>0) _deallocate.push_back((void*){result}.leak());\n" )); From f5c984c6caa5dc73f6b2f4d94520eb3452f86460 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 28 Apr 2026 02:49:41 -0400 Subject: [PATCH 14/16] feat(cpp): introduce wit::map_view for borrowed map arguments Borrowed map arguments were surfacing as std::span const>, which carries vector-shaped affordances (positional indexing, span conversions) on what is conceptually a map. wit::map_view is a borrow- only counterpart to wit::map that mimics map semantics rather than vector semantics. Signed-off-by: Yordis Prieto --- crates/cpp/helper-types/wit.h | 27 +++++++++++++++++++++++++++ crates/cpp/src/lib.rs | 12 +++++------- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/crates/cpp/helper-types/wit.h b/crates/cpp/helper-types/wit.h index d0b09d997..800165d6b 100644 --- a/crates/cpp/helper-types/wit.h +++ b/crates/cpp/helper-types/wit.h @@ -241,6 +241,33 @@ template class map { entry_type const *end() const { return data_ + length; } }; +/// @brief A non-owning, read-only view over a contiguous run of key-value +/// pairs in linear memory. +/// +/// Counterpart to `wit::map` for borrowed arguments: a borrow-only +/// handle that mimics map semantics rather than vector semantics — entries +/// are reachable via range-for or `data()`/`size()`, with no positional +/// `operator[]`. Construct from a `wit::map` or an explicit +/// `(data, size)` pair. +template class map_view { +public: + using entry_type = std::pair; + +private: + entry_type const *data_; + size_t length; + +public: + map_view() : data_(nullptr), length(0) {} + map_view(entry_type const *d, size_t l) : data_(d), length(l) {} + map_view(map const &m) : data_(m.data()), length(m.size()) {} + entry_type const *data() const { return data_; } + size_t size() const { return length; } + bool empty() const { return !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 310ab3fc1..5944e6dbd 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -1750,20 +1750,18 @@ impl CppInterfaceGenerator<'_> { }; let k = self.type_name(key, from_namespace, element_flavor); let v = self.type_name(value, from_namespace, element_flavor); + self.r#gen.dependencies.needs_wit = true; match flavor { Flavor::BorrowedArgument => { - self.r#gen.dependencies.needs_span = true; - format!("std::span const>") + format!("wit::map_view<{k}, {v}>") } 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>") + format!("wit::map_view<{k}, {v}>") } _ => { - self.r#gen.dependencies.needs_wit = true; format!("wit::map<{k}, {v}>") } } @@ -3653,9 +3651,9 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) { - self.r#gen.r#gen.dependencies.needs_span = true; + self.r#gen.r#gen.dependencies.needs_wit = true; results.push(format!( - "std::span const>({result}.data(), {result}.size())" + "wit::map_view<{key_type}, {value_type}>({result}.data(), {result}.size())" )); self.leak_on_insertion.replace(format!( "if ({len}>0) _deallocate.push_back((void*){result}.leak());\n" From 8e357c2cec5d28b2284566c1a88e41f80bafe408 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 28 Apr 2026 03:00:01 -0400 Subject: [PATCH 15/16] refactor(cpp): rename wit::map to wit::unordered_map Component-model map carries no ordering or hashing guarantee, and the type's API surface deliberately mirrors std::unordered_map (size, empty, begin/end) plus bindings-construction primitives, so the name should reflect the unordered semantics rather than std::map's ordered ones. Companion borrow type renamed to wit::unordered_map_view. Signed-off-by: Yordis Prieto --- crates/cpp/helper-types/wit.h | 54 +++++++++++++++++++---------------- crates/cpp/src/lib.rs | 10 +++---- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/crates/cpp/helper-types/wit.h b/crates/cpp/helper-types/wit.h index 800165d6b..ec607fc3f 100644 --- a/crates/cpp/helper-types/wit.h +++ b/crates/cpp/helper-types/wit.h @@ -175,10 +175,12 @@ template class vector { /// 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 `data()`/`size()` or by ranging over entries. -template class map { +/// to enable lift without per-entry tree allocation. The container has no +/// ordering or hashing guarantees and exposes only the subset of the +/// `std::unordered_map` API that's meaningful over a flat pair buffer plus +/// the bindings-construction primitives (`allocate`, `initialize`, `leak`, +/// `drop_raw`, `data`). +template class unordered_map { public: using entry_type = std::pair; @@ -189,13 +191,13 @@ template class map { 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) { + unordered_map(unordered_map const &) = delete; + unordered_map(unordered_map &&b) : data_(b.data_), length(b.length) { b.data_ = nullptr; b.length = 0; } - map &operator=(map const &) = delete; - map &operator=(map &&b) { + unordered_map &operator=(unordered_map const &) = delete; + unordered_map &operator=(unordered_map &&b) { if (data_ && length > 0) { for (unsigned i = 0; i < length; ++i) { data_[i].~entry_type(); } free(data_); @@ -206,23 +208,24 @@ template class map { b.length = 0; return *this; } - map(entry_type *d, size_t l) : data_(d), length(l) {} - map() : data_(empty_ptr()), length() {} + unordered_map(entry_type *d, size_t l) : data_(d), length(l) {} + unordered_map() : data_(empty_ptr()), length() {} entry_type const *data() const { return data_; } entry_type *data() { return data_; } size_t size() const { return length; } bool empty() const { return !length; } - ~map() { + ~unordered_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); + // WARNING: unordered_map contains uninitialized entries; caller must + // construct them via `initialize` before the map is observed or destroyed. + static unordered_map allocate(size_t len) { + if (!len) return unordered_map(empty_ptr(), 0); + return unordered_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)); @@ -244,12 +247,12 @@ template class map { /// @brief A non-owning, read-only view over a contiguous run of key-value /// pairs in linear memory. /// -/// Counterpart to `wit::map` for borrowed arguments: a borrow-only -/// handle that mimics map semantics rather than vector semantics — entries -/// are reachable via range-for or `data()`/`size()`, with no positional -/// `operator[]`. Construct from a `wit::map` or an explicit -/// `(data, size)` pair. -template class map_view { +/// Counterpart to `wit::unordered_map` for borrowed arguments: a +/// borrow-only handle that mimics map semantics rather than vector semantics +/// — entries are reachable via range-for or `data()`/`size()`, with no +/// positional `operator[]`. Construct from a `wit::unordered_map` or +/// an explicit `(data, size)` pair. +template class unordered_map_view { public: using entry_type = std::pair; @@ -258,9 +261,10 @@ template class map_view { size_t length; public: - map_view() : data_(nullptr), length(0) {} - map_view(entry_type const *d, size_t l) : data_(d), length(l) {} - map_view(map const &m) : data_(m.data()), length(m.size()) {} + unordered_map_view() : data_(nullptr), length(0) {} + unordered_map_view(entry_type const *d, size_t l) : data_(d), length(l) {} + unordered_map_view(unordered_map const &m) + : data_(m.data()), length(m.size()) {} entry_type const *data() const { return data_; } size_t size() const { return length; } bool empty() const { return !length; } diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 5944e6dbd..e83f5bb7f 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -1753,16 +1753,16 @@ impl CppInterfaceGenerator<'_> { self.r#gen.dependencies.needs_wit = true; match flavor { Flavor::BorrowedArgument => { - format!("wit::map_view<{k}, {v}>") + format!("wit::unordered_map_view<{k}, {v}>") } Flavor::Argument(var) if matches!(var, AbiVariant::GuestImport) || self.r#gen.opts.api_style == APIStyle::Symmetric => { - format!("wit::map_view<{k}, {v}>") + format!("wit::unordered_map_view<{k}, {v}>") } _ => { - format!("wit::map<{k}, {v}>") + format!("wit::unordered_map<{k}, {v}>") } } } @@ -3627,7 +3627,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { uwriteln!(self.src, "auto {len} = {};", operands[1]); uwriteln!( self.src, - "auto {result} = wit::map<{key_type}, {value_type}>::allocate({len});" + "auto {result} = wit::unordered_map<{key_type}, {value_type}>::allocate({len});" ); if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric && matches!(self.variant, AbiVariant::GuestExport) @@ -3653,7 +3653,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { { self.r#gen.r#gen.dependencies.needs_wit = true; results.push(format!( - "wit::map_view<{key_type}, {value_type}>({result}.data(), {result}.size())" + "wit::unordered_map_view<{key_type}, {value_type}>({result}.data(), {result}.size())" )); self.leak_on_insertion.replace(format!( "if ({len}>0) _deallocate.push_back((void*){result}.leak());\n" From f61665a14aecb5e23ac01517807f4847191ef52b Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 28 Apr 2026 04:06:44 -0400 Subject: [PATCH 16/16] fix(cpp): align map iteration variable with renamed _base Adjacent block bodies (string/list/option dealloc) now reference _base after main's rename, so MapLower / MapLift / GuestDeallocateMap must declare the per-entry pointer under the same name to compile. Adds the matching (void) _base; suppression so loops still compile when the inner body doesn't reference it. Signed-off-by: Yordis Prieto --- crates/cpp/src/lib.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index e83f5bb7f..09cf7ab40 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -3594,7 +3594,8 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { ptr_type = self.r#gen.r#gen.opts.ptr_type() ); uwriteln!(self.src, "for (size_t i = 0; i < {len}; ++i) {{"); - uwriteln!(self.src, "auto base = {ptr} + i * {size};"); + uwriteln!(self.src, "auto _base = {ptr} + i * {size};"); + uwriteln!(self.src, "(void) _base;"); uwriteln!(self.src, "auto&& iter_entry = {val}.data()[i];"); uwriteln!(self.src, "auto&& iter_map_key = iter_entry.first;"); uwriteln!(self.src, "auto&& iter_map_value = iter_entry.second;"); @@ -3636,7 +3637,8 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { 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};"); + uwriteln!(self.src, "auto _base = {base} + i * {size};"); + uwriteln!(self.src, "(void) _base;"); uwrite!(self.src, "{}", body.0); let body_key = &body.1[0]; let body_value = &body.1[1]; @@ -3672,8 +3674,8 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { 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); + 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]); if !body.trim().is_empty() { @@ -3681,7 +3683,8 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { 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, "uint8_t* _base = {ptr} + {i} * {size};"); + uwriteln!(self.src, "(void) _base;"); uwrite!(self.src, "{body}"); uwriteln!(self.src, "}}"); }