Skip to content
6 changes: 6 additions & 0 deletions crates/cpp/helper-types/wit.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ class string {
char const* end() const {
return (char const*)data_ + length;
}
friend bool operator<(string const &a, string const &b) {
return a.get_view() < b.get_view();
}
friend bool operator==(string const &a, string const &b) {
return a.get_view() == b.get_view();
}
};

/// A vector in linear memory, freed unconditionally using free
Expand Down
117 changes: 108 additions & 9 deletions crates/cpp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ struct Includes {
needs_wit: bool,
needs_memory: bool,
needs_array: bool,
needs_map: bool,
}

#[derive(Default)]
Expand Down Expand Up @@ -435,6 +436,9 @@ impl Cpp {
if self.dependencies.needs_bit {
self.include("<bit>");
}
if self.dependencies.needs_map {
self.include("<map>");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would be better to have some configurability here? As a user, I'd often want to avoid std::map for performance reasons, and if I'm really perf-focused, I'd also avoid std::unordered_map and use some custom cache-friendly hash map type. Do you think we can enable this use-case without mandating an additional copy of the data?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that all the allowed key types from the spec would be hashable, so at least it may be better to default to std::unordered_map

}
}

fn start_new_file(&mut self, condition: Option<bool>) -> FileContext {
Expand Down Expand Up @@ -914,7 +918,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 +1745,12 @@ impl CppInterfaceGenerator<'_> {
self.type_name(ty, from_namespace, flavor)
)
}
TypeDefKind::Map(_, _) => todo!(),
TypeDefKind::Map(key, value) => {
self.r#gen.dependencies.needs_map = true;
let k = self.type_name(key, from_namespace, flavor);
let v = self.type_name(value, from_namespace, flavor);
format!("std::map<{k}, {v}>")
}
TypeDefKind::Unknown => todo!(),
},
Type::ErrorContext => todo!(),
Expand Down Expand Up @@ -2266,7 +2275,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 @@ -3540,12 +3549,102 @@ 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 idx = format!("idx{tmp}");
let entry_name = format!("entry{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);
self.push_str(&format!("auto&& {val} = {};\n", operands[0]));
self.push_str(&format!("auto {len} = (size_t)({val}.size());\n"));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid the C-style cast here?

uwriteln!(
self.src,
"auto {ptr} = ({ptr_type})({len} > 0 ? cabi_realloc(nullptr, 0, {align}, {len} * {size}) : nullptr);",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid the C-style cast here?

ptr_type = self.r#gen.r#gen.opts.ptr_type()
);
uwriteln!(self.src, "size_t {idx} = 0;");
uwriteln!(self.src, "for (auto& {entry_name} : {val}) {{");
uwriteln!(self.src, "auto iter_map_key = {entry_name}.first;");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we're copying the key and taking a reference to the value? Can we at least move the key?

uwriteln!(self.src, "auto& iter_map_value = {entry_name}.second;");
uwriteln!(self.src, "auto base = {ptr} + {idx} * {size};");
uwrite!(self.src, "{}", body.0);
uwriteln!(self.src, "++{idx};");
uwriteln!(self.src, "}}");
if realloc.is_some() {
// ownership transfers
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this comment means or what this block is for

}
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 key_type = self.r#gen.type_name(key, &self.namespace, Flavor::InStruct);
let value_type = self
.r#gen
.type_name(value, &self.namespace, Flavor::InStruct);
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, "std::map<{key_type}, {value_type}> {result};");
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}.insert(std::make_pair({}, {}));",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think .emplace would be better here

move_if_necessary(body_key),
move_if_necessary(body_value)
);
uwriteln!(self.src, "}}");
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]);
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;");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this line? In case the body doesn't use 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 @@ -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