Skip to content

Commit bcd136e

Browse files
authored
[c#] Fix GC hole in return areas (#945)
* uncomment to force crash * fix GC hole with return areas * remove vscode files * add comment remove sln from git * format * Close all fixed blocks * correct tmp variable name use * correct comment. * use nint for wit ptr
1 parent 66a3386 commit bcd136e

File tree

4 files changed

+177
-47
lines changed

4 files changed

+177
-47
lines changed

crates/csharp/src/RepTable.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ internal int Add(T v) {
3030
return rep;
3131
}
3232

33-
internal T Get(int rep) {
34-
if (list[rep] is Vacant) {
33+
internal T Get(nint rep) {
34+
if (list[(int)rep] is Vacant) {
3535
throw new ArgumentException("invalid rep");
3636
}
37-
return (T) list[rep];
37+
return (T) list[(int)rep];
3838
}
3939

40-
internal T Remove(int rep) {
40+
internal T Remove(nint rep) {
4141
var val = Get(rep);
42-
list[rep] = new Vacant(firstVacant);
43-
firstVacant = rep;
42+
list[(int)rep] = new Vacant(firstVacant);
43+
firstVacant = (int)rep;
4444
return (T) val;
4545
}
4646
}

crates/csharp/src/csproj.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ impl CSProjectLLVMBuilder {
8080
<ItemGroup>
8181
<NativeLibrary Include=\"{camel}_component_type.o\" />
8282
<NativeLibrary Include=\"$(MSBuildProjectDirectory)/{camel}_cabi_realloc.o\" />
83-
8483
</ItemGroup>
8584
8685
<ItemGroup>

crates/csharp/src/lib.rs

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,8 @@ impl WorldGenerator for CSharp {
519519
if self.needs_export_return_area {
520520
let mut ret_area_str = String::new();
521521

522+
let (array_size, element_type) =
523+
dotnet_aligned_array(self.return_area_size, self.return_area_align);
522524
uwrite!(
523525
ret_area_str,
524526
"
@@ -528,23 +530,22 @@ impl WorldGenerator for CSharp {
528530
[StructLayout(LayoutKind.Sequential, Pack = {1})]
529531
internal struct ReturnArea
530532
{{
531-
private byte buffer;
533+
private {2} buffer;
532534
533-
internal unsafe int AddressOfReturnArea()
535+
internal unsafe nint AddressOfReturnArea()
534536
{{
535-
fixed(byte* ptr = &buffer)
536-
{{
537-
return (int)ptr;
538-
}}
537+
return (nint)Unsafe.AsPointer(ref buffer);
539538
}}
540539
}}
541540
542541
[ThreadStatic]
542+
[FixedAddressValueType]
543543
internal static ReturnArea returnArea = default;
544544
}}
545545
",
546-
self.return_area_size,
546+
array_size,
547547
self.return_area_align,
548+
element_type
548549
);
549550

550551
src.push_str(&ret_area_str);
@@ -984,8 +985,6 @@ impl InterfaceGenerator<'_> {
984985
);
985986

986987
let src = bindgen.src;
987-
let import_return_pointer_area_size = bindgen.import_return_pointer_area_size;
988-
let import_return_pointer_area_align = bindgen.import_return_pointer_area_align;
989988

990989
let params = func
991990
.params
@@ -1022,28 +1021,6 @@ impl InterfaceGenerator<'_> {
10221021
"#
10231022
);
10241023

1025-
if import_return_pointer_area_size > 0 {
1026-
uwrite!(
1027-
target,
1028-
r#"
1029-
[InlineArray({import_return_pointer_area_size})]
1030-
[StructLayout(LayoutKind.Sequential, Pack = {import_return_pointer_area_align})]
1031-
internal struct ImportReturnArea
1032-
{{
1033-
private byte buffer;
1034-
1035-
internal unsafe int AddressOfReturnArea()
1036-
{{
1037-
fixed(byte* ptr = &buffer)
1038-
{{
1039-
return (int)ptr;
1040-
}}
1041-
}}
1042-
}}
1043-
"#,
1044-
)
1045-
}
1046-
10471024
uwrite!(
10481025
target,
10491026
r#"
@@ -1862,6 +1839,7 @@ struct FunctionBindgen<'a, 'b> {
18621839
cleanup: Vec<Cleanup>,
18631840
import_return_pointer_area_size: usize,
18641841
import_return_pointer_area_align: usize,
1842+
fixed: usize, // Number of `fixed` blocks that need to be closed.
18651843
resource_drops: Vec<(String, String)>,
18661844
}
18671845

@@ -1892,6 +1870,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
18921870
cleanup: Vec::new(),
18931871
import_return_pointer_area_size: 0,
18941872
import_return_pointer_area_align: 0,
1873+
fixed: 0,
18951874
resource_drops: Vec::new(),
18961875
}
18971876
}
@@ -2468,7 +2447,6 @@ impl Bindgen for FunctionBindgen<'_, '_> {
24682447

24692448
let list = &operands[0];
24702449
let size = self.gen.gen.sizes.size(element);
2471-
let _align = self.gen.gen.sizes.align(element);
24722450
let ty = self.gen.type_name_with_qualifier(element, true);
24732451
let index = self.locals.tmp("index");
24742452

@@ -2513,7 +2491,6 @@ impl Bindgen for FunctionBindgen<'_, '_> {
25132491
let array = self.locals.tmp("array");
25142492
let ty = self.gen.type_name_with_qualifier(element, true);
25152493
let size = self.gen.gen.sizes.size(element);
2516-
let _align = self.gen.gen.sizes.align(element);
25172494
let index = self.locals.tmp("index");
25182495

25192496
let result = match &block_results[..] {
@@ -2526,7 +2503,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
25262503
"
25272504
var {array} = new List<{ty}>({length});
25282505
for (int {index} = 0; {index} < {length}; ++{index}) {{
2529-
int {base} = {address} + ({index} * {size});
2506+
nint {base} = {address} + ({index} * {size});
25302507
{body}
25312508
{array}.Add({result});
25322509
}}
@@ -2651,6 +2628,11 @@ impl Bindgen for FunctionBindgen<'_, '_> {
26512628
uwriteln!(self.src, "return ({results});")
26522629
}
26532630
}
2631+
2632+
// Close all the fixed blocks.
2633+
for _ in 0..self.fixed {
2634+
uwriteln!(self.src, "}}");
2635+
}
26542636
}
26552637
}
26562638

@@ -2780,19 +2762,30 @@ impl Bindgen for FunctionBindgen<'_, '_> {
27802762
// their return area to be live until the post-return call.
27812763
match self.gen.direction {
27822764
Direction::Import => {
2783-
let ret_area = self.locals.tmp("retArea");
2784-
let name = self.func_name.to_upper_camel_case();
27852765
self.import_return_pointer_area_size =
27862766
self.import_return_pointer_area_size.max(size);
27872767
self.import_return_pointer_area_align =
27882768
self.import_return_pointer_area_align.max(align);
2769+
let (array_size, element_type) = dotnet_aligned_array(
2770+
self.import_return_pointer_area_size,
2771+
self.import_return_pointer_area_align,
2772+
);
2773+
let ret_area = self.locals.tmp("retArea");
2774+
let ret_area_byte0 = self.locals.tmp("retAreaByte0");
27892775
uwrite!(
27902776
self.src,
27912777
"
2792-
var {ret_area} = new {name}WasmInterop.ImportReturnArea();
2793-
var {ptr} = {ret_area}.AddressOfReturnArea();
2794-
"
2778+
var {2} = new {0}[{1}];
2779+
fixed ({0}* {3} = &{2}[0])
2780+
{{
2781+
var {ptr} = (nint){3};
2782+
",
2783+
element_type,
2784+
array_size,
2785+
ret_area,
2786+
ret_area_byte0
27952787
);
2788+
self.fixed = self.fixed + 1;
27962789

27972790
return format!("{ptr}");
27982791
}
@@ -2857,6 +2850,26 @@ impl Bindgen for FunctionBindgen<'_, '_> {
28572850
}
28582851
}
28592852

2853+
// We cant use "StructLayout.Pack" as dotnet will use the minimum of the type and the "Pack" field,
2854+
// so for byte it would always use 1 regardless of the "Pack".
2855+
fn dotnet_aligned_array(array_size: usize, required_alignment: usize) -> (usize, String) {
2856+
match required_alignment {
2857+
1 => {
2858+
return (array_size, "byte".to_owned());
2859+
}
2860+
2 => {
2861+
return ((array_size + 1) / 2, "ushort".to_owned());
2862+
}
2863+
4 => {
2864+
return ((array_size + 3) / 4, "uint".to_owned());
2865+
}
2866+
8 => {
2867+
return ((array_size + 7) / 8, "ulong".to_owned());
2868+
}
2869+
_ => todo!("unsupported return_area_align {}", required_alignment),
2870+
}
2871+
}
2872+
28602873
fn perform_cast(op: &String, cast: &Bitcast) -> String {
28612874
match cast {
28622875
Bitcast::I32ToF32 => format!("BitConverter.Int32BitsToSingle({op})"),
@@ -2900,7 +2913,7 @@ fn wasm_type(ty: WasmType) -> &'static str {
29002913
WasmType::I64 => "long",
29012914
WasmType::F32 => "float",
29022915
WasmType::F64 => "double",
2903-
WasmType::Pointer => "int",
2916+
WasmType::Pointer => "nint",
29042917
WasmType::PointerOrI64 => "long",
29052918
WasmType::Length => "int",
29062919
}

tests/runtime/variants/wasm.cs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
using System.Diagnostics;
4+
using VariantsWorld.wit.imports.test.variants;
5+
6+
namespace VariantsWorld
7+
{
8+
9+
public class VariantsWorldImpl : IVariantsWorld
10+
{
11+
public static void TestImports()
12+
{
13+
Debug.Assert(TestInterop.RoundtripOption(new Option<float>(1.0f)).Value == 1);
14+
Debug.Assert(TestInterop.RoundtripOption(Option<float>.None).HasValue == false);
15+
Debug.Assert(TestInterop.RoundtripOption(new Option<float>(2.0f)).Value == 2);
16+
17+
Debug.Assert(TestInterop.RoundtripResult(Result<uint, float>.ok(2)).AsOk == 2.0);
18+
Debug.Assert(TestInterop.RoundtripResult(Result<uint, float>.ok(4)).AsOk == 4.0);
19+
Debug.Assert(TestInterop.RoundtripResult(Result<uint, float>.err(5.3f)).AsErr == 5);
20+
21+
Debug.Assert(TestInterop.RoundtripEnum(ITest.E1.A) == ITest.E1.A);
22+
Debug.Assert(TestInterop.RoundtripEnum(ITest.E1.B) == ITest.E1.B);
23+
24+
Debug.Assert(TestInterop.InvertBool(true) == false);
25+
Debug.Assert(TestInterop.InvertBool(false) == true);
26+
27+
var (a1, a2, a3, a4, a5, a6) =
28+
TestInterop.VariantCasts((ITest.C1.a(1), ITest.C2.a(2), ITest.C3.a(3), ITest.C4.a(4), ITest.C5.a(5), ITest.C6.a(6.0f)));
29+
Debug.Assert(a1.AsA == 1);
30+
Debug.Assert(a2.AsA == 2);
31+
Debug.Assert(a3.AsA == 3);
32+
Debug.Assert(a4.AsA == 4);
33+
Debug.Assert(a5.AsA == 5);
34+
Debug.Assert(a6.AsA == 6.0f);
35+
36+
var (b1, b2, b3, b4, b5, b6) =
37+
TestInterop.VariantCasts((ITest.C1.b(1), ITest.C2.b(2), ITest.C3.b(3), ITest.C4.b(4), ITest.C5.b(5), ITest.C6.b(6.0)));
38+
Debug.Assert(b1.AsB == 1);
39+
Debug.Assert(b2.AsB == 2.0f);
40+
Debug.Assert(b3.AsB == 3.0f);
41+
Debug.Assert(b4.AsB == 4.0f);
42+
Debug.Assert(b5.AsB == 5.0f);
43+
Debug.Assert(b6.AsB == 6.0);
44+
45+
var (za1, za2, za3, za4) =
46+
TestInterop.VariantZeros((ITest.Z1.a(1), ITest.Z2.a(2), ITest.Z3.a(3.0f), ITest.Z4.a(4.0f)));
47+
Debug.Assert(za1.AsA == 1);
48+
Debug.Assert(za2.AsA == 2);
49+
Debug.Assert(za3.AsA == 3.0f);
50+
Debug.Assert(za4.AsA == 4.0f);
51+
52+
var (zb1, zb2, zb3, zb4) =
53+
TestInterop.VariantZeros((ITest.Z1.b(), ITest.Z2.b(), ITest.Z3.b(), ITest.Z4.b()));
54+
//TODO: Add comparison operator to variants and None
55+
//Debug.Assert(zb1.AsB == ITest.Z1.b());
56+
//Debug.Assert(zb2.AsB == ITest.Z2.b());
57+
//Debug.Assert(zb3.AsB == ITest.Z3.b());
58+
//Debug.Assert(zb4.AsB == ITest.Z4.b());
59+
60+
TestInterop.VariantTypedefs(Option<uint>.None, false, Result<uint, None>.err(new None()));
61+
62+
var (a, b, c) = TestInterop.VariantEnums(true, Result<None, None>.ok(new None()), ITest.MyErrno.SUCCESS);
63+
Debug.Assert(a == false);
64+
var test = b.AsErr;
65+
Debug.Assert(c == ITest.MyErrno.A);
66+
}
67+
}
68+
}
69+
70+
namespace VariantsWorld.wit.exports.test.variants
71+
{
72+
public class TestImpl : ITest
73+
{
74+
public static Option<byte> RoundtripOption(Option<float> a)
75+
{
76+
return a.HasValue ? new Option<byte>((byte)a.Value) : Option<byte>.None;
77+
}
78+
79+
public static Result<double, byte> RoundtripResult(Result<uint, float> a)
80+
{
81+
switch (a.Tag)
82+
{
83+
case Result<double, byte>.OK: return Result<double, byte>.ok((double)a.AsOk);
84+
case Result<double, byte>.ERR: return Result<double, byte>.err((byte)a.AsErr);
85+
default: throw new ArgumentException();
86+
}
87+
}
88+
89+
public static ITest.E1 RoundtripEnum(ITest.E1 a)
90+
{
91+
return a;
92+
}
93+
94+
public static bool InvertBool(bool a)
95+
{
96+
return !a;
97+
}
98+
99+
public static (ITest.C1, ITest.C2, ITest.C3, ITest.C4, ITest.C5, ITest.C6)
100+
VariantCasts((ITest.C1, ITest.C2, ITest.C3, ITest.C4, ITest.C5, ITest.C6) a)
101+
{
102+
return a;
103+
}
104+
105+
public static (bool, Result<None, None>, ITest.MyErrno)
106+
VariantEnums(bool a, Result<None, None> b, ITest.MyErrno c)
107+
{
108+
return new(a, b, c);
109+
}
110+
111+
public static void VariantTypedefs(Option<uint> a, bool b, Result<uint, None> c) { }
112+
113+
public static (ITest.Z1, ITest.Z2, ITest.Z3, ITest.Z4) VariantZeros((ITest.Z1, ITest.Z2, ITest.Z3, ITest.Z4) a)
114+
{
115+
return a;
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)