Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

c#: Use span and memory apis for primative type parameters #1138

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1005,11 +1022,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 @@ -1023,11 +1047,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 @@ -1218,6 +1246,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 @@ -1233,6 +1263,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
element,
base,
});
self.is_block = false;
}

fn sizes(&self) -> &SizeAlign {
Expand Down Expand Up @@ -1305,6 +1336,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
Loading