Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 193 additions & 6 deletions crates/csharp/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
results: block_results,
element: block_element,
base,
..
} = self.blocks.pop().unwrap();
assert!(block_results.is_empty());

Expand Down Expand Up @@ -1491,6 +1492,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
results: block_results,
base,
element: _,
..
} = self.blocks.pop().unwrap();
assert!(block_results.is_empty());

Expand Down Expand Up @@ -1756,18 +1758,193 @@ impl Bindgen for FunctionBindgen<'_, '_> {
self.interface_gen.csharp_gen.needs_async_support = true;
}

Instruction::MapLower {
key,
value,
realloc,
} => {
let Block {
body,
results: block_results,
base,
map_key,
map_value,
..
} = self.blocks.pop().unwrap();
assert!(block_results.is_empty());

let map = &operands[0];
let entry = self
.interface_gen
.csharp_gen
.sizes
.record([*key, *value].iter().copied());
let size = entry.size.size_wasm32();
let align = entry.align.align_wasm32();
let key_ty = self.interface_gen.type_name_with_qualifier(key, true);
let value_ty = self.interface_gen.type_name_with_qualifier(value, true);

let index = self.locals.tmp("index");
let address = self.locals.tmp("address");
let buffer_size = self.locals.tmp("bufferSize");
let entry_var = self.locals.tmp("entry");

let (array_size, element_type) =
crate::world_generator::dotnet_aligned_array(size, align);
let ret_area = self.locals.tmp("retArea");

let array_size = if align > 1 {
format!("{array_size} * {map}.Count + 1")
} else {
format!("{array_size} * {map}.Count")
};

match realloc {
None => {
self.needs_cleanup = true;
self.interface_gen.csharp_gen.needs_align_stack_ptr = true;
uwrite!(
self.src,
"
void* {address};
if (({size} * {map}.Count) < 1024) {{
var {ret_area} = stackalloc {element_type}[{array_size}];
{address} = MemoryHelper.AlignStackPtr({ret_area}, {align});
}}
else
{{
var {buffer_size} = {size} * (nuint){map}.Count;
{address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align});
cleanups.Add(() => global::System.Runtime.InteropServices.NativeMemory.AlignedFree({address}));
}}
"
);
}
Some(_) => {
uwrite!(
self.src,
"
var {buffer_size} = {size} * (nuint){map}.Count;
void* {address} = global::System.Runtime.InteropServices.NativeMemory.AlignedAlloc({buffer_size}, {align});
"
);
}
}

uwrite!(
self.src,
"
int {index} = 0;
foreach (var {entry_var} in {map}) {{
{key_ty} {map_key} = {entry_var}.Key;
{value_ty} {map_value} = {entry_var}.Value;
int {base} = (int){address} + ({index} * {size});
{body}
++{index};
}}
"
);

results.push(format!("(int){address}"));
results.push(format!("{map}.Count"));
}

Instruction::MapLift { key, value, .. } => {
let Block {
body,
results: block_results,
base,
..
} = self.blocks.pop().unwrap();
let address = &operands[0];
let length = &operands[1];
let map = self.locals.tmp("map");
let key_ty = self.interface_gen.type_name_with_qualifier(key, true);
let value_ty = self.interface_gen.type_name_with_qualifier(value, true);
let entry = self
.interface_gen
.csharp_gen
.sizes
.record([*key, *value].iter().copied());
let size = entry.size.size_wasm32();
let index = self.locals.tmp("index");

let body_key = &block_results[0];
let body_value = &block_results[1];

uwrite!(
self.src,
"
var {map} = new global::System.Collections.Generic.Dictionary<{key_ty}, {value_ty}>((int){length});
for (int {index} = 0; {index} < {length}; ++{index}) {{
nint {base} = {address} + ({index} * {size});
{body}
{map}[{body_key}] = {body_value};
}}

if ({length} > 0) {{
global::System.Runtime.InteropServices.NativeMemory.Free((void*){address});
}}
"
);

results.push(map);
}

Instruction::IterMapKey { .. } => {
results.push(self.block_storage.last().unwrap().map_key.clone())
}

Instruction::IterMapValue { .. } => {
results.push(self.block_storage.last().unwrap().map_value.clone())
}

