diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index c35f00cb1..9de5852cf 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -1017,6 +1017,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results: block_results, element: block_element, base, + .. } = self.blocks.pop().unwrap(); assert!(block_results.is_empty()); @@ -1491,6 +1492,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results: block_results, base, element: _, + .. } = self.blocks.pop().unwrap(); assert!(block_results.is_empty()); @@ -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!() } @@ -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; @@ -1853,6 +2032,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { body, element, base, + map_key, + map_value, } = self.block_storage.pop().unwrap(); self.blocks.push(Block { @@ -1860,6 +2041,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { results: mem::take(operands), element, base, + map_key, + map_value, }); self.is_block = false; } @@ -1940,6 +2123,8 @@ struct Block { results: Vec, element: String, base: String, + map_key: String, + map_value: String, } struct Fixed { @@ -1951,6 +2136,8 @@ struct BlockStorage { body: String, element: String, base: String, + map_key: String, + map_value: String, } #[derive(Clone)] diff --git a/crates/csharp/src/interface.rs b/crates/csharp/src/interface.rs index bc76aff9d..742b3d1ca 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -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(_) => { @@ -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!( @@ -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) { diff --git a/crates/csharp/src/world_generator.rs b/crates/csharp/src/world_generator.rs index 7199b66fd..685c69076 100644 --- a/crates/csharp/src/world_generator.rs +++ b/crates/csharp/src/world_generator.rs @@ -825,14 +825,6 @@ impl WorldGenerator for CSharp { ); } - // For the time being, we generate both a .wit file and a .o file to - // 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 diff --git a/crates/test/src/csharp.rs b/crates/test/src/csharp.rs index a4d017e18..8e11ca7f7 100644 --- a/crates/test/src/csharp.rs +++ b/crates/test/src/csharp.rs @@ -49,7 +49,6 @@ impl LanguageMethods for Csharp { | "import-export-resource.wit" | "issue-1433.wit" | "named-fixed-length-list.wit" - | "map.wit" ) }