Skip to content

Commit b601633

Browse files
authored
c#: Use span and memory apis for primitive type parameters on imports (#1138)
* Use memory api Signed-off-by: James Sturtevant <jsturtevant@gmail.com> * Use Span when working with primative lists Signed-off-by: James Sturtevant <jsturtevant@gmail.com> * Gen to functions Signed-off-by: James Sturtevant <jsturtevant@gmail.com> * Generate multiple functions Signed-off-by: James Sturtevant <jsturtevant@gmail.com> * Some clean up Signed-off-by: James Sturtevant <jsturtevant@gmail.com> --------- Signed-off-by: James Sturtevant <jsturtevant@gmail.com>
1 parent cae738d commit b601633

File tree

3 files changed

+172
-66
lines changed

3 files changed

+172
-66
lines changed

crates/csharp/src/function.rs

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::csharp_ident::ToCSharpIdent;
2-
use crate::interface::InterfaceGenerator;
2+
use crate::interface::{InterfaceGenerator, ParameterType};
33
use crate::world_generator::CSharp;
44
use heck::ToUpperCamelCase;
55
use std::fmt::Write;
@@ -26,6 +26,9 @@ pub(crate) struct FunctionBindgen<'a, 'b> {
2626
import_return_pointer_area_size: usize,
2727
import_return_pointer_area_align: usize,
2828
pub(crate) resource_drops: Vec<(String, String)>,
29+
is_block: bool,
30+
fixed_statments: Vec<Fixed>,
31+
parameter_type: ParameterType,
2932
}
3033

3134
impl<'a, 'b> FunctionBindgen<'a, 'b> {
@@ -35,6 +38,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
3538
kind: &'b FunctionKind,
3639
params: Box<[String]>,
3740
results: Vec<TypeId>,
41+
parameter_type: ParameterType,
3842
) -> FunctionBindgen<'a, 'b> {
3943
let mut locals = Ns::default();
4044
// Ensure temporary variable names don't clash with parameter names:
@@ -57,6 +61,9 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
5761
import_return_pointer_area_size: 0,
5862
import_return_pointer_area_align: 0,
5963
resource_drops: Vec::new(),
64+
is_block: false,
65+
fixed_statments: Vec::new(),
66+
parameter_type: parameter_type,
6067
}
6168
}
6269

@@ -497,12 +504,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
497504
results.push(result);
498505
}
499506
Instruction::TupleLift { .. } => {
500-
let mut result = String::from("(");
501-
502-
uwriteln!(result, "{}", operands.join(", "));
503-
504-
result.push_str(")");
505-
results.push(result);
507+
results.push(format!("({})", operands.join(", ")));
506508
}
507509