Instruction::GuestDeallocateMap { key, value } => {
let Block {
body,
results: block_results,
base,
..
} = self.blocks.pop().unwrap();
assert!(block_results.is_empty());

let address = &operands[0];
let length = &operands[1];
let entry = self
.interface_gen
.csharp_gen
.sizes
.record([*key, *value].iter().copied());
let size = entry.size.size_wasm32();

if !body.trim().is_empty() {
let index = self.locals.tmp("index");

uwrite!(
self.src,
"
for (int {index} = 0; {index} < {length}; ++{index}) {{
int {base} = (int){address} + ({index} * {size});
{body}
}}
"
);
}

uwriteln!(
self.src,
r#"global::System.Runtime.InteropServices.NativeMemory.Free((void*){});"#,
operands[0]
);
}

Instruction::ErrorContextLower { .. }
| Instruction::ErrorContextLift { .. }
| Instruction::DropHandle { .. }
| Instruction::FixedLengthListLift { .. }
| Instruction::FixedLengthListLower { .. }
| Instruction::FixedLengthListLowerToMemory { .. }
| Instruction::FixedLengthListLiftFromMemory { .. }
| Instruction::MapLower { .. }
| Instruction::MapLift { .. }
| Instruction::IterMapKey { .. }
| Instruction::IterMapValue { .. }
| Instruction::GuestDeallocateMap { .. } => {
| Instruction::FixedLengthListLiftFromMemory { .. } => {
dbg!(inst);
todo!()
}
Expand Down Expand Up @@ -1843,6 +2020,8 @@ impl Bindgen for FunctionBindgen<'_, '_> {
body: mem::take(&mut self.src),
element: self.locals.tmp("element"),
base: self.locals.tmp("basePtr"),
map_key: self.locals.tmp("mapKey"),
map_value: self.locals.tmp("mapValue"),
});

self.is_block = true;
Expand All @@ -1853,13 +2032,17 @@ impl Bindgen for FunctionBindgen<'_, '_> {
body,
element,
base,
map_key,
map_value,
} = self.block_storage.pop().unwrap();

self.blocks.push(Block {
body: mem::replace(&mut self.src, body),
results: mem::take(operands),
element,
base,
map_key,
map_value,
});
self.is_block = false;
}
Expand Down Expand Up @@ -1940,6 +2123,8 @@ struct Block {
results: Vec<String>,
element: String,
base: String,
map_key: String,
map_value: String,
}

struct Fixed {
Expand All @@ -1951,6 +2136,8 @@ struct BlockStorage {
body: String,
element: String,
base: String,
map_key: String,
map_value: String,
}

#[derive(Clone)]
Expand Down
14 changes: 12 additions & 2 deletions crates/csharp/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ impl InterfaceGenerator<'_> {
TypeDefKind::Option(t) => self.type_option(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::Record(t) => self.type_record(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::List(t) => self.type_list(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::Map(key, value) => {
self.type_map(type_id, typedef_name, key, value, &type_def.docs)
}
TypeDefKind::Variant(t) => self.type_variant(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::Result(t) => self.type_result(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::Handle(_) => {
Expand Down Expand Up @@ -1297,6 +1300,13 @@ var {async_status_var} = {raw_name}({wasm_params});
};
return format!("StreamReader{generic_type}").to_owned();
}
TypeDefKind::Map(key, value) => {
format!(
"global::System.Collections.Generic.Dictionary<{}, {}>",
self.type_name_with_qualifier(key, qualifier),
self.type_name_with_qualifier(value, qualifier)
)
}
_ => {
if let Some(name) = &ty.name {
format!(
Expand Down Expand Up @@ -1916,8 +1926,8 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> {
todo!("named fixed-length list types are not yet supported in the C# backend")
}

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

fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {
Expand Down
8 changes: 0 additions & 8 deletions crates/csharp/src/world_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -825,14 +825,6 @@ impl WorldGenerator for CSharp {
);
}

// For the time being, we generate both a .wit file and a .o file to
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@yowl this got me foncused which is related to your question, I removed the .o file

Copy link
Copy Markdown
Contributor Author

@yordis yordis Apr 23, 2026

Choose a reason for hiding this comment

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

Should I put this comment back? Maybe I misunderstood, if so, my apologies

// represent the component type. Newer releases of the .NET runtime
// will be able to use the former, but older ones will need the
// latter.
//
// TODO: stop generating the .o file once a new-enough release is
// available for us to test using only the .wit file.

{
// When generating a WIT file, we first round-trip through the
// binary encoding. This has the effect of flattening any
Expand Down
1 change: 0 additions & 1 deletion crates/test/src/csharp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ impl LanguageMethods for Csharp {
| "import-export-resource.wit"
| "issue-1433.wit"
| "named-fixed-length-list.wit"
| "map.wit"
)
}

Expand Down
Loading