Skip to content

Commit 33bb552

Browse files
committed
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 <yordis.prieto@gmail.com>
1 parent 235eb9e commit 33bb552

File tree

2 files changed

+124
-30
lines changed

2 files changed

+124
-30
lines changed

crates/cpp/helper-types/wit.h

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <stdlib.h> // free
1414
#include <new>
1515
#include <span>
16+
#include <utility> // pair
1617

1718
namespace wit {
1819
/// @brief Helper class to map between IDs and resources
@@ -107,12 +108,6 @@ class string {
107108
char const* end() const {
108109
return (char const*)data_ + length;
109110
}
110-
friend bool operator<(string const &a, string const &b) {
111-
return a.get_view() < b.get_view();
112-
}
113-
friend bool operator==(string const &a, string const &b) {
114-
return a.get_view() == b.get_view();
115-
}
116111
};
117112

118113
/// A vector in linear memory, freed unconditionally using free
@@ -176,6 +171,70 @@ template <class T> class vector {
176171
}
177172
};
178173

174+
/// @brief A map stored as a contiguous array of key-value pairs in linear
175+
/// memory, freed unconditionally using free.
176+
///
177+
/// Mirrors the canonical ABI representation of `map<K, V>` (`list<tuple<K, V>>`)
178+
/// to enable lift without per-entry tree allocation. Unlike `std::map` this
179+
/// container provides flat iteration only — callers who need keyed lookup can
180+
/// build their own index from `get_view()`.
181+
template <class K, class V> class map {
182+
public:
183+
using entry_type = std::pair<K, V>;
184+
185+
private:
186+
entry_type *data_;
187+
size_t length;
188+
189+
static entry_type* empty_ptr() { return (entry_type*)alignof(entry_type); }
190+
191+
public:
192+
map(map const &) = delete;
193+
map(map &&b) : data_(b.data_), length(b.length) { b.data_ = nullptr; }
194+
map &operator=(map const &) = delete;
195+
map &operator=(map &&b) {
196+
if (data_ && length > 0) {
197+
for (unsigned i = 0; i < length; ++i) { data_[i].~entry_type(); }
198+
free(data_);
199+
}
200+
data_ = b.data_;
201+
length = b.length;
202+
b.data_ = nullptr;
203+
return *this;
204+
}
205+
map(entry_type *d, size_t l) : data_(d), length(l) {}
206+
map() : data_(empty_ptr()), length() {}
207+
entry_type const *data() const { return data_; }
208+
entry_type *data() { return data_; }
209+
entry_type &operator[](size_t n) { return data_[n]; }
210+
entry_type const &operator[](size_t n) const { return data_[n]; }
211+
size_t size() const { return length; }
212+
bool empty() const { return !length; }
213+
~map() {
214+
if (data_ && length > 0) {
215+
for (unsigned i = 0; i < length; ++i) { data_[i].~entry_type(); }
216+
free((void*)data_);
217+
}
218+
}
219+
// WARNING: map contains uninitialized entries; caller must construct them via
220+
// `initialize` before the map is observed or destroyed.
221+
static map<K, V> allocate(size_t len) {
222+
if (!len) return map<K, V>(empty_ptr(), 0);
223+
return map<K, V>((entry_type*)malloc(sizeof(entry_type) * len), len);
224+
}
225+
void initialize(size_t n, entry_type&& entry) {
226+
new ((void*)(data_ + n)) entry_type(std::move(entry));
227+
}
228+
entry_type* leak() { entry_type* result = data_; data_ = nullptr; return result; }
229+
static void drop_raw(void *ptr) { if (ptr != empty_ptr()) free(ptr); }
230+
std::span<entry_type> get_view() const { return std::span<entry_type>(data_, length); }
231+
std::span<entry_type const> get_const_view() const { return std::span<entry_type const>(data_, length); }
232+
entry_type* begin() { return data_; }
233+
entry_type* end() { return data_ + length; }
234+
entry_type const* begin() const { return data_; }
235+
entry_type const* end() const { return data_ + length; }
236+
};
237+
179238
/// @brief A Resource defined within the guest (guest side)
180239
///
181240
/// It registers with the host and should remain in a static location.

