Skip to content

Commit 62bba7d

Browse files
authored
feat(c): add map type support (#1611)
* feat(c): add map type support The flat { entry_t *ptr; size_t len; } shape matches the canonical ABI's lowered layout for map<K, V>, so reusing the list fast-path keeps the FFI boundary copy-free and brings C to parity with the Rust, Go, MoonBit, and C# backends that already ship map support. Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com> * test(c): add map runtime test variants Covers the C-side lower/lift boundary behaviorally against the existing Rust/Go/MoonBit suite, so future ABI changes that compile cleanly cannot silently regress map round-trips. Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com> --------- Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent 0975733 commit 62bba7d

4 files changed

Lines changed: 675 additions & 14 deletions

File tree

‎crates/c/src/lib.rs‎

Lines changed: 123 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,7 @@ fn is_prim_type_id(resolve: &Resolve, id: TypeId) -> bool {
10991099
| TypeDefKind::Stream(_)
11001100
| TypeDefKind::Unknown => false,
11011101
TypeDefKind::FixedLengthList(..) => todo!(),
1102-
TypeDefKind::Map(..) => todo!(),
1102+
TypeDefKind::Map(key, value) => is_prim_type(resolve, key) && is_prim_type(resolve, value),
11031103
}
11041104
}
11051105

@@ -1185,7 +1185,12 @@ pub fn push_ty_name(resolve: &Resolve, ty: &Type, src: &mut String) {
11851185
}
11861186
TypeDefKind::Unknown => unreachable!(),
11871187
TypeDefKind::FixedLengthList(..) => todo!(),
1188-
TypeDefKind::Map(..) => todo!(),
1188+
TypeDefKind::Map(key, value) => {
1189+
src.push_str("map_");
1190+
push_ty_name(resolve, key, src);
1191+
src.push_str("_");
1192+
push_ty_name(resolve, value, src);
1193+
}
11891194
}
11901195
}
11911196
}
@@ -1394,12 +1399,12 @@ impl Return {
13941399
TypeDefKind::Tuple(_)
13951400
| TypeDefKind::Record(_)
13961401
| TypeDefKind::List(_)
1402+
| TypeDefKind::Map(..)
13971403
| TypeDefKind::Variant(_) => {}
13981404

13991405
TypeDefKind::Resource => todo!("return_single for resource"),
14001406
TypeDefKind::Unknown => unreachable!(),
14011407
TypeDefKind::FixedLengthList(..) => todo!(),
1402-
TypeDefKind::Map(..) => todo!(),
14031408
}
14041409

