Skip to content

Commit a4b3eb1

Browse files
authored
feat(moonbit): add map type support (#1584)
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent 7b5c1c6 commit a4b3eb1

File tree

4 files changed

+195
-8
lines changed

4 files changed

+195
-8
lines changed

crates/moonbit/src/lib.rs

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,7 +1459,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> {
14591459
}
14601460

14611461
fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) {
1462-
todo!("map types are not yet supported in the MoonBit backend")
1462+
// Not needed. Maps become `Map[K, V]` inline in MoonBit
14631463
}
14641464

14651465
fn type_future(&mut self, _id: TypeId, _name: &str, _ty: &Option<Type>, _docs: &Docs) {
@@ -2889,12 +2889,118 @@ impl Bindgen for FunctionBindgen<'_, '_> {
28892889
results.push(format!("FixedArray::from_array({array}[:])"));
28902890
}
28912891

2892-
Instruction::MapLower { .. }
2893-
| Instruction::MapLift { .. }
2894-
| Instruction::IterMapKey { .. }
2895-
| Instruction::IterMapValue { .. }
2896-
| Instruction::GuestDeallocateMap { .. } => {
2897-
todo!("map types are not yet supported in this backend")
2892+
Instruction::MapLower {
2893+
key,
2894+
value,
2895+
realloc,
2896+
} => {
2897+
let Block {
2898+
body,
2899+
results: block_results,
2900+
} = self.blocks.pop().unwrap();
2901+
assert!(block_results.is_empty());
2902+
2903+
let op = &operands[0];
2904+
let entry = self.interface_gen.world_gen.sizes.record([*key, *value]);
2905+
let size = entry.size.size_wasm32();
2906+
let address = self.locals.tmp("address");
2907+
let index = self.locals.tmp("index");
2908+
let iter_map_key = self.locals.tmp("iter_map_key");
2909+
let iter_map_value = self.locals.tmp("iter_map_value");
2910+
2911+
self.use_ffi(ffi::MALLOC);
2912+
uwrite!(
2913+
self.src,
2914+
"
2915+
let {address} = mbt_ffi_malloc(({op}).length() * {size});
2916+
let mut {index} = 0
2917+
({op}).each(fn({iter_map_key}, {iter_map_value}) {{
2918+
let iter_map_key = {iter_map_key}
2919+
let iter_map_value = {iter_map_value}
2920+
let iter_base = {address} + ({index} * {size})
2921+
{body}
2922+
{index} = {index} + 1
2923+
}})
2924+
",
2925+
);
2926+
2927+
results.push(address.clone());
2928+
results.push(format!("({op}).length()"));
2929+
2930+
if realloc.is_none() {
2931+
self.cleanup.push(Cleanup { address });
2932+
}
2933+
}
2934+
2935+
Instruction::MapLift { key, value, .. } => {
2936+
let Block {
2937+
body,
2938+
results: block_results,
2939+
} = self.blocks.pop().unwrap();
2940+
let address = &operands[0];
2941+
let length = &operands[1];
2942+
let map = self.locals.tmp("map");
2943+
let key_ty = self.resolve_type_name(key);
2944+
let value_ty = self.resolve_type_name(value);
2945+
let entry = self.interface_gen.world_gen.sizes.record([*key, *value]);
2946+
let size = entry.size.size_wasm32();
2947+
let index = self.locals.tmp("index");
2948+
2949+
let (body_key, body_value) = match &block_results[..] {
2950+
[k, v] => (k, v),
2951+
_ => todo!(
2952+
"expected 2 results from map lift block, got {}",
2953+
block_results.len()
2954+
),
2955+
};
2956+
2957+
self.use_ffi(ffi::FREE);
2958+
uwrite!(
2959+
self.src,
2960+
"
2961+
let {map} : Map[{key_ty}, {value_ty}] = {{}}
2962+
for {index} = 0; {index} < ({length}); {index} = {index} + 1 {{
2963+
let iter_base = ({address}) + ({index} * {size})
2964+
{body}
2965+
{map}[{body_key}] = {body_value}
2966+
}}
2967+
mbt_ffi_free({address})
2968+
",
2969+
);
2970+
2971+
results.push(map);
2972+
}
2973+
2974+
Instruction::IterMapKey { .. } => results.push("iter_map_key".into()),
2975+
2976+
Instruction::IterMapValue { .. } => results.push("iter_map_value".into()),
2977+
2978+
Instruction::GuestDeallocateMap { key, value } => {
2979+
let Block { body, results, .. } = self.blocks.pop().unwrap();
2980+
assert!(results.is_empty());
2981+
2982+
let address = &operands[0];
2983+
let length = &operands[1];
2984+
2985+
let entry = self.interface_gen.world_gen.sizes.record([*key, *value]);
2986+
let size = entry.size.size_wasm32();
2987+
2988+
if !body.trim().is_empty() {
2989+
let index = self.locals.tmp("index");
2990+
2991+
uwrite!(
2992+
self.src,
2993+
"
2994+
for {index} = 0; {index} < ({length}); {index} = {index} + 1 {{
2995+
let iter_base = ({address}) + ({index} * {size})
2996+
{body}
2997+
}}
2998+
"
2999+
);
3000+
}
3001+
3002+
self.use_ffi(ffi::FREE);
3003+
uwriteln!(self.src, "mbt_ffi_free({address})",);
28983004
}
28993005
}
29003006
}

