diff --git a/crates/cpp/helper-types/wit.h b/crates/cpp/helper-types/wit.h index 8a79db32c..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 @@ -170,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 3e549b0e1..beb6bb838 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,33 @@ 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); + 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!(), }, Type::ErrorContext => todo!(), @@ -2266,7 +2292,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( @@ -3482,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, "}}"); @@ -3540,12 +3567,121 @@ 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, "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, "}}"); + 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::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};"); + 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) + { + 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()); + } + 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};"); + 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_; }