Skip to content

Commit acc9533

Browse files
authored
fix(c#): strings code generation test (#760)
* fix(c#): strings code generation test * Add runtime test for strings Signed-off-by: James Sturtevant <[email protected]>
1 parent b9d5d1e commit acc9533

File tree

3 files changed

+122
-32
lines changed

3 files changed

+122
-32
lines changed

crates/csharp/src/lib.rs

Lines changed: 97 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ impl WorldGenerator for CSharp {
147147
gen.import(&resolve.name_world_key(key), func);
148148
}
149149

150+
gen.add_import_return_area();
150151
gen.add_interface_fragment(false);
151152
}
152153

@@ -183,6 +184,7 @@ impl WorldGenerator for CSharp {
183184
gen.export(func, Some(key));
184185
}
185186

187+
gen.add_export_return_area();
186188
gen.add_interface_fragment(true);
187189
Ok(())
188190
}
@@ -591,6 +593,99 @@ impl InterfaceGenerator<'_> {
591593
});
592594
}
593595

596+
fn add_import_return_area(&mut self) {
597+
let mut ret_struct_type = String::new();
598+
if self.gen.return_area_size > 0 {
599+
uwrite!(
600+
ret_struct_type,
601+
r#"
602+
private unsafe struct ReturnArea
603+
{{
604+
private int GetS32(IntPtr ptr, int offset)
605+
{{
606+
var span = new Span<byte>((void*)ptr, {});
607+
608+
return BitConverter.ToInt32(span.Slice(offset, 4));
609+
}}
610+
611+
public string GetUTF8String(IntPtr ptr)
612+
{{
613+
return Encoding.UTF8.GetString((byte*)GetS32(ptr, 0), GetS32(ptr, 4));
614+
}}
615+
616+
}}
617+
618+
[ThreadStatic]
619+
[FixedAddressValueType]
620+
private static ReturnArea returnArea;
621+
"#,
622+
self.gen.return_area_size
623+
);
624+
}
625+
626+
uwrite!(
627+
self.csharp_interop_src,
628+
r#"
629+
{ret_struct_type}
630+
"#
631+
);
632+
}
633+
634+
fn add_export_return_area(&mut self) {
635+
// Declare a statically-allocated return area, if needed. We only do
636+
// this for export bindings, because import bindings allocate their
637+
// return-area on the stack.
638+
if self.gen.return_area_size > 0 {
639+
let mut ret_area_str = String::new();
640+
641+
uwrite!(
642+
ret_area_str,
643+
"
644+
[InlineArray({})]
645+
[StructLayout(LayoutKind.Sequential, Pack = {})]
646+
private struct ReturnArea
647+
{{
648+
private byte buffer;
649+
650+
private int GetS32(int offset)
651+
{{
652+
ReadOnlySpan<byte> span = this;
653+
654+
return BitConverter.ToInt32(span.Slice(offset, 4));
655+
}}
656+
657+
public void SetS32(int offset, int value)
658+
{{
659+
Span<byte> span = this;
660+
661+
BitConverter.TryWriteBytes(span.Slice(offset), value);
662+
}}
663+
664+
internal unsafe int AddrOfBuffer()
665+
{{
666+
fixed(byte* ptr = &buffer)
667+
{{
668+
return (int)ptr;
669+
}}
670+
}}
671+
672+
public unsafe string GetUTF8String(int p0, int p1)
673+
{{
674+
return Encoding.UTF8.GetString((byte*)p0, p1);
675+
}}
676+
}}
677+
678+
[ThreadStatic]
679+
private static ReturnArea returnArea = default;
680+
",
681+
self.gen.return_area_size,
682+
self.gen.return_area_align,
683+
);
684+
685+
self.csharp_interop_src.push_str(&ret_area_str);
686+
}
687+
}
688+
594689
fn add_world_fragment(self) {
595690
self.gen.world_fragments.push(InterfaceFragment {
596691
csharp_src: self.src,
@@ -677,39 +772,9 @@ impl InterfaceGenerator<'_> {
677772
"#
678773
);
679774

680-
let mut ret_struct_type = String::new();
681-
if self.gen.return_area_size > 0 {
682-
uwrite!(
683-
ret_struct_type,
684-
r#"
685-
private unsafe struct ReturnArea
686-
{{
687-
private int GetS32(IntPtr ptr, int offset)
688-
{{
689-
var span = new Span<byte>((void*)ptr, {});
690-
691-
return BitConverter.ToInt32(span.Slice(offset, 4));
692-
}}
693-
694-
public string GetUTF8String(IntPtr ptr)
695-
{{
696-
return Encoding.UTF8.GetString((byte*)GetS32(ptr, 0), GetS32(ptr, 4));
697-
}}
698-
699-
}}
700-
701-
[ThreadStatic]
702-
[FixedAddressValueType]
703-
private static ReturnArea returnArea;
704-
"#,
705-
self.gen.return_area_size
706-
);
707-
}
708-
709775
uwrite!(
710776
self.csharp_interop_src,
711777
r#"
712-
{ret_struct_type}
713778
internal static unsafe {result_type} {camel_name}({params})
714779
{{
715780
{src}
@@ -1460,7 +1525,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
14601525
self.src,
14611526
"
14621527
var {result_var} = {op};
1463-
IntPtr {interop_string} = InteropString.FromString({result_var}, out int length);"
1528+
IntPtr {interop_string} = InteropString.FromString({result_var}, out int length{result_var});"
14641529
);
14651530

14661531
//TODO: Oppertunity to optimize and not reallocate every call
@@ -1469,7 +1534,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
14691534
} else {
14701535
results.push(format!("{interop_string}.ToInt32()"));
14711536
}
1472-
results.push(format!("length"));
1537+
results.push(format!("length{result_var}"));
14731538

14741539
self.gen.gen.needs_interop_string = true;
14751540
}

crates/csharp/tests/codegen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ macro_rules! codegen_test {
5858
"simple-lists",
5959
"small-anonymous",
6060
"strings",
61+
"smoke-default",
6162
"unused-import",
6263
"use-across-interfaces",
6364
"variants",

tests/runtime/strings/wasm.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Diagnostics;
3+
using wit_strings.Wit.imports.test.strings.Imports;
4+
5+
namespace wit_strings;
6+
7+
public class StringsWorldImpl : StringsWorld
8+
{
9+
public static void TestImports()
10+
{
11+
ImportsInterop.TakeBasic("latin utf16");
12+
Debug.Assert(ImportsInterop.ReturnUnicode() == "🚀🚀🚀 𠈄𓀀");
13+
}
14+
15+
public static string ReturnEmpty()
16+
{
17+
return "";
18+
}
19+
20+
public static string Roundtrip(string s)
21+
{
22+
return s;
23+
}
24+
}

0 commit comments

Comments
 (0)