Skip to content

Commit

Permalink
c#: Use span and memory apis for primitive type parameters on imports (
Browse files Browse the repository at this point in the history
…#1138)

* Use memory api

Signed-off-by: James Sturtevant <[email protected]>

* Use Span when working with primative lists

Signed-off-by: James Sturtevant <[email protected]>

* Gen to functions

Signed-off-by: James Sturtevant <[email protected]>

* Generate multiple functions

Signed-off-by: James Sturtevant <[email protected]>

* Some clean up

Signed-off-by: James Sturtevant <[email protected]>

---------

Signed-off-by: James Sturtevant <[email protected]>
  • Loading branch information
jsturtevant authored Jan 31, 2025
1 parent cae738d commit b601633
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 66 deletions.
80 changes: 58 additions & 22 deletions crates/csharp/src/function.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::csharp_ident::ToCSharpIdent;
use crate::interface::InterfaceGenerator;
use crate::interface::{InterfaceGenerator, ParameterType};
use crate::world_generator::CSharp;
use heck::ToUpperCamelCase;
use std::fmt::Write;
Expand All @@ -26,6 +26,9 @@ pub(crate) struct FunctionBindgen<'a, 'b> {
import_return_pointer_area_size: usize,
import_return_pointer_area_align: usize,
pub(crate) resource_drops: Vec<(String, String)>,
is_block: bool,
fixed_statments: Vec<Fixed>,
parameter_type: ParameterType,
}

impl<'a, 'b> FunctionBindgen<'a, 'b> {
Expand All @@ -35,6 +38,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
kind: &'b FunctionKind,
params: Box<[String]>,
results: Vec<TypeId>,
parameter_type: ParameterType,
) -> FunctionBindgen<'a, 'b> {
let mut locals = Ns::default();
// Ensure temporary variable names don't clash with parameter names:
Expand All @@ -57,6 +61,9 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
import_return_pointer_area_size: 0,
import_return_pointer_area_align: 0,
resource_drops: Vec::new(),
is_block: false,
fixed_statments: Vec::new(),
parameter_type: parameter_type,
}
}

Expand Down Expand Up @@ -497,12 +504,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
results.push(result);
}
Instruction::TupleLift { .. } => {
let mut result = String::from("(");

uwriteln!(result, "{}", operands.join(", "));

result.push_str(")");
results.push(result);
results.push(format!("({})", operands.join(", ")));
}

