Skip to content

Commit cd5e771

Browse files
authored
c#: Handle Cabi realloc post return (#1145)
* wire in post return abi * Deallocate allocs on exports Signed-off-by: James Sturtevant <jsturtevant@gmail.com> * Fix cleanup and rebase Signed-off-by: James Sturtevant <jsturtevant@gmail.com> * Add a large string array test Signed-off-by: James Sturtevant <jsturtevant@gmail.com> * borrow the list Signed-off-by: James Sturtevant <jsturtevant@gmail.com> * Fix language tests Signed-off-by: James Sturtevant <jsturtevant@gmail.com> --------- Signed-off-by: James Sturtevant <jsturtevant@gmail.com>
1 parent 58897bf commit cd5e771

File tree

9 files changed

+201
-57
lines changed

9 files changed

+201
-57
lines changed

crates/csharp/src/function.rs

Lines changed: 126 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
718718
// );
719719
}
720720

721-
Instruction::ListCanonLower { element, realloc } => {
721+
Instruction::ListCanonLower { element, .. } => {
722722
let list: &String = &operands[0];
723723
match self.interface_gen.direction {
724724
Direction::Import => {
@@ -755,29 +755,20 @@ impl Bindgen for FunctionBindgen<'_, '_> {
755755
results.push(format!("({list}).Length"));
756756
}
757757
Direction::Export => {
758+
let (_, ty) = list_element_info(element);
758759
let address = self.locals.tmp("address");
759-
let buffer = self.locals.tmp("buffer");
760-
let gc_handle = self.locals.tmp("gcHandle");
761760
let size = self.interface_gen.csharp_gen.sizes.size(element).size_wasm32();
761+
let byte_length = self.locals.tmp("byteLength");
762762
uwrite!(
763763
self.src,
764764
"
765-
byte[] {buffer} = new byte[({size}) * {list}.Length];
766-
Buffer.BlockCopy({list}.ToArray(), 0, {buffer}, 0, ({size}) * {list}.Length);
767-
var {gc_handle} = GCHandle.Alloc({buffer}, GCHandleType.Pinned);
768-
var {address} = {gc_handle}.AddrOfPinnedObject();
765+
var {byte_length} = ({size}) * {list}.Length;
766+
var {address} = NativeMemory.Alloc((nuint)({byte_length}));
767+
{list}.AsSpan().CopyTo(new Span<{ty}>({address},{byte_length}));
769768
"
770769
);
771770

772-
if realloc.is_none() {
773-
self.needs_cleanup = true;
774-
uwrite!(
775-
self.src,
776-
"
777-
cleanups.Add(()=> {gc_handle}.Free());
778-
");
779-
}
780-
results.push(format!("((IntPtr)({address})).ToInt32()"));
771+
results.push(format!("(int)({address})"));
781772
results.push(format!("{list}.Length"));
782773
}
783774
}
@@ -802,33 +793,45 @@ impl Bindgen for FunctionBindgen<'_, '_> {
802793

803794
Instruction::StringLower { realloc } => {
804795
let op = &operands[0];
805-
let interop_string = self.locals.tmp("interopString");
796+
let str_ptr = self.locals.tmp("strPtr");
806797
let utf8_bytes = self.locals.tmp("utf8Bytes");
807798
let length = self.locals.tmp("length");
808799
let gc_handle = self.locals.tmp("gcHandle");
809-
uwriteln!(
810-
self.src,
811-
"
812-
var {utf8_bytes} = Encoding.UTF8.GetBytes({op});
813-
var {length} = {utf8_bytes}.Length;
814-
var {gc_handle} = GCHandle.Alloc({utf8_bytes}, GCHandleType.Pinned);
815-
var {interop_string} = {gc_handle}.AddrOfPinnedObject();
816-
"
817-
);
818800

819801
if realloc.is_none() {
820-
results.push(format!("{interop_string}.ToInt32()"));
802+
uwriteln!(
803+
self.src,
804+
"
805+
var {utf8_bytes} = Encoding.UTF8.GetBytes({op});
806+
var {length} = {utf8_bytes}.Length;
807+
var {gc_handle} = GCHandle.Alloc({utf8_bytes}, GCHandleType.Pinned);
808+
var {str_ptr} = {gc_handle}.AddrOfPinnedObject();
809+
"
810+
);
811+
821812
self.needs_cleanup = true;
822813
uwrite!(
823814
self.src,
824815
"
825816
cleanups.Add(()=> {gc_handle}.Free());
826-
");
817+
"
818+
);
819+
results.push(format!("{str_ptr}.ToInt32()"));
827820
} else {
828-
results.push(format!("{interop_string}.ToInt32()"));
821+
let string_span = self.locals.tmp("stringSpan");
822+
uwriteln!(
823+
self.src,
824+
"
825+
var {string_span} = {op}.AsSpan();
826+
var {length} = Encoding.UTF8.GetByteCount({string_span});
827+
var {str_ptr} = NativeMemory.Alloc((nuint){length});
828+
Encoding.UTF8.GetBytes({string_span}, new Span<byte>({str_ptr}, {length}));
829+
"
830+
);
831+
results.push(format!("(int){str_ptr}"));
829832
}
830-
results.push(format!("{length}"));
831833

834+
results.push(format!("{length}"));
832835
if FunctionKind::Freestanding == *self.kind || self.interface_gen.direction == Direction::Export {
833836
self.interface_gen.require_interop_using("System.Text");
834837
self.interface_gen.require_interop_using("System.Runtime.InteropServices");
@@ -851,7 +854,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
851854
));
852855
}
853856

854-
Instruction::ListLower { element, .. } => {
857+
Instruction::ListLower { element, realloc } => {
855858
let Block {
856859
body,
857860
results: block_results,
@@ -876,22 +879,38 @@ impl Bindgen for FunctionBindgen<'_, '_> {
876879
);
877880
let ret_area = self.locals.tmp("retArea");
878881

879-
self.needs_cleanup = true;
880-
uwrite!(
881-
self.src,
882-
"
883-
void* {address};
884-
if (({size} * {list}.Count) < 1024) {{
885-
var {ret_area} = stackalloc {element_type}[({array_size}*{list}.Count)+1];
886-
{address} = (void*)(((int){ret_area}) + ({align} - 1) & -{align});
887-
}}
888-
else
889-
{{
890-
var {buffer_size} = {size} * (nuint){list}.Count;
891-
{address} = NativeMemory.AlignedAlloc({buffer_size}, {align});
892-
cleanups.Add(()=> NativeMemory.AlignedFree({address}));
893-
}}
882+
match realloc {
883+
None => {
884+
self.needs_cleanup = true;
885+
uwrite!(self.src,
886+
"
887+
void* {address};
888+
if (({size} * {list}.Count) < 1024) {{
889+
var {ret_area} = stackalloc {element_type}[({array_size}*{list}.Count)+1];
890+
{address} = (void*)(((int){ret_area}) + ({align} - 1) & -{align});
891+
}}
892+
else
893+
{{
894+
var {buffer_size} = {size} * (nuint){list}.Count;
895+
{address} = NativeMemory.AlignedAlloc({buffer_size}, {align});
896+
cleanups.Add(() => NativeMemory.AlignedFree({address}));
897+
}}
898+
"
899+
);
900+
}
901+
Some(_) => {
902+
//cabi_realloc_post_return will be called to clean up this allocation
903+
uwrite!(self.src,
904+
"
905+
var {buffer_size} = {size} * (nuint){list}.Count;
906+
void* {address} = NativeMemory.AlignedAlloc({buffer_size}, {align});
907+
"
908+
);
909+
}
910+
}
894911

912+
uwrite!(self.src,
913+
"
895914
for (int {index} = 0; {index} < {list}.Count; ++{index}) {{
896915
{ty} {block_element} = {list}[{index}];
897916
int {base} = (int){address} + ({index} * {size});
@@ -1035,7 +1054,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10351054
}
10361055
}
10371056

1038-
Instruction::Return { amt: _, func } => {
1057+
Instruction::Return { amt, .. } => {
10391058
if self.fixed_statments.len() > 0 {
10401059
let fixed: String = self.fixed_statments.iter().map(|f| format!("{} = {}", f.ptr_name, f.item_to_pin)).collect::<Vec<_>>().join(", ");
10411060
self.src.insert_str(0, &format!("fixed (void* {fixed})
@@ -1055,7 +1074,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10551074
}
10561075

10571076
if !matches!((self.interface_gen.direction, self.kind), (Direction::Import, FunctionKind::Constructor(_))) {
1058-
match func.results.len() {
1077+
match *amt {
10591078
0 => (),
10601079
1 => {
10611080
self.handle_result_import(operands);
@@ -1075,19 +1094,72 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10751094
Instruction::Malloc { .. } => unimplemented!(),
10761095

10771096
Instruction::GuestDeallocate { .. } => {
1078-
uwriteln!(self.src, r#"Console.WriteLine("TODO: deallocate buffer for indirect parameters");"#);
1097+
// the original alloc here comes from cabi_realloc implementation (wasi-libc in .net)
1098+
uwriteln!(self.src, r#"NativeMemory.Free((void*){});"#, operands[0]);
10791099
}
10801100

10811101
Instruction::GuestDeallocateString => {
1082-
uwriteln!(self.src, r#"Console.WriteLine("TODO: deallocate buffer for string");"#);
1102+
uwriteln!(self.src, r#"NativeMemory.Free((void*){});"#, operands[0]);
10831103
}
10841104

1085-
Instruction::GuestDeallocateVariant { .. } => {
1086-
uwriteln!(self.src, r#"Console.WriteLine("TODO: deallocate buffer for variant");"#);
1105+
Instruction::GuestDeallocateVariant { blocks } => {
1106+
let cases = self
1107+
.blocks
1108+
.drain(self.blocks.len() - blocks..)
1109+
.enumerate()
1110+
.map(|(i, Block { body, results, .. })| {
1111+
assert!(results.is_empty());
1112+
1113+
format!(
1114+
"case {i}: {{
1115+
{body}
1116+
break;
1117+
}}"
1118+
)
1119+
})
1120+
.collect::<Vec<_>>()
1121+
.join("\n");
1122+
1123+
let op = &operands[0];
1124+
1125+
uwrite!(
1126+
self.src,
1127+
"
1128+
switch ({op}) {{
1129+
{cases}
1130+
}}
1131+
"
1132+
);
10871133
}
10881134

1089-
Instruction::GuestDeallocateList { .. } => {
1090-
uwriteln!(self.src, r#"Console.WriteLine("TODO: deallocate buffer for list");"#);
1135+
Instruction::GuestDeallocateList { element: element_type } => {
1136+
let Block {
1137+
body,
1138+
results: block_results,
1139+
base,
1140+
element: _,
1141+
} = self.blocks.pop().unwrap();
1142+
assert!(block_results.is_empty());
1143+
1144+
let address = &operands[0];
1145+
let length = &operands[1];
1146+
let size = self.interface_gen.csharp_gen.sizes.size(element_type).size_wasm32();
1147+
1148+
if !body.trim().is_empty() {
1149+
let index = self.locals.tmp("index");
1150+
1151+
uwrite!(
1152+
self.src,
1153+
"
1154+
for (int {index} = 0; {index} < {length}; ++{index}) {{
1155+
int {base} = (int){address} + ({index} * {size});
1156+
{body}
1157+
}}
1158+
"
1159+
);
1160+
}
1161+
1162+
uwriteln!(self.src, r#"NativeMemory.Free((void*){});"#, operands[0]);
10911163
}
10921164

10931165
Instruction::HandleLower {

crates/csharp/src/interface.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -484,13 +484,37 @@ impl InterfaceGenerator<'_> {
484484
"#
485485
);
486486

487-
if !sig.results.is_empty() {
487+
if abi::guest_export_needs_post_return(self.resolve, func) {
488+
let params = sig
489+
.results
490+
.iter()
491+
.enumerate()
492+
.map(|(i, param)| {
493+
let ty = crate::world_generator::wasm_type(*param);
494+
format!("{ty} p{i}")
495+
})
496+
.collect::<Vec<_>>()
497+
.join(", ");
498+
499+
let mut bindgen = FunctionBindgen::new(
500+
self,
501+
"INVALID",
502+
&func.kind,
503+
(0..sig.results.len()).map(|i| format!("p{i}")).collect(),
504+
Vec::new(),
505+
ParameterType::ABI,
506+
);
507+
508+
abi::post_return(bindgen.interface_gen.resolve, func, &mut bindgen, false);
509+
510+
let src = bindgen.src;
511+
488512
uwrite!(
489513
self.csharp_interop_src,
490514
r#"
491515
[UnmanagedCallersOnly(EntryPoint = "cabi_post_{export_name}")]
492-
{access} static void cabi_post_{interop_name}({wasm_result_type} returnValue) {{
493-
Console.WriteLine("TODO: cabi_post_{export_name}");
516+
{access} static unsafe void cabi_post_{interop_name}({params}) {{
517+
{src}
494518
}}
495519
"#
496520
);

tests/runtime/lists.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ impl test::lists::test::Host for MyImports {
5151
assert_eq!(ptr, [(1, 2, 3), (4, 5, 6)]);
5252
}
5353

54+
fn list_param_large(&mut self, ptr: Vec<String>) {
55+
assert_eq!(ptr.len(), 1000);
56+
}
57+
5458
fn list_result(&mut self) -> Vec<u8> {
5559
vec![1, 2, 3, 4, 5]
5660
}
@@ -133,6 +137,8 @@ fn run_test(lists: Lists, store: &mut Store<crate::Wasi<MyImports>>) -> Result<(
133137
vec!["baz".to_owned()],
134138
],
135139
)?;
140+
let arg0: Vec<String> = (0..1000).map(|_| "string".to_string()).collect();
141+
exports.call_list_param_large(&mut *store, &arg0)?;
136142
assert_eq!(exports.call_list_result(&mut *store)?, [1, 2, 3, 4, 5]);
137143
assert_eq!(exports.call_list_result2(&mut *store)?, "hello!");
138144
assert_eq!(

tests/runtime/lists/wasm.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,11 @@ void exports_test_lists_test_list_param4(lists_list_list_string_t *a) {
315315
lists_list_list_string_free(a);
316316
}
317317

318+
void exports_test_lists_test_list_param_large(lists_list_string_t *a) {
319+
assert(a->len == 1000);
320+
lists_list_string_free(a);
321+
}
322+
318323
void exports_test_lists_test_list_param5(lists_list_tuple3_u8_u32_u8_t *a) {
319324
assert(a->len == 2);
320325
assert(a->ptr[0].f0 == 1);

tests/runtime/lists/wasm.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ public static void TestImports()
4949
}
5050
});
5151

52+
List<string> randomStrings = new List<string>();
53+
for (int i = 0; i < 1000; i++)
54+
{
55+
randomStrings.Add(Guid.NewGuid().ToString());
56+
}
57+
TestInterop.ListParamLarge(randomStrings);
58+
5259
{
5360
byte[] result = TestInterop.ListResult();
5461
Debug.Assert(result.Length == 5);
@@ -233,6 +240,11 @@ public static void ListParam5(List<(byte, uint, byte)> a)
233240
Debug.Assert(a[1].Item3 == 6);
234241
}
235242

243+
public static void ListParamLarge(List<String> a)
244+
{
245+
Debug.Assert(a.Count() == 1000);
246+
}
247+
236248
public static byte[] ListResult()
237249
{
238250
return new byte[] { (byte)1, (byte)2, (byte)3, (byte)4, (byte)5 };

tests/runtime/lists/wasm.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package main
22

33
import (
44
"math"
5+
"math/rand"
6+
"strconv"
57
. "wit_lists_go/gen"
68
)
79

@@ -29,6 +31,12 @@ func (i ListImpl) TestImports() {
2931
TestListsTestListParam2("foo")
3032
TestListsTestListParam3([]string{"foo", "bar", "baz"})
3133
TestListsTestListParam4([][]string{{"foo", "bar"}, {"baz"}})
34+
35+
randomStrings := make([]string, 1000)
36+
for i := 0; i < 1000; i++ {
37+
randomStrings[i] = "str" + strconv.Itoa(rand.Intn(1000))
38+
}
39+
TestListsTestListParamLarge(randomStrings)
3240
res3 := TestListsTestListResult()
3341
if len(res3) != 5 {
3442
panic("TestListsTestListResult")
@@ -212,6 +220,12 @@ func (i ListImpl) ListParam5(a []ExportsTestListsTestTuple3U8U32U8T) {
212220
}
213221
}
214222

223+
func (i ListImpl) ListParamLarge(a []string) {
224+
if len(a) != 1000 {
225+
panic("ListParamLarge")
226+
}
227+
}
228+
215229
func (i ListImpl) ListResult() []uint8 {
216230
return []uint8{1, 2, 3, 4, 5}
217231
}

0 commit comments

Comments
 (0)