crates/cpp/src/lib.rs

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ struct Includes {
7575
needs_wit: bool,
7676
needs_memory: bool,
7777
needs_array: bool,
78-
needs_map: bool,
7978
}
8079

8180
#[derive(Default)]
@@ -436,9 +435,6 @@ impl Cpp {
436435
if self.dependencies.needs_bit {
437436
self.include("<bit>");
438437
}
439-
if self.dependencies.needs_map {
440-
self.include("<map>");
441-
}
442438
}
443439

444440
fn start_new_file(&mut self, condition: Option<bool>) -> FileContext {
@@ -1746,10 +1742,31 @@ impl CppInterfaceGenerator<'_> {
17461742
)
17471743
}
17481744
TypeDefKind::Map(key, value) => {
1749-
self.r#gen.dependencies.needs_map = true;
1750-
let k = self.type_name(key, from_namespace, flavor);
1751-
let v = self.type_name(value, from_namespace, flavor);
1752-
format!("std::map<{k}, {v}>")
1745+
let element_flavor = match flavor {
1746+
Flavor::BorrowedArgument | Flavor::Argument(AbiVariant::GuestImport) => {
1747+
Flavor::BorrowedArgument
1748+
}
1749+
_ => Flavor::InStruct,
1750+
};
1751+
let k = self.type_name(key, from_namespace, element_flavor);
1752+
let v = self.type_name(value, from_namespace, element_flavor);
1753+
match flavor {
1754+
Flavor::BorrowedArgument => {
1755+
self.r#gen.dependencies.needs_span = true;
1756+
format!("std::span<std::pair<{k}, {v}> const>")
1757+
}
1758+
Flavor::Argument(var)
1759+
if matches!(var, AbiVariant::GuestImport)
1760+
|| self.r#gen.opts.api_style == APIStyle::Symmetric =>
1761+
{
1762+
self.r#gen.dependencies.needs_span = true;
1763+
format!("std::span<std::pair<{k}, {v}> const>")
1764+
}
1765+
_ => {
1766+
self.r#gen.dependencies.needs_wit = true;
1767+
format!("wit::map<{k}, {v}>")
1768+
}
1769+
}
17531770
}
17541771
TypeDefKind::Unknown => todo!(),
17551772
},
@@ -3559,28 +3576,28 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> {
35593576
let val = format!("map{tmp}");
35603577
let ptr = format!("ptr{tmp}");
35613578
let len = format!("len{tmp}");
3562-
let idx = format!("idx{tmp}");
3563-
let entry_name = format!("entry{tmp}");
35643579
let entry = self.r#gen.sizes.record([*key, *value]);
35653580
let size = entry.size.format(POINTER_SIZE_EXPRESSION);
35663581
let align = entry.align.format(POINTER_SIZE_EXPRESSION);
3582+
// The canonical ABI entry layout can differ from the C++ entry
3583+
// layout (see wit-bindgen#1592), so always allocate a fresh ABI
3584+
// buffer rather than reusing the source map's storage.
35673585
self.push_str(&format!("auto&& {val} = {};\n", operands[0]));
35683586
self.push_str(&format!("auto {len} = (size_t)({val}.size());\n"));
35693587
uwriteln!(
35703588
self.src,
35713589
"auto {ptr} = ({ptr_type})({len} > 0 ? cabi_realloc(nullptr, 0, {align}, {len} * {size}) : nullptr);",
35723590
ptr_type = self.r#gen.r#gen.opts.ptr_type()
35733591
);
3574-
uwriteln!(self.src, "size_t {idx} = 0;");
3575-
uwriteln!(self.src, "for (auto& {entry_name} : {val}) {{");
3576-
uwriteln!(self.src, "auto iter_map_key = {entry_name}.first;");
3577-
uwriteln!(self.src, "auto& iter_map_value = {entry_name}.second;");
3578-
uwriteln!(self.src, "auto base = {ptr} + {idx} * {size};");
3592+
uwriteln!(self.src, "for (size_t i = 0; i < {len}; ++i) {{");
3593+
uwriteln!(self.src, "auto base = {ptr} + i * {size};");
3594+
uwriteln!(self.src, "auto&& iter_entry = {val}[i];");
3595+
uwriteln!(self.src, "auto&& iter_map_key = iter_entry.first;");
3596+
uwriteln!(self.src, "auto&& iter_map_value = iter_entry.second;");
35793597
uwrite!(self.src, "{}", body.0);
3580-
uwriteln!(self.src, "++{idx};");
35813598
uwriteln!(self.src, "}}");
35823599
if realloc.is_some() {
3583-
// ownership transfers
3600+
uwriteln!(self.src, "{}.leak();", operands[0]);
35843601
}
35853602
results.push(ptr);
35863603
results.push(len);
@@ -3590,16 +3607,24 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> {
35903607
let tmp = self.tmp();
35913608
let entry = self.r#gen.sizes.record([*key, *value]);
35923609
let size = entry.size.format(POINTER_SIZE_EXPRESSION);
3593-
let key_type = self.r#gen.type_name(key, &self.namespace, Flavor::InStruct);
3594-
let value_type = self
3595-
.r#gen
3596-
.type_name(value, &self.namespace, Flavor::InStruct);
3610+
let flavor = if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric
3611+
&& matches!(self.variant, AbiVariant::GuestExport)
3612+
{
3613+
Flavor::BorrowedArgument
3614+
} else {
3615+
Flavor::InStruct
3616+
};
3617+
let key_type = self.r#gen.type_name(key, &self.namespace, flavor);
3618+
let value_type = self.r#gen.type_name(value, &self.namespace, flavor);
35973619
let len = format!("len{tmp}");
35983620
let base = format!("base{tmp}");
35993621
let result = format!("result{tmp}");
36003622
uwriteln!(self.src, "auto {base} = {};", operands[0]);
36013623
uwriteln!(self.src, "auto {len} = {};", operands[1]);
3602-
uwriteln!(self.src, "std::map<{key_type}, {value_type}> {result};");
3624+
uwriteln!(
3625+
self.src,
3626+
"auto {result} = wit::map<{key_type}, {value_type}>::allocate({len});"
3627+
);
36033628
if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric
36043629
&& matches!(self.variant, AbiVariant::GuestExport)
36053630
{
@@ -3613,12 +3638,22 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> {
36133638
let body_value = &body.1[1];
36143639
uwriteln!(
36153640
self.src,
3616-
"{result}.insert(std::make_pair({}, {}));",
3641+
"{result}.initialize(i, std::make_pair({}, {}));",
36173642
move_if_necessary(body_key),
36183643
move_if_necessary(body_value)
36193644
);
36203645
uwriteln!(self.src, "}}");
3621-
results.push(move_if_necessary(&result));
3646+
3647+
if self.r#gen.r#gen.opts.api_style == APIStyle::Symmetric
3648+
&& matches!(self.variant, AbiVariant::GuestExport)
3649+
{
3650+
results.push(format!("{result}.get_const_view()"));
3651+
self.leak_on_insertion.replace(format!(
3652+
"if ({len}>0) _deallocate.push_back((void*){result}.leak());\n"
3653+
));
3654+
} else {
3655+
results.push(move_if_necessary(&result));
3656+
}
36223657
}
36233658
abi::Instruction::IterMapKey { .. } => {
36243659
results.push("iter_map_key".to_string());

0 commit comments

Comments
 (0)