Instruction::TupleLower { tuple, ty: _ } => {
Expand Down Expand Up @@ -722,19 +724,34 @@ impl Bindgen for FunctionBindgen<'_, '_> {
Direction::Import => {
let ptr: String = self.locals.tmp("listPtr");
let handle: String = self.locals.tmp("gcHandle");
// Despite the name GCHandle.Alloc here this does not actually allocate memory on the heap.
// It pins the array with the garbage collector so that it can be passed to unmanaged code.
// It is required to free the pin after use which is done in the Cleanup section.
self.needs_cleanup = true;
uwrite!(
self.src,
"
var {handle} = GCHandle.Alloc({list}, GCHandleType.Pinned);
var {ptr} = {handle}.AddrOfPinnedObject();
cleanups.Add(()=> {handle}.Free());
"
);
results.push(format!("{ptr}"));

if !self.is_block && self.parameter_type == ParameterType::Span {
self.fixed_statments.push(Fixed {
item_to_pin: list.clone(),
ptr_name: ptr.clone(),
});
}else if !self.is_block && self.parameter_type == ParameterType::Memory {
self.fixed_statments.push(Fixed {
item_to_pin: format!("{list}.Span"),
ptr_name: ptr.clone(),
});
} else {
// With variants we can't use span since the Fixed statment can't always be applied to all the variants
// Despite the name GCHandle.Alloc here this does not re-allocate the object but it does make an
// allocation for the handle in a special resource pool which can result in GC pressure.
// It pins the array with the garbage collector so that it can be passed to unmanaged code.
// It is required to free the pin after use which is done in the Cleanup section.
self.needs_cleanup = true;
uwrite!(
self.src,
"
var {handle} = GCHandle.Alloc({list}, GCHandleType.Pinned);
var {ptr} = {handle}.AddrOfPinnedObject();
cleanups.Add(()=> {handle}.Free());
"
);
}
results.push(format!("(nint){ptr}"));
results.push(format!("({list}).Length"));
}
Direction::Export => {
Expand Down Expand Up @@ -1019,11 +1036,18 @@ impl Bindgen for FunctionBindgen<'_, '_> {
}

Instruction::Return { amt: _, func } => {
if self.fixed_statments.len() > 0 {
let fixed: String = self.fixed_statments.iter().map(|f| format!("{} = {}", f.ptr_name, f.item_to_pin)).collect::<Vec<_>>().join(", ");
self.src.insert_str(0, &format!("fixed (void* {fixed})
{{
"));
}

if self.needs_cleanup {
self.src.insert_str(0, "var cleanups = new List<Action>();
");

uwriteln!(self.src, "\
uwriteln!(self.src, "
foreach (var cleanup in cleanups)
{{
cleanup();
Expand All @@ -1037,11 +1061,15 @@ impl Bindgen for FunctionBindgen<'_, '_> {
self.handle_result_import(operands);
}
_ => {
let results = operands.join(", ");
let results: String = operands.join(", ");
uwriteln!(self.src, "return ({results});")
}
}
}

if self.fixed_statments.len() > 0 {
uwriteln!(self.src, "}}");
}
}

Instruction::Malloc { .. } => unimplemented!(),
Expand Down Expand Up @@ -1232,6 +1260,8 @@ impl Bindgen for FunctionBindgen<'_, '_> {
element: self.locals.tmp("element"),
base: self.locals.tmp("basePtr"),
});

self.is_block = true;
}

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

fn sizes(&self) -> &SizeAlign {
Expand Down Expand Up @@ -1319,6 +1350,11 @@ struct Block {
base: String,
}

struct Fixed {
item_to_pin: String,
ptr_name: String,
}

struct BlockStorage {
body: String,
element: String,
Expand Down
156 changes: 112 additions & 44 deletions crates/csharp/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,67 @@ impl InterfaceGenerator<'_> {
.collect::<Vec<_>>()
.join(", ");

let mut funcs: Vec<(String, String)> = Vec::new();
funcs.push(self.gen_import_src(func, &results, ParameterType::ABI));

let include_additional_functions = func
.params
.iter()
.skip(if let FunctionKind::Method(_) = &func.kind {
1
} else {
0
})
.any(|param| self.is_primative_list(&param.1));

if include_additional_functions {
funcs.push(self.gen_import_src(func, &results, ParameterType::Span));
funcs.push(self.gen_import_src(func, &results, ParameterType::Memory));
}

let import_name = &func.name;

self.csharp_gen
.require_using("System.Runtime.InteropServices");

let target = if let FunctionKind::Freestanding = &func.kind {
self.require_interop_using("System.Runtime.InteropServices");
&mut self.csharp_interop_src
} else {
self.require_using("System.Runtime.InteropServices");
&mut self.src
};

uwrite!(
target,
r#"
internal static class {interop_camel_name}WasmInterop
{{
[DllImport("{import_module_name}", EntryPoint = "{import_name}"), WasmImportLinkage]
internal static extern {wasm_result_type} wasmImport{interop_camel_name}({wasm_params});
}}
"#
);

for (src, params) in funcs {
uwrite!(
target,
r#"
{access} {extra_modifiers} {modifiers} unsafe {result_type} {camel_name}({params})
{{
{src}
}}
"#
);
}
}

fn gen_import_src(
&mut self,
func: &Function,
results: &Vec<TypeId>,
parameter_type: ParameterType,
) -> (String, String) {
let mut bindgen = FunctionBindgen::new(
self,
&func.item_name(),
Expand All @@ -262,7 +323,8 @@ impl InterfaceGenerator<'_> {
}
})
.collect(),
results,
results.clone(),
parameter_type,
);

abi::call(
Expand All @@ -285,54 +347,15 @@ impl InterfaceGenerator<'_> {
0
})
.map(|param| {
let ty = self.type_name_with_qualifier(&param.1, true);
let ty = self.name_with_qualifier(&param.1, true, parameter_type);
let param_name = &param.0;
let param_name = param_name.to_csharp_ident();
format!("{ty} {param_name}")
})
.collect::<Vec<_>>()
.join(", ");

let import_name = &func.name;

self.csharp_gen
.require_using("System.Runtime.InteropServices");

let target = if let FunctionKind::Freestanding = &func.kind {
self.require_interop_using("System.Runtime.InteropServices");
&mut self.csharp_interop_src
} else {
self.require_using("System.Runtime.InteropServices");
&mut self.src
};

uwrite!(
target,
r#"
internal static class {interop_camel_name}WasmInterop
{{
[DllImport("{import_module_name}", EntryPoint = "{import_name}"), WasmImportLinkage]
internal static extern {wasm_result_type} wasmImport{interop_camel_name}({wasm_params});
"#
);

uwrite!(
target,
r#"
}}
"#,
);

uwrite!(
target,
r#"
{access} {extra_modifiers} {modifiers} unsafe {result_type} {camel_name}({params})
{{
{src}
//TODO: free alloc handle (interopString) if exists
}}
"#
);
(src, params)
}

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

abi::call(
Expand Down Expand Up @@ -518,6 +542,31 @@ impl InterfaceGenerator<'_> {
}

pub(crate) fn type_name_with_qualifier(&mut self, ty: &Type, qualifier: bool) -> String {
self.name_with_qualifier(ty, qualifier, ParameterType::ABI)
}

fn is_primative_list(&mut self, ty: &Type) -> bool {
match ty {
Type::Id(id) => {
let ty = &self.resolve.types[*id];
match &ty.kind {
TypeDefKind::Type(ty) => self.is_primative_list(ty),
TypeDefKind::List(ty) if crate::world_generator::is_primitive(ty) => {
return true
}
_ => false,
}
}
_ => false,
}
}

pub(crate) fn name_with_qualifier(
&mut self,
ty: &Type,
qualifier: bool,
parameter_type: ParameterType,
) -> String {
match ty {
Type::Bool => "bool".to_owned(),
Type::U8 => "byte".to_owned(),
Expand All @@ -535,9 +584,21 @@ impl InterfaceGenerator<'_> {
Type::Id(id) => {
let ty = &self.resolve.types[*id];
match &ty.kind {
TypeDefKind::Type(ty) => self.type_name_with_qualifier(ty, qualifier),
TypeDefKind::Type(ty) => {
self.name_with_qualifier(ty, qualifier, parameter_type)
}
TypeDefKind::List(ty) => {
if crate::world_generator::is_primitive(ty) {
if crate::world_generator::is_primitive(ty)
&& self.direction == Direction::Import
&& parameter_type == ParameterType::Span
{
format!("Span<{}>", self.type_name(ty))
} else if crate::world_generator::is_primitive(ty)
&& self.direction == Direction::Import
&& parameter_type == ParameterType::Memory
{
format!("Memory<{}>", self.type_name(ty))
} else if crate::world_generator::is_primitive(ty) {
format!("{}[]", self.type_name(ty))
} else {
format!("List<{}>", self.type_name_with_qualifier(ty, qualifier))
Expand Down Expand Up @@ -1178,6 +1239,13 @@ impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> {
}
}

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum ParameterType {
ABI,
Span,
Memory,
}

fn payload_and_results(
resolve: &Resolve,
ty: Type,
Expand Down
2 changes: 2 additions & 0 deletions tests/runtime/lists/wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public static void TestImports()
}

TestInterop.ListParam(new byte[] { (byte)1, (byte)2, (byte)3, (byte)4 });
TestInterop.ListParam((new byte[] { (byte)1, (byte)2, (byte)3, (byte)4 }).AsSpan());
TestInterop.ListParam((new byte[] { (byte)1, (byte)2, (byte)3, (byte)4 }).AsMemory());
TestInterop.ListParam2("foo");
TestInterop.ListParam3(new List<String>() {
"foo",
Expand Down

0 comments on commit b601633

Please sign in to comment.