Skip to content

Commit d56a486

Browse files
committed
feat(csharp): add map type support
Dictionary<K,V> provides a familiar, zero-dependency host representation for WIT maps in C#, and unblocks runtime interop with components that use them without requiring a custom collection type. Signed-off-by: Yordis Prieto <[email protected]>
1 parent 61243cf commit d56a486

5 files changed

Lines changed: 526 additions & 9 deletions

File tree

crates/csharp/src/function.rs

Lines changed: 193 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10111011
results: block_results,
10121012
element: block_element,
10131013
base,
1014+
..
10141015
} = self.blocks.pop().unwrap();
10151016
assert!(block_results.is_empty());
10161017

@@ -1414,6 +1415,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
14141415
results: block_results,
14151416
base,
14161417
element: _,
1418+
..
14171419
} = self.blocks.pop().unwrap();
14181420
assert!(block_results.is_empty());
14191421

@@ -1651,18 +1653,193 @@ impl Bindgen for FunctionBindgen<'_, '_> {
16511653
self.interface_gen.csharp_gen.needs_async_support = true;
16521654
}
16531655

1656+
Instruction::MapLower {
1657+
key,
1658+
value,
1659+
realloc,
1660+
} => {
1661+
let Block {
1662+
body,
1663+
results: block_results,
1664+
base,
1665+
map_key,
1666+
map_value,
1667+
..
1668+
} = self.blocks.pop().unwrap();
1669+
assert!(block_results.is_empty());
1670+
1671+
let map = &operands[0];
1672+
let entry = self
1673+
.interface_gen
1674+
.csharp_gen
1675+
.sizes
1676+
.record([*key, *value].iter().copied());
1677+
let size = entry.size.size_wasm32();
1678+
let align = entry.align.align_wasm32();
1679+
let key_ty = self.interface_gen.type_name_with_qualifier(key, true);
1680+
let value_ty = self.interface_gen.type_name_with_qualifier(value, true);
1681+
1682+
let index = self.locals.tmp("index");
1683+
let address = self.locals.tmp("address");
1684+
let buffer_size = self.locals.tmp("bufferSize");
1685+
let entry_var = self.locals.tmp("entry");
1686+
1687+
let (array_size, element_type) =
1688+
crate::world_generator::dotnet_aligned_array(size, align);
1689+
let ret_area = self.locals.tmp("retArea");
1690+
1691+
let array_size = if align > 1 {
1692+
format!("{array_size} * {map}.Count + 1")
1693+
} else {
1694+
format!("{array_size} * {map}.Count")
1695+
};
1696+
1697+
match realloc {
1698+
None => {
1699+
self.needs_cleanup = true;
1700+
self.interface_gen.csharp_gen.needs_align_stack_ptr = true;
1701+
uwrite!(
1702+
self.src,
1703+
"
1704+
void* {address};
1705+
if (({size} * {map}.Count) < 1024) {{
1706+
var {ret_area} = stackalloc {element_type}[{array_size}];
1707+
{address} = MemoryHelper.AlignStackPtr({ret_area}, {align});
1708+
}}
1709+
else
1710+
{{
1711+
var {buffer_size} = {size} * (nuint){map}.Count;
1712+
{address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align});
1713+
cleanups.Add(() => global::System.Runtime.InteropServices.NativeMemory.AlignedFree({address}));
1714+
}}
1715+
"
1716+
);
1717+
}
1718+
Some(_) => {
1719+
uwrite!(
1720+
self.src,
1721+
"
1722+
var {buffer_size} = {size} * (nuint){map}.Count;
1723+
void* {address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align});
1724+
"
1725+
);
1726+
}
1727+
}
1728+
1729+
uwrite!(
1730+
self.src,
1731+
"
1732+
int {index} = 0;
1733+
foreach (var {entry_var} in {map}) {{
1734+
{key_ty} {map_key} = {entry_var}.Key;
1735+
{value_ty} {map_value} = {entry_var}.Value;
1736+
int {base} = (int){address} + ({index} * {size});
1737+
{body}
1738+
++{index};
1739+
}}
1740+
"
1741+
);
1742+
1743+
results.push(format!("(int){address}"));
1744+
results.push(format!("{map}.Count"));
1745+
}
1746+
1747+
Instruction::MapLift { key, value, .. } => {
1748+
let Block {
1749+
body,
1750+
results: block_results,
1751+
base,
1752+
..
1753+
} = self.blocks.pop().unwrap();
1754+
let address = &operands[0];
1755+
let length = &operands[1];
1756+
let map = self.locals.tmp("map");
1757+
let key_ty = self.interface_gen.type_name_with_qualifier(key, true);
1758+
let value_ty = self.interface_gen.type_name_with_qualifier(value, true);
1759+
let entry = self
1760+
.interface_gen
1761+
.csharp_gen
1762+
.sizes
1763+
.record([*key, *value].iter().copied());
1764+
let size = entry.size.size_wasm32();
1765+
let index = self.locals.tmp("index");
1766+
1767+
let body_key = &block_results[0];
1768+
let body_value = &block_results[1];
1769+
1770+
uwrite!(
1771+
self.src,
1772+
"
1773+
var {map} = new global::System.Collections.Generic.Dictionary<{key_ty}, {value_ty}>((int){length});
1774+
for (int {index} = 0; {index} < {length}; ++{index}) {{
1775+
nint {base} = {address} + ({index} * {size});
1776+
{body}
1777+
{map}[{body_key}] = {body_value};
1778+
}}
1779+
1780+
if ({length} > 0) {{
1781+
global::System.Runtime.InteropServices.NativeMemory.Free((void*){address});
1782+
}}
1783+
"
1784+
);
1785+
1786+
results.push(map);
1787+
}
1788+
1789+
Instruction::IterMapKey { .. } => {
1790+
results.push(self.block_storage.last().unwrap().map_key.clone())
1791+
}
1792+
1793+
Instruction::IterMapValue { .. } => {
1794+
results.push(self.block_storage.last().unwrap().map_value.clone())
1795+
}
1796+
1797+
Instruction::GuestDeallocateMap { key, value } => {
1798+
let Block {
1799+
body,
1800+
results: block_results,
1801+
base,
1802+
..
1803+
} = self.blocks.pop().unwrap();
1804+
assert!(block_results.is_empty());
1805+
1806+
let address = &operands[0];
1807+
let length = &operands[1];
1808+
let entry = self
1809+
.interface_gen
1810+
.csharp_gen
1811+
.sizes
1812+
.record([*key, *value].iter().copied());
1813+
let size = entry.size.size_wasm32();
1814+
1815+
if !body.trim().is_empty() {
1816+
let index = self.locals.tmp("index");
1817+
1818+
uwrite!(
1819+
self.src,
1820+
"
1821+
for (int {index} = 0; {index} < {length}; ++{index}) {{
1822+
int {base} = (int){address} + ({index} * {size});
1823+
{body}
1824+
}}
1825+
"
1826+
);
1827+
}
1828+
1829+
uwriteln!(
1830+
self.src,
1831+
r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#,
1832+
operands[0]
1833+
);
1834+
}
1835+
16541836
Instruction::ErrorContextLower { .. }
16551837
| Instruction::ErrorContextLift { .. }
16561838
| Instruction::DropHandle { .. }
16571839
| Instruction::FixedLengthListLift { .. }
16581840
| Instruction::FixedLengthListLower { .. }
16591841
| Instruction::FixedLengthListLowerToMemory { .. }
1660-
| Instruction::FixedLengthListLiftFromMemory { .. }
1661-
| Instruction::MapLower { .. }
1662-
| Instruction::MapLift { .. }
1663-
| Instruction::IterMapKey { .. }
1664-
| Instruction::IterMapValue { .. }
1665-
| Instruction::GuestDeallocateMap { .. } => {
1842+
| Instruction::FixedLengthListLiftFromMemory { .. } => {
16661843
dbg!(inst);
16671844
todo!()
16681845
}
@@ -1738,6 +1915,8 @@ impl Bindgen for FunctionBindgen<'_, '_> {
17381915
body: mem::take(&mut self.src),
17391916
element: self.locals.tmp("element"),
17401917
base: self.locals.tmp("basePtr"),
1918+
map_key: self.locals.tmp("mapKey"),
1919+
map_value: self.locals.tmp("mapValue"),
17411920
});
17421921