crates/moonbit/src/pkg.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,14 @@ impl PkgResolver {
270270
)
271271
}
272272

273+
TypeDefKind::Map(key, value) => {
274+
format!(
275+
"Map[{}, {}]",
276+
self.type_name(this, &key),
277+
self.type_name(this, &value)
278+
)
279+
}
280+
273281
_ => {
274282
if let Some(name) = &ty.name {
275283
format!(

crates/test/src/moonbit.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ impl LanguageMethods for MoonBit {
126126
) -> bool {
127127
// async-resource-func actually works, but most other async tests
128128
// fail during codegen or verification
129-
name == "map.wit" || (config.async_ && name != "async-resource-func.wit")
129+
config.async_ && name != "async-resource-func.wit"
130130
}
131131

132132
fn verify(&self, runner: &Runner, verify: &crate::Verify) -> anyhow::Result<()> {

tests/runtime/map/test.mbt

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//@ [lang]
2+
//@ path = 'gen/interface/test_/maps/toTest/stub.mbt'
3+
4+
///|
5+
pub fn named_roundtrip(a : Map[UInt, String]) -> Map[String, UInt] {
6+
let result : Map[String, UInt] = {}
7+
a.each(fn(id, name) { result[name] = id })
8+
result
9+
}
10+
11+
///|
12+
pub fn bytes_roundtrip(a : Map[String, FixedArray[Byte]]) -> Map[String, FixedArray[Byte]] {
13+
a
14+
}
15+
16+
///|
17+
pub fn empty_roundtrip(a : Map[UInt, String]) -> Map[UInt, String] {
18+
a
19+
}
20+
21+
///|
22+
pub fn option_roundtrip(a : Map[String, UInt?]) -> Map[String, UInt?] {
23+
a
24+
}
25+
26+
///|
27+
pub fn record_roundtrip(a : LabeledEntry) -> LabeledEntry {
28+
a
29+
}
30+
31+
///|
32+
pub fn inline_roundtrip(a : Map[UInt, String]) -> Map[String, UInt] {
33+
let result : Map[String, UInt] = {}
34+
a.each(fn(k, v) { result[v] = k })
35+
result
36+
}
37+
38+
///|
39+
pub fn large_roundtrip(a : Map[UInt, String]) -> Map[UInt, String] {
40+
a
41+
}
42+
43+
///|
44+
pub fn multi_param_roundtrip(a : Map[UInt, String], b : Map[String, FixedArray[Byte]]) -> (Map[String, UInt], Map[String, FixedArray[Byte]]) {
45+
let ids : Map[String, UInt] = {}
46+
a.each(fn(id, name) { ids[name] = id })
47+
(ids, b)
48+
}
49+
50+
///|
51+
pub fn nested_roundtrip(a : Map[String, Map[UInt, String]]) -> Map[String, Map[UInt, String]] {
52+
a
53+
}
54+
55+
///|
56+
pub fn variant_roundtrip(a : MapOrString) -> MapOrString {
57+
a
58+
}
59+
60+
///|
61+
pub fn result_roundtrip(a : Result[Map[UInt, String], String]) -> Result[Map[UInt, String], String] {
62+
a
63+
}
64+
65+
///|
66+
pub fn tuple_roundtrip(a : (Map[UInt, String], UInt64)) -> (Map[UInt, String], UInt64) {
67+
a
68+
}
69+
70+
///|
71+
pub fn single_entry_roundtrip(a : Map[UInt, String]) -> Map[UInt, String] {
72+
a
73+
}

0 commit comments

Comments
 (0)