Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 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,107 @@ 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. 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 K, class V> class unordered_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:
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<K, V> allocate(size_t len) {
if (!len) return unordered_map<K, V>(empty_ptr(), 0);
return unordered_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);
}
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<K, V>` 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<K, V>` or
/// an explicit `(data, size)` pair.
template <class K, class V> class unordered_map_view {
public:
using entry_type = std::pair<K, V>;

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<K, V> 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.
Expand Down
181 changes: 161 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,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!(),
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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, "}}");
Expand Down Expand Up @@ -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, "}}");
}
}
}
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 @@ -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_;
}
Expand Down
Loading