17431922
self.is_block = true;
@@ -1748,13 +1927,17 @@ impl Bindgen for FunctionBindgen<'_, '_> {
17481927
body,
17491928
element,
17501929
base,
1930+
map_key,
1931+
map_value,
17511932
} = self.block_storage.pop().unwrap();
17521933

17531934
self.blocks.push(Block {
17541935
body: mem::replace(&mut self.src, body),
17551936
results: mem::take(operands),
17561937
element,
17571938
base,
1939+
map_key,
1940+
map_value,
17581941
});
17591942
self.is_block = false;
17601943
}
@@ -1827,6 +2010,8 @@ struct Block {
18272010
results: Vec<String>,
18282011
element: String,
18292012
base: String,
2013+
map_key: String,
2014+
map_value: String,
18302015
}
18312016

18322017
struct Fixed {
@@ -1838,6 +2023,8 @@ struct BlockStorage {
18382023
body: String,
18392024
element: String,
18402025
base: String,
2026+
map_key: String,
2027+
map_value: String,
18412028
}
18422029

18432030
#[derive(Clone)]

crates/csharp/src/interface.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ impl InterfaceGenerator<'_> {
134134
TypeDefKind::Option(t) => self.type_option(type_id, typedef_name, t, &type_def.docs),
135135
TypeDefKind::Record(t) => self.type_record(type_id, typedef_name, t, &type_def.docs),
136136
TypeDefKind::List(t) => self.type_list(type_id, typedef_name, t, &type_def.docs),
137+
TypeDefKind::Map(key, value) => {
138+
self.type_map(type_id, typedef_name, key, value, &type_def.docs)
139+
}
137140
TypeDefKind::Variant(t) => self.type_variant(type_id, typedef_name, t, &type_def.docs),
138141
TypeDefKind::Result(t) => self.type_result(type_id, typedef_name, t, &type_def.docs),
139142
TypeDefKind::Handle(_) => {
@@ -1174,6 +1177,13 @@ var {async_status_var} = {raw_name}({wasm_params});
11741177
};
11751178
return format!("StreamReader{generic_type}").to_owned();
11761179
}
1180+
TypeDefKind::Map(key, value) => {
1181+
format!(
1182+
"global::System.Collections.Generic.Dictionary<{}, {}>",
1183+
self.type_name_with_qualifier(key, qualifier),
1184+
self.type_name_with_qualifier(value, qualifier)
1185+
)
1186+
}
11771187
_ => {
11781188
if let Some(name) = &ty.name {
11791189
format!(
@@ -1749,8 +1759,8 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> {
17491759
todo!("named fixed-length list types are not yet supported in the C# backend")
17501760
}
17511761

1752-
fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) {
1753-
todo!("map types are not yet supported in the C# backend")
1762+
fn type_map(&mut self, id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) {
1763+
self.type_name(&Type::Id(id));
17541764
}
17551765

17561766
fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {

crates/test/src/csharp.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ impl LanguageMethods for Csharp {
5050
| "issue-1433.wit"
5151
| "issue-1598.wit"
5252
| "named-fixed-length-list.wit"
53-
| "map.wit"
5453
)
5554
}
5655

0 commit comments

Comments
 (0)