508510
Instruction::TupleLower { tuple, ty: _ } => {
@@ -722,19 +724,34 @@ impl Bindgen for FunctionBindgen<'_, '_> {
722724
Direction::Import => {
723725
let ptr: String = self.locals.tmp("listPtr");
724726
let handle: String = self.locals.tmp("gcHandle");
725-
// Despite the name GCHandle.Alloc here this does not actually allocate memory on the heap.
726-
// It pins the array with the garbage collector so that it can be passed to unmanaged code.
727-
// It is required to free the pin after use which is done in the Cleanup section.
728-
self.needs_cleanup = true;
729-
uwrite!(
730-
self.src,
731-
"
732-
var {handle} = GCHandle.Alloc({list}, GCHandleType.Pinned);
733-
var {ptr} = {handle}.AddrOfPinnedObject();
734-
cleanups.Add(()=> {handle}.Free());
735-
"
736-
);
737-
results.push(format!("{ptr}"));
727+
728+
if !self.is_block && self.parameter_type == ParameterType::Span {
729+
self.fixed_statments.push(Fixed {
730+
item_to_pin: list.clone(),
731+
ptr_name: ptr.clone(),
732+
});
733+
}else if !self.is_block && self.parameter_type == ParameterType::Memory {
734+
self.fixed_statments.push(Fixed {
735+
item_to_pin: format!("{list}.Span"),
736+
ptr_name: ptr.clone(),
737+
});
738+
} else {
739+
// With variants we can't use span since the Fixed statment can't always be applied to all the variants
740+
// Despite the name GCHandle.Alloc here this does not re-allocate the object but it does make an
741+
// allocation for the handle in a special resource pool which can result in GC pressure.
742+
// It pins the array with the garbage collector so that it can be passed to unmanaged code.
743+
// It is required to free the pin after use which is done in the Cleanup section.
744+
self.needs_cleanup = true;
745+
uwrite!(
746+
self.src,
747+
"
748+
var {handle} = GCHandle.Alloc({list}, GCHandleType.Pinned);
749+
var {ptr} = {handle}.AddrOfPinnedObject();
750+
cleanups.Add(()=> {handle}.Free());
751+
"
752+
);
753+
}
754+
results.push(format!("(nint){ptr}"));
738755
results.push(format!("({list}).Length"));
739756
}
740757
Direction::Export => {
@@ -1019,11 +1036,18 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10191036
}
10201037

10211038
Instruction::Return { amt: _, func } => {
1039+
if self.fixed_statments.len() > 0 {
1040+
let fixed: String = self.fixed_statments.iter().map(|f| format!("{} = {}", f.ptr_name, f.item_to_pin)).collect::<Vec<_>>().join(", ");
1041+
self.src.insert_str(0, &format!("fixed (void* {fixed})
1042+
{{
1043+
"));
1044+
}
1045+
10221046
if self.needs_cleanup {
10231047
self.src.insert_str(0, "var cleanups = new List<Action>();
10241048
");
10251049

1026-
uwriteln!(self.src, "\
1050+
uwriteln!(self.src, "
10271051
foreach (var cleanup in cleanups)
10281052
{{
10291053
cleanup();
@@ -1037,11 +1061,15 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10371061
self.handle_result_import(operands);
10381062
}
10391063
_ => {
1040-
let results = operands.join(", ");
1064+
let results: String = operands.join(", ");
10411065
uwriteln!(self.src, "return ({results});")
10421066
}
10431067
}
10441068
}
1069+
1070+
if self.fixed_statments.len() > 0 {
1071+
uwriteln!(self.src, "}}");
1072+
}
10451073
}
10461074

10471075
Instruction::Malloc { .. } => unimplemented!(),
@@ -1232,6 +1260,8 @@ impl Bindgen for FunctionBindgen<'_, '_> {
12321260
element: self.locals.tmp("element"),
12331261
base: self.locals.tmp("basePtr"),
12341262
});
1263+
1264+
self.is_block = true;
12351265
}
12361266

12371267
fn finish_block(&mut self, operands: &mut Vec<String>) {
@@ -1247,6 +1277,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
12471277
element,
12481278
base,
12491279
});
1280+
self.is_block = false;
12501281
}
12511282

12521283
fn sizes(&self) -> &SizeAlign {
@@ -1319,6 +1350,11 @@ struct Block {
13191350
base: String,
13201351
}
13211352

1353+
struct Fixed {
1354+
item_to_pin: String,
1355+
ptr_name: String,
1356+
}
1357+
13221358
struct BlockStorage {
13231359
body: String,
13241360
element: String,

crates/csharp/src/interface.rs

Lines changed: 112 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,67 @@ impl InterfaceGenerator<'_> {
247247
.collect::<Vec<_>>()
248248
.join(", ");
249249

250+
let mut funcs: Vec<(String, String)> = Vec::new();
251+
funcs.push(self.gen_import_src(func, &results, ParameterType::ABI));
252+
253+
let include_additional_functions = func
254+
.params
255+
.iter()
256+
.skip(if let FunctionKind::Method(_) = &func.kind {
257+
1
258+
} else {
259+
0
260+
})
261+
.any(|param| self.is_primative_list(&param.1));
262+
263+
if include_additional_functions {
264+
funcs.push(self.gen_import_src(func, &results, ParameterType::Span));
265+
funcs.push(self.gen_import_src(func, &results, ParameterType::Memory));
266+
}
267+
268+
let import_name = &func.name;
269+
270+
self.csharp_gen
271+
.require_using("System.Runtime.InteropServices");
272+
273+
let target = if let FunctionKind::Freestanding = &func.kind {
274+
self.require_interop_using("System.Runtime.InteropServices");
275+
&mut self.csharp_interop_src
276+
} else {
277+
self.require_using("System.Runtime.InteropServices");
278+
&mut self.src
279+
};
280+
281+
uwrite!(
282+
target,
283+
r#"
284+
internal static class {interop_camel_name}WasmInterop
285+
{{
286+
[DllImport("{import_module_name}", EntryPoint = "{import_name}"), WasmImportLinkage]
287+
internal static extern {wasm_result_type} wasmImport{interop_camel_name}({wasm_params});
288+
}}
289+
"#
290+
);
291+
292+
for (src, params) in funcs {
293+
uwrite!(
294+
target,
295+
r#"
296+
{access} {extra_modifiers} {modifiers} unsafe {result_type} {camel_name}({params})
297+
{{
298+
{src}
299+
}}
300+
"#
301+
);
302+
}
303+
}
304+
305+
fn gen_import_src(
306+
&mut self,
307+
func: &Function,
308+
results: &Vec<TypeId>,
309+
parameter_type: ParameterType,
310+
) -> (String, String) {
250311
let mut bindgen = FunctionBindgen::new(
251312
self,
252313
&func.item_name(),
@@ -262,7 +323,8 @@ impl InterfaceGenerator<'_> {
262323
}
263324
})
264325
.collect(),
265-
results,
326+
results.clone(),
327+
parameter_type,
266328
);
267329

268330
abi::call(
@@ -285,54 +347,15 @@ impl InterfaceGenerator<'_> {
285347
0
286348
})
287349
.map(|param| {
288-
let ty = self.type_name_with_qualifier(&param.1, true);
350+
let ty = self.name_with_qualifier(&param.1, true, parameter_type);
289351
let param_name = &param.0;
290352
let param_name = param_name.to_csharp_ident();
291353
format!("{ty} {param_name}")
292354
})
293355
.collect::<Vec<_>>()
294356
.join(", ");
295357

296-
let import_name = &func.name;
297-
298-
self.csharp_gen
299-
.require_using("System.Runtime.InteropServices");
300-
301-
let target = if let FunctionKind::Freestanding = &func.kind {
302-
self.require_interop_using("System.Runtime.InteropServices");
303-
&mut self.csharp_interop_src
304-
} else {
305-
self.require_using("System.Runtime.InteropServices");
306-
&mut self.src
307-
};
308-
309-
uwrite!(
310-
target,
311-
r#"
312-
internal static class {interop_camel_name}WasmInterop
313-
{{
314-
[DllImport("{import_module_name}", EntryPoint = "{import_name}"), WasmImportLinkage]
315-
internal static extern {wasm_result_type} wasmImport{interop_camel_name}({wasm_params});
316-
"#
317-
);
318-
319-
uwrite!(
320-
target,
321-
r#"
322-
}}
323-
"#,
324-
);
325-
326-
uwrite!(
327-
target,
328-
r#"
329-
{access} {extra_modifiers} {modifiers} unsafe {result_type} {camel_name}({params})
330-
{{
331-
{src}
332-
//TODO: free alloc handle (interopString) if exists
333-
}}
334-
"#
335-
);
358+
(src, params)
336359
}
337360

338361
pub(crate) fn export(&mut self, func: &Function, interface_name: Option<&WorldKey>) {
@@ -390,6 +413,7 @@ impl InterfaceGenerator<'_> {
390413
&func.kind,
391414
(0..sig.params.len()).map(|i| format!("p{i}")).collect(),
392415
results,
416+
ParameterType::ABI,
393417
);
394418

395419
abi::call(
@@ -518,6 +542,31 @@ impl InterfaceGenerator<'_> {
518542
}
519543

520544
pub(crate) fn type_name_with_qualifier(&mut self, ty: &Type, qualifier: bool) -> String {
545+
self.name_with_qualifier(ty, qualifier, ParameterType::ABI)
546+
}
547+
548+
fn is_primative_list(&mut self, ty: &Type) -> bool {
549+
match ty {
550+
Type::Id(id) => {
551+
let ty = &self.resolve.types[*id];
552+
match &ty.kind {
553+
TypeDefKind::Type(ty) => self.is_primative_list(ty),
554+
TypeDefKind::List(ty) if crate::world_generator::is_primitive(ty) => {
555+
return true
556+
}
557+
_ => false,
558+
}
559+
}
560+
_ => false,
561+
}
562+
}
563+
564+
pub(crate) fn name_with_qualifier(
565+
&mut self,
566+
ty: &Type,
567+
qualifier: bool,
568+
parameter_type: ParameterType,
569+
) -> String {
521570
match ty {
522571
Type::Bool => "bool".to_owned(),
523572
Type::U8 => "byte".to_owned(),
@@ -535,9 +584,21 @@ impl InterfaceGenerator<'_> {
535584
Type::Id(id) => {
536585
let ty = &self.resolve.types[*id];
537586
match &ty.kind {
538-
TypeDefKind::Type(ty) => self.type_name_with_qualifier(ty, qualifier),
587+
TypeDefKind::Type(ty) => {
588+
self.name_with_qualifier(ty, qualifier, parameter_type)
589+
}
539590
TypeDefKind::List(ty) => {
540-
if crate::world_generator::is_primitive(ty) {
591+
if crate::world_generator::is_primitive(ty)
592+
&& self.direction == Direction::Import
593+
&& parameter_type == ParameterType::Span
594+
{
595+
format!("Span<{}>", self.type_name(ty))
596+
} else if crate::world_generator::is_primitive(ty)
597+
&& self.direction == Direction::Import
598+
&& parameter_type == ParameterType::Memory
599+
{
600+
format!("Memory<{}>", self.type_name(ty))
601+
} else if crate::world_generator::is_primitive(ty) {
541602
format!("{}[]", self.type_name(ty))
542603
} else {
543604
format!("List<{}>", self.type_name_with_qualifier(ty, qualifier))
@@ -1178,6 +1239,13 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> {
11781239
}
11791240
}
11801241

1242+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1243+
pub(crate) enum ParameterType {
1244+
ABI,
1245+
Span,
1246+
Memory,
1247+
}
1248+
11811249
fn payload_and_results(
11821250
resolve: &Resolve,
11831251
ty: Type,

tests/runtime/lists/wasm.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public static void TestImports()
3030
}
3131

3232
TestInterop.ListParam(new byte[] { (byte)1, (byte)2, (byte)3, (byte)4 });
33+
TestInterop.ListParam((new byte[] { (byte)1, (byte)2, (byte)3, (byte)4 }).AsSpan());
34+
TestInterop.ListParam((new byte[] { (byte)1, (byte)2, (byte)3, (byte)4 }).AsMemory());
3335
TestInterop.ListParam2("foo");
3436
TestInterop.ListParam3(new List<String>() {
3537
"foo",

0 commit comments

Comments
 (0)