diff --git a/crates/cpp/helper-types/wit.h b/crates/cpp/helper-types/wit.h index 8a79db32c..ec607fc3f 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 @@ -170,6 +171,107 @@ 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. 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; + +private: + entry_type *data_; + size_t length; + + static entry_type* empty_ptr() { return (entry_type*)alignof(entry_type); } + +public: + unordered_map(unordered_map const &) = delete; + unordered_map(unordered_map &&b) : data_(b.data_), length(b.length) { + b.data_ = nullptr; + b.length = 0; + } + 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_); + } + data_ = b.data_; + length = b.length; + b.data_ = nullptr; + b.length = 0; + return *this; + } + 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; } + ~unordered_map() { + if (data_ && length > 0) { + for (unsigned i = 0; i < length; ++i) { data_[i].~entry_type(); } + free((void*)data_); + } + } + // 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)); + } + entry_type *leak() { + entry_type *result = data_; + data_ = nullptr; + return result; + } + static void drop_raw(void *ptr) { + if (ptr != empty_ptr()) free(ptr); + } + 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 non-owning, read-only view over a contiguous run of key-value +/// pairs in linear memory. +/// +/// 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; + +private: + entry_type const *data_; + size_t length; + +public: + 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; } + 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 0348ff7f9..09cf7ab40 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -914,7 +914,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 +1741,31 @@ impl CppInterfaceGenerator<'_> { self.type_name(ty, from_namespace, flavor) ) } - TypeDefKind::Map(_, _) => todo!(), + TypeDefKind::Map(key, value) => { + 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); + self.r#gen.dependencies.needs_wit = true; + match flavor { + Flavor::BorrowedArgument => { + 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::unordered_map_view<{k}, {v}>") + } + _ => { + format!("wit::unordered_map<{k}, {v}>") + } + } + } TypeDefKind::Unknown => todo!(), }, Type::ErrorContext => todo!(), @@ -2266,7 +2290,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( @@ -3486,17 +3510,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, "}}"); @@ -3544,12 +3570,127 @@ 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 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} = {val}.size();\n")); + uwriteln!( + self.src, + "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) {{"); + 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;"); + uwrite!(self.src, "{}", body.0); + uwriteln!(self.src, "}}"); + if realloc.is_some() { + uwriteln!(self.src, "{}.leak();", operands[0]); + } + 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 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, + "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) + { + 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};"); + uwriteln!(self.src, "(void) _base;"); + uwrite!(self.src, "{}", body.0); + let body_key = &body.1[0]; + let body_value = &body.1[1]; + uwriteln!( + self.src, + "{result}.initialize(i, std::make_pair({}, {}));", + move_if_necessary(body_key), + move_if_necessary(body_value) + ); + uwriteln!(self.src, "}}"); + + if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric + && matches!(self.variant, AbiVariant::GuestExport) + { + self.r#gen.r#gen.dependencies.needs_wit = true; + results.push(format!( + "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" + )); + } else { + 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]); + 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};"); + 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_; }