diff --git a/crates/csharp/src/lib.rs b/crates/csharp/src/lib.rs index 5910abac0..21893d1ef 100644 --- a/crates/csharp/src/lib.rs +++ b/crates/csharp/src/lib.rs @@ -145,6 +145,7 @@ impl WorldGenerator for CSharp { gen.import(&resolve.name_world_key(key), func); } + gen.add_import_return_area(); gen.add_interface_fragment(false); } @@ -181,6 +182,7 @@ impl WorldGenerator for CSharp { gen.export(func, Some(key)); } + gen.add_export_return_area(); gen.add_interface_fragment(true); Ok(()) } @@ -589,6 +591,99 @@ impl InterfaceGenerator<'_> { }); } + fn add_import_return_area(&mut self) { + let mut ret_struct_type = String::new(); + if self.gen.return_area_size > 0 { + uwrite!( + ret_struct_type, + r#" + private unsafe struct ReturnArea + {{ + private int GetS32(IntPtr ptr, int offset) + {{ + var span = new Span((void*)ptr, {}); + + return BitConverter.ToInt32(span.Slice(offset, 4)); + }} + + public string GetUTF8String(IntPtr ptr) + {{ + return Encoding.UTF8.GetString((byte*)GetS32(ptr, 0), GetS32(ptr, 4)); + }} + + }} + + [ThreadStatic] + [FixedAddressValueType] + private static ReturnArea returnArea; + "#, + self.gen.return_area_size + ); + } + + uwrite!( + self.csharp_interop_src, + r#" + {ret_struct_type} + "# + ); + } + + fn add_export_return_area(&mut self) { + // Declare a statically-allocated return area, if needed. We only do + // this for export bindings, because import bindings allocate their + // return-area on the stack. + if self.gen.return_area_size > 0 { + let mut ret_area_str = String::new(); + + uwrite!( + ret_area_str, + " + [InlineArray({})] + [StructLayout(LayoutKind.Sequential, Pack = {})] + private struct ReturnArea + {{ + private byte buffer; + + private int GetS32(int offset) + {{ + ReadOnlySpan span = this; + + return BitConverter.ToInt32(span.Slice(offset, 4)); + }} + + public void SetS32(int offset, int value) + {{ + Span span = this; + + BitConverter.TryWriteBytes(span.Slice(offset), value); + }} + + internal unsafe int AddrOfBuffer() + {{ + fixed(byte* ptr = &buffer) + {{ + return (int)ptr; + }} + }} + + public unsafe string GetUTF8String(int p0, int p1) + {{ + return Encoding.UTF8.GetString((byte*)p0, p1); + }} + }} + + [ThreadStatic] + private static ReturnArea returnArea = default; + ", + self.gen.return_area_size, + self.gen.return_area_align, + ); + + self.csharp_interop_src.push_str(&ret_area_str); + } + } + fn add_world_fragment(self) { self.gen.world_fragments.push(InterfaceFragment { csharp_src: self.src, @@ -675,39 +770,9 @@ impl InterfaceGenerator<'_> { "# ); - let mut ret_struct_type = String::new(); - if self.gen.return_area_size > 0 { - uwrite!( - ret_struct_type, - r#" - private unsafe struct ReturnArea - {{ - private int GetS32(IntPtr ptr, int offset) - {{ - var span = new Span((void*)ptr, {}); - - return BitConverter.ToInt32(span.Slice(offset, 4)); - }} - - public string GetUTF8String(IntPtr ptr) - {{ - return Encoding.UTF8.GetString((byte*)GetS32(ptr, 0), GetS32(ptr, 4)); - }} - - }} - - [ThreadStatic] - [FixedAddressValueType] - private static ReturnArea returnArea; - "#, - self.gen.return_area_size - ); - } - uwrite!( self.csharp_interop_src, r#" - {ret_struct_type} internal static unsafe {result_type} {camel_name}({params}) {{ {src} @@ -1458,7 +1523,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.src, " var {result_var} = {op}; - IntPtr {interop_string} = InteropString.FromString({result_var}, out int length);" + IntPtr {interop_string} = InteropString.FromString({result_var}, out int length{result_var});" ); //TODO: Oppertunity to optimize and not reallocate every call @@ -1467,7 +1532,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } else { results.push(format!("{interop_string}.ToInt32()")); } - results.push(format!("length")); + results.push(format!("length{result_var}")); self.gen.gen.needs_interop_string = true; } diff --git a/crates/csharp/tests/codegen.rs b/crates/csharp/tests/codegen.rs index c44e53a3b..55cb974da 100644 --- a/crates/csharp/tests/codegen.rs +++ b/crates/csharp/tests/codegen.rs @@ -58,6 +58,7 @@ macro_rules! codegen_test { "simple-lists", "small-anonymous", "strings", + "smoke-default", "unused-import", "use-across-interfaces", "variants", diff --git a/tests/runtime/strings/wasm.cs b/tests/runtime/strings/wasm.cs new file mode 100644 index 000000000..36288c54a --- /dev/null +++ b/tests/runtime/strings/wasm.cs @@ -0,0 +1,24 @@ +using System; +using System.Diagnostics; +using wit_strings.Wit.imports.test.strings.Imports; + +namespace wit_strings; + +public class StringsWorldImpl : StringsWorld +{ + public static void TestImports() + { + ImportsInterop.TakeBasic("latin utf16"); + Debug.Assert(ImportsInterop.ReturnUnicode() == "🚀🚀🚀 𠈄𓀀"); + } + + public static string ReturnEmpty() + { + return ""; + } + + public static string Roundtrip(string s) + { + return s; + } +} \ No newline at end of file