Skip to content
65 changes: 65 additions & 0 deletions crates/cpp/helper-types/wit.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <stdlib.h> // free
#include <new>
#include <span>
#include <utility> // pair

namespace wit {
/// @brief Helper class to map between IDs and resources
Expand Down Expand Up @@ -170,6 +171,70 @@ template <class T> 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<K, V>` (`list<tuple<K, V>>`)
/// 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 K, class V> class map {
public:
using entry_type = std::pair<K, V>;

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<K, V> allocate(size_t len) {
if (!len) return map<K, V>(empty_ptr(), 0);
return map<K, V>((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<entry_type> get_view() const { return std::span<entry_type>(data_, length); }
std::span<entry_type const> get_const_view() const { return std::span<entry_type const>(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.
Expand Down
176 changes: 156 additions & 20 deletions crates/cpp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(),
}
}
Expand Down Expand Up @@ -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<std::pair<{k}, {v}> 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<std::pair<{k}, {v}> const>")
}
_ => {
self.r#gen.dependencies.needs_wit = true;
format!("wit::map<{k}, {v}>")
}
}
}
TypeDefKind::Unknown => todo!(),
},
Type::ErrorContext => todo!(),
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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, "}}");
Expand Down Expand Up @@ -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, "}}");
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/test/src/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
}
Expand Down
Loading