14051410
self.retptrs.push(*orig_ty);
@@ -1757,8 +1762,15 @@ void __wasm_export_{ns}_{snake}_dtor({ns}_{snake}_t* arg) {{
17571762
todo!("named fixed-length list types are not yet supported in the C backend")
17581763
}
17591764

1760-
fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) {
1761-
todo!("map types are not yet supported in the C backend")
1765+
fn type_map(&mut self, id: TypeId, _name: &str, key: &Type, value: &Type, docs: &Docs) {
1766+
self.src.h_defs("\n");
1767+
self.docs(docs, SourceType::HDefs);
1768+
self.print_map_entry_typedef(id, key, value);
1769+
self.start_typedef_struct(id);
1770+
self.print_map_entry_typename(id);
1771+
self.src.h_defs(" *ptr;\n");
1772+
self.src.h_defs("size_t len;\n");
1773+
self.finish_typedef_struct(id);
17621774
}
17631775

17641776
fn type_future(&mut self, id: TypeId, _name: &str, _ty: &Option<Type>, docs: &Docs) {
@@ -1883,8 +1895,16 @@ impl<'a> wit_bindgen_core::AnonymousTypeGenerator<'a> for InterfaceGenerator<'a>
18831895
todo!("print_anonymous_type for fixed length list");
18841896
}
18851897

1886-
fn anonymous_type_map(&mut self, _id: TypeId, _key: &Type, _value: &Type, _docs: &Docs) {
1887-
todo!("anonymous map types are not yet supported in the C backend");
1898+
fn anonymous_type_map(&mut self, id: TypeId, key: &Type, value: &Type, _docs: &Docs) {
1899+
self.print_map_entry_typedef(id, key, value);
1900+
self.src.h_defs("\ntypedef ");
1901+
self.src.h_defs("struct {\n");
1902+
self.print_map_entry_typename(id);
1903+
self.src.h_defs(" *ptr;\n");
1904+
self.src.h_defs("size_t len;\n");
1905+
self.src.h_defs("}");
1906+
self.src.h_defs(" ");
1907+
self.print_typedef_target(id);
18881908
}
18891909
}
18901910

@@ -2064,7 +2084,19 @@ impl InterfaceGenerator<'_> {
20642084
}
20652085
TypeDefKind::Unknown => unreachable!(),
20662086
TypeDefKind::FixedLengthList(..) => todo!(),
2067-
TypeDefKind::Map(..) => todo!(),
2087+
TypeDefKind::Map(key, value) => {
2088+
let entry_name = self.map_entry_typename(id);
2089+
self.src.c_helpers("size_t map_len = ptr->len;\n");
2090+
uwriteln!(self.src.c_helpers, "if (map_len > 0) {{");
2091+
uwriteln!(self.src.c_helpers, "{entry_name} *map_ptr = ptr->ptr;");
2092+
self.src
2093+
.c_helpers("for (size_t i = 0; i < map_len; i++) {\n");
2094+
self.free(key, "&map_ptr[i].key");
2095+
self.free(value, "&map_ptr[i].value");
2096+
self.src.c_helpers("}\n");
2097+
uwriteln!(self.src.c_helpers, "free(map_ptr);");
2098+
uwriteln!(self.src.c_helpers, "}}");
2099+
}
20682100
}
20692101
if c_helpers_body_start == self.src.c_helpers.len() {
20702102
self.src.c_helpers.as_mut_string().truncate(c_helpers_start);
@@ -2673,6 +2705,31 @@ void {name}_return({return_ty}) {{
26732705
self.print_typedef_target(id);
26742706
}
26752707

2708+
/// Returns the typedef name of the synthetic entry struct used as the
2709+
/// element type of a `map<K, V>`'s flat-pair representation.
2710+
fn map_entry_typename(&self, id: TypeId) -> String {
2711+
let map_name = &self.r#gen.type_names[&id];
2712+
let prefix = map_name.strip_suffix("_t").unwrap_or(map_name.as_str());
2713+
format!("{prefix}_entry_t")
2714+
}
2715+
2716+
fn print_map_entry_typename(&mut self, id: TypeId) {
2717+
let entry_name = self.map_entry_typename(id);
2718+
self.src.h_defs(&entry_name);
2719+
}
2720+
2721+
/// Emits the `typedef struct { K key; V value; } <map>_entry_t;` companion
2722+
/// declaration that precedes the map struct itself.
2723+
fn print_map_entry_typedef(&mut self, id: TypeId, key: &Type, value: &Type) {
2724+
self.src.h_defs("\ntypedef struct {\n");
2725+
self.print_ty(SourceType::HDefs, key);
2726+
self.src.h_defs(" key;\n");
2727+
self.print_ty(SourceType::HDefs, value);
2728+
self.src.h_defs(" value;\n} ");
2729+
self.print_map_entry_typename(id);
2730+
self.src.h_defs(";\n");
2731+
}
2732+
26762733
fn owner_namespace(&self, id: TypeId) -> String {
26772734
owner_namespace(
26782735
self.interface,
@@ -2758,7 +2815,9 @@ void {name}_return({return_ty}) {{
27582815

27592816
TypeDefKind::Unknown => false,
27602817
TypeDefKind::FixedLengthList(..) => todo!(),
2761-
TypeDefKind::Map(..) => todo!(),
2818+
TypeDefKind::Map(key, value) => {
2819+
self.contains_droppable_borrow(key) || self.contains_droppable_borrow(value)
2820+
}
27622821
}
27632822
} else {
27642823
false
@@ -3640,7 +3699,35 @@ impl Bindgen for FunctionBindgen<'_, '_> {
36403699
list_name, elem_name, operands[0], operands[1]
36413700
));
36423701
}
3702+
3703+
// Maps share the canonical "flat array of entries" layout with the
3704+
// C struct emitted by `type_map`/`anonymous_type_map`, so the body
3705+
// block is discarded and we splice `.ptr` / `.len` through directly
3706+
// (mirroring how `ListLower`/`ListLift` work above).
3707+
Instruction::MapLower { .. } => {
3708+
let _body = self.blocks.pop().unwrap();
3709+
results.push(format!("(uint8_t *) ({}).ptr", operands[0]));
3710+
results.push(format!("({}).len", operands[0]));
3711+
}
3712+
3713+
Instruction::MapLift { ty, .. } => {
3714+
self.assert_no_droppable_borrows("map", &Type::Id(*ty));
3715+
3716+
let _body = self.blocks.pop().unwrap();
3717+
let map_name = self.r#gen.r#gen.type_name(&Type::Id(*ty));
3718+
let entry_name = {
3719+
let prefix = map_name.strip_suffix("_t").unwrap_or(&map_name);
3720+
format!("{prefix}_entry_t")
3721+
};
3722+
results.push(format!(
3723+
"({}) {{ ({}*)({}), ({}) }}",
3724+
map_name, entry_name, operands[0], operands[1]
3725+
));
3726+
}
3727+
36433728
Instruction::IterElem { .. } => results.push("e".to_string()),
3729+
Instruction::IterMapKey { .. } => results.push("map_key".to_string()),
3730+
Instruction::IterMapValue { .. } => results.push("map_value".to_string()),
36443731
Instruction::IterBasePointer => results.push("base".to_string()),
36453732

36463733
Instruction::CallWasm { sig, .. } => {
@@ -3976,6 +4063,29 @@ impl Bindgen for FunctionBindgen<'_, '_> {
39764063
uwriteln!(self.src, "}}");
39774064
}
39784065

4066+
Instruction::GuestDeallocateMap { key, value } => {
4067+
let (body, results) = self.blocks.pop().unwrap();
4068+
assert!(results.is_empty());
4069+
let len = self.locals.tmp("len");
4070+
uwriteln!(self.src, "size_t {len} = {};", operands[1]);
4071+
uwriteln!(self.src, "if ({len} > 0) {{");
4072+
let ptr = self.locals.tmp("ptr");
4073+
uwriteln!(self.src, "uint8_t *{ptr} = {};", operands[0]);
4074+
let i = self.locals.tmp("i");
4075+
uwriteln!(self.src, "for (size_t {i} = 0; {i} < {len}; {i}++) {{");
4076+
let entry = self.r#gen.r#gen.sizes.record([*key, *value]);
4077+
uwriteln!(
4078+
self.src,
4079+
"uint8_t *base = {ptr} + {i} * {};",
4080+
entry.size.format(POINTER_SIZE_EXPRESSION)
4081+
);
4082+
uwriteln!(self.src, "(void) base;");
4083+
uwrite!(self.src, "{body}");
4084+
uwriteln!(self.src, "}}");
4085+
uwriteln!(self.src, "free({ptr});");
4086+
uwriteln!(self.src, "}}");
4087+
}
4088+
39794089
Instruction::Flush { amt } => {
39804090
results.extend(operands.iter().take(*amt).cloned());
39814091
}
@@ -4122,13 +4232,15 @@ pub fn is_arg_by_pointer(resolve: &Resolve, ty: &Type) -> bool {
41224232
TypeDefKind::Enum(_) => false,
41234233
TypeDefKind::Flags(_) => false,
41244234
TypeDefKind::Handle(_) => false,
4125-
TypeDefKind::Tuple(_) | TypeDefKind::Record(_) | TypeDefKind::List(_) => true,
4235+
TypeDefKind::Tuple(_)
4236+
| TypeDefKind::Record(_)
4237+
| TypeDefKind::List(_)
4238+
| TypeDefKind::Map(..) => true,
41264239
TypeDefKind::Future(_) => false,
41274240
TypeDefKind::Stream(_) => false,
41284241
TypeDefKind::Resource => todo!("is_arg_by_pointer for resource"),
41294242
TypeDefKind::Unknown => unreachable!(),
41304243
TypeDefKind::FixedLengthList(..) => todo!(),
4131-
TypeDefKind::Map(..) => todo!(),
41324244
},
41334245
Type::String => true,
41344246
_ => false,

‎crates/test/src/c.rs‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ impl LanguageMethods for C {
5656
config: &crate::config::WitConfig,
5757
_args: &[String],
5858
) -> bool {
59-
config.error_context
60-
|| name.starts_with("named-fixed-length-list.wit")
61-
|| name.starts_with("map.wit")
59+
config.error_context || name.starts_with("named-fixed-length-list.wit")
6260
}
6361

6462
fn codegen_test_variants(&self) -> &[(&str, &[&str])] {

0 commit comments

Comments
 (0)