diff --git a/crates/csharp/src/function.rs b/crates/csharp/src/function.rs index 91f68c398..0c12ed8b9 100644 --- a/crates/csharp/src/function.rs +++ b/crates/csharp/src/function.rs @@ -1,5 +1,5 @@ use crate::csharp_ident::ToCSharpIdent; -use crate::interface::InterfaceGenerator; +use crate::interface::{InterfaceGenerator, ParameterType}; use crate::world_generator::CSharp; use heck::ToUpperCamelCase; use std::fmt::Write; @@ -26,6 +26,9 @@ pub(crate) struct FunctionBindgen<'a, 'b> { import_return_pointer_area_size: usize, import_return_pointer_area_align: usize, pub(crate) resource_drops: Vec<(String, String)>, + is_block: bool, + fixed_statments: Vec, + parameter_type: ParameterType, } impl<'a, 'b> FunctionBindgen<'a, 'b> { @@ -35,6 +38,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { kind: &'b FunctionKind, params: Box<[String]>, results: Vec, + parameter_type: ParameterType, ) -> FunctionBindgen<'a, 'b> { let mut locals = Ns::default(); // Ensure temporary variable names don't clash with parameter names: @@ -57,6 +61,9 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { import_return_pointer_area_size: 0, import_return_pointer_area_align: 0, resource_drops: Vec::new(), + is_block: false, + fixed_statments: Vec::new(), + parameter_type: parameter_type, } } @@ -497,12 +504,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(result); } Instruction::TupleLift { .. } => { - let mut result = String::from("("); - - uwriteln!(result, "{}", operands.join(", ")); - - result.push_str(")"); - results.push(result); + results.push(format!("({})", operands.join(", "))); } Instruction::TupleLower { tuple, ty: _ } => { @@ -722,19 +724,34 @@ impl Bindgen for FunctionBindgen<'_, '_> { Direction::Import => { let ptr: String = self.locals.tmp("listPtr"); let handle: String = self.locals.tmp("gcHandle"); - // Despite the name GCHandle.Alloc here this does not actually allocate memory on the heap. - // It pins the array with the garbage collector so that it can be passed to unmanaged code. - // It is required to free the pin after use which is done in the Cleanup section. - self.needs_cleanup = true; - uwrite!( - self.src, - " - var {handle} = GCHandle.Alloc({list}, GCHandleType.Pinned); - var {ptr} = {handle}.AddrOfPinnedObject(); - cleanups.Add(()=> {handle}.Free()); - " - ); - results.push(format!("{ptr}")); + + if !self.is_block && self.parameter_type == ParameterType::Span { + self.fixed_statments.push(Fixed { + item_to_pin: list.clone(), + ptr_name: ptr.clone(), + }); + }else if !self.is_block && self.parameter_type == ParameterType::Memory { + self.fixed_statments.push(Fixed { + item_to_pin: format!("{list}.Span"), + ptr_name: ptr.clone(), + }); + } else { + // With variants we can't use span since the Fixed statment can't always be applied to all the variants + // Despite the name GCHandle.Alloc here this does not re-allocate the object but it does make an + // allocation for the handle in a special resource pool which can result in GC pressure. + // It pins the array with the garbage collector so that it can be passed to unmanaged code. + // It is required to free the pin after use which is done in the Cleanup section. + self.needs_cleanup = true; + uwrite!( + self.src, + " + var {handle} = GCHandle.Alloc({list}, GCHandleType.Pinned); + var {ptr} = {handle}.AddrOfPinnedObject(); + cleanups.Add(()=> {handle}.Free()); + " + ); + } + results.push(format!("(nint){ptr}")); results.push(format!("({list}).Length")); } Direction::Export => { @@ -1005,11 +1022,18 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::Return { amt: _, func } => { + if self.fixed_statments.len() > 0 { + let fixed: String = self.fixed_statments.iter().map(|f| format!("{} = {}", f.ptr_name, f.item_to_pin)).collect::>().join(", "); + self.src.insert_str(0, &format!("fixed (void* {fixed}) + {{ + ")); + } + if self.needs_cleanup { self.src.insert_str(0, "var cleanups = new List(); "); - uwriteln!(self.src, "\ + uwriteln!(self.src, " foreach (var cleanup in cleanups) {{ cleanup(); @@ -1023,11 +1047,15 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.handle_result_import(operands); } _ => { - let results = operands.join(", "); + let results: String = operands.join(", "); uwriteln!(self.src, "return ({results});") } } } + + if self.fixed_statments.len() > 0 { + uwriteln!(self.src, "}}"); + } } Instruction::Malloc { .. } => unimplemented!(), @@ -1218,6 +1246,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { element: self.locals.tmp("element"), base: self.locals.tmp("basePtr"), }); + + self.is_block = true; } fn finish_block(&mut self, operands: &mut Vec) { @@ -1233,6 +1263,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { element, base, }); + self.is_block = false; } fn sizes(&self) -> &SizeAlign { @@ -1305,6 +1336,11 @@ struct Block { base: String, } +struct Fixed { + item_to_pin: String, + ptr_name: String, +} + struct BlockStorage { body: String, element: String, diff --git a/crates/csharp/src/interface.rs b/crates/csharp/src/interface.rs index d99bc2f74..56c1e91c8 100644 --- a/crates/csharp/src/interface.rs +++ b/crates/csharp/src/interface.rs @@ -247,6 +247,67 @@ impl InterfaceGenerator<'_> { .collect::>() .join(", "); + let mut funcs: Vec<(String, String)> = Vec::new(); + funcs.push(self.gen_import_src(func, &results, ParameterType::ABI)); + + let include_additional_functions = func + .params + .iter() + .skip(if let FunctionKind::Method(_) = &func.kind { + 1 + } else { + 0 + }) + .any(|param| self.is_primative_list(¶m.1)); + + if include_additional_functions { + funcs.push(self.gen_import_src(func, &results, ParameterType::Span)); + funcs.push(self.gen_import_src(func, &results, ParameterType::Memory)); + } + + let import_name = &func.name; + + self.csharp_gen + .require_using("System.Runtime.InteropServices"); + + let target = if let FunctionKind::Freestanding = &func.kind { + self.require_interop_using("System.Runtime.InteropServices"); + &mut self.csharp_interop_src + } else { + self.require_using("System.Runtime.InteropServices"); + &mut self.src + }; + + uwrite!( + target, + r#" + internal static class {interop_camel_name}WasmInterop + {{ + [DllImport("{import_module_name}", EntryPoint = "{import_name}"), WasmImportLinkage] + internal static extern {wasm_result_type} wasmImport{interop_camel_name}({wasm_params}); + }} + "# + ); + + for (src, params) in funcs { + uwrite!( + target, + r#" + {access} {extra_modifiers} {modifiers} unsafe {result_type} {camel_name}({params}) + {{ + {src} + }} + "# + ); + } + } + + fn gen_import_src( + &mut self, + func: &Function, + results: &Vec, + parameter_type: ParameterType, + ) -> (String, String) { let mut bindgen = FunctionBindgen::new( self, &func.item_name(), @@ -262,7 +323,8 @@ impl InterfaceGenerator<'_> { } }) .collect(), - results, + results.clone(), + parameter_type, ); abi::call( @@ -285,7 +347,7 @@ impl InterfaceGenerator<'_> { 0 }) .map(|param| { - let ty = self.type_name_with_qualifier(¶m.1, true); + let ty = self.name_with_qualifier(¶m.1, true, parameter_type); let param_name = ¶m.0; let param_name = param_name.to_csharp_ident(); format!("{ty} {param_name}") @@ -293,46 +355,7 @@ impl InterfaceGenerator<'_> { .collect::>() .join(", "); - let import_name = &func.name; - - self.csharp_gen - .require_using("System.Runtime.InteropServices"); - - let target = if let FunctionKind::Freestanding = &func.kind { - self.require_interop_using("System.Runtime.InteropServices"); - &mut self.csharp_interop_src - } else { - self.require_using("System.Runtime.InteropServices"); - &mut self.src - }; - - uwrite!( - target, - r#" - internal static class {interop_camel_name}WasmInterop - {{ - [DllImport("{import_module_name}", EntryPoint = "{import_name}"), WasmImportLinkage] - internal static extern {wasm_result_type} wasmImport{interop_camel_name}({wasm_params}); - "# - ); - - uwrite!( - target, - r#" - }} - "#, - ); - - uwrite!( - target, - r#" - {access} {extra_modifiers} {modifiers} unsafe {result_type} {camel_name}({params}) - {{ - {src} - //TODO: free alloc handle (interopString) if exists - }} - "# - ); + (src, params) } pub(crate) fn export(&mut self, func: &Function, interface_name: Option<&WorldKey>) { @@ -390,6 +413,7 @@ impl InterfaceGenerator<'_> { &func.kind, (0..sig.params.len()).map(|i| format!("p{i}")).collect(), results, + ParameterType::ABI, ); abi::call( @@ -518,6 +542,31 @@ impl InterfaceGenerator<'_> { } pub(crate) fn type_name_with_qualifier(&mut self, ty: &Type, qualifier: bool) -> String { + self.name_with_qualifier(ty, qualifier, ParameterType::ABI) + } + + fn is_primative_list(&mut self, ty: &Type) -> bool { + match ty { + Type::Id(id) => { + let ty = &self.resolve.types[*id]; + match &ty.kind { + TypeDefKind::Type(ty) => self.is_primative_list(ty), + TypeDefKind::List(ty) if crate::world_generator::is_primitive(ty) => { + return true + } + _ => false, + } + } + _ => false, + } + } + + pub(crate) fn name_with_qualifier( + &mut self, + ty: &Type, + qualifier: bool, + parameter_type: ParameterType, + ) -> String { match ty { Type::Bool => "bool".to_owned(), Type::U8 => "byte".to_owned(), @@ -535,9 +584,21 @@ impl InterfaceGenerator<'_> { Type::Id(id) => { let ty = &self.resolve.types[*id]; match &ty.kind { - TypeDefKind::Type(ty) => self.type_name_with_qualifier(ty, qualifier), + TypeDefKind::Type(ty) => { + self.name_with_qualifier(ty, qualifier, parameter_type) + } TypeDefKind::List(ty) => { - if crate::world_generator::is_primitive(ty) { + if crate::world_generator::is_primitive(ty) + && self.direction == Direction::Import + && parameter_type == ParameterType::Span + { + format!("Span<{}>", self.type_name(ty)) + } else if crate::world_generator::is_primitive(ty) + && self.direction == Direction::Import + && parameter_type == ParameterType::Memory + { + format!("Memory<{}>", self.type_name(ty)) + } else if crate::world_generator::is_primitive(ty) { format!("{}[]", self.type_name(ty)) } else { format!("List<{}>", self.type_name_with_qualifier(ty, qualifier)) @@ -1178,6 +1239,13 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> { } } +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub(crate) enum ParameterType { + ABI, + Span, + Memory, +} + fn payload_and_results( resolve: &Resolve, ty: Type, diff --git a/tests/runtime/lists/wasm.cs b/tests/runtime/lists/wasm.cs index fd929c7ba..8ec91f5bc 100644 --- a/tests/runtime/lists/wasm.cs +++ b/tests/runtime/lists/wasm.cs @@ -30,6 +30,8 @@ public static void TestImports() } TestInterop.ListParam(new byte[] { (byte)1, (byte)2, (byte)3, (byte)4 }); + TestInterop.ListParam((new byte[] { (byte)1, (byte)2, (byte)3, (byte)4 }).AsSpan()); + TestInterop.ListParam((new byte[] { (byte)1, (byte)2, (byte)3, (byte)4 }).AsMemory()); TestInterop.ListParam2("foo"); TestInterop.ListParam3(new List() { "foo",