Skip to content
Merged
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
21 changes: 12 additions & 9 deletions crates/backend_csharp/src/pass/output/rust/pattern/slices.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! Renders slice types (`Slice<T>` and `SliceMut<T>`) per output file.
//!
//! For each slice type, determines whether to use the "fast" (blittable) or
//! "marshalling" template based on the element type's `ManagedConversion`.
//! Elements with `AsIs` or `To` conversion are blittable and use `GCHandle` pinning;
//! elements with `Into` conversion require per-element marshalling.
//! For each slice type, determines whether to use the "fast" (representation-
//! identical) or "marshalling" template based on the element type's
//! `ManagedConversion`. Only `AsIs` elements can be projected directly over
//! native memory. `To` elements require per-element conversion, while `Into`
//! elements cannot be safely read from borrowed memory.

use crate::lang::TypeId;
use crate::lang::types::ManagedConversion;
Expand Down Expand Up @@ -54,11 +55,13 @@ impl Pass {
let Some(element_ty) = types.get(element_ty_id) else { continue };
let element_name = &element_ty.name;

let is_blittable = matches!(managed_conversion.managed_conversion(element_ty_id), Some(ManagedConversion::AsIs | ManagedConversion::To));
let Some(element_conversion) = managed_conversion.managed_conversion(element_ty_id) else {
continue;
};

let method = if is_mut { "SliceMut" } else { "Slice" };

let rendered = if is_blittable {
let rendered = if element_conversion == ManagedConversion::AsIs {
let mut context = Context::new();
context.insert("name", &ty.name);
context.insert("element_type", element_name);
Expand All @@ -73,12 +76,12 @@ impl Pass {
context.insert("element_type", element_name);
context.insert("unmanaged_element_type", &unmanaged_name);
context.insert("method", method);
// Element conversion method names for non-blittable elements.
let element_to_managed = managed_conversion.managed_conversion(element_ty_id).map_or("ToManaged", |mc| match mc {
let element_to_managed = match element_conversion {
ManagedConversion::Into => "IntoManaged",
_ => "ToManaged",
});
};
context.insert("element_to_managed", element_to_managed);
context.insert("has_indexer", &(element_conversion == ManagedConversion::To));
templates.render("rust/pattern/slice/marshalling.cs", &context)?
};

Expand Down
24 changes: 16 additions & 8 deletions crates/backend_csharp/src/pass/output/rust/pattern/vec.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! Renders `Vec<T>` pattern types per output file.
//!
//! For each Vec type, determines whether to use the "fast" (blittable) or
//! "marshalling" template based on the element type's `ManagedConversion`.
//! Elements with `AsIs` or `To` conversion are blittable; elements with `Into`
//! conversion require per-element marshalling. The nested `InteropHelper` class
//! embeds the `vec_create` / `vec_destroy` entry points discovered by the
//! `model::rust::pattern::vec` pass.
//! For each Vec type, determines whether to use the "fast" (representation-
//! identical) or "marshalling" template based on the element type's
//! `ManagedConversion`. Only `AsIs` elements can be projected directly over
//! native memory; `To` and `Into` elements require per-element conversion. The
//! nested `InteropHelper` class embeds the `vec_create` / `vec_destroy` entry
//! points discovered by the `model::rust::pattern::vec` pass.

use crate::lang::types::ManagedConversion;
use crate::lang::types::kind::{TypeKind, TypePattern};
Expand Down Expand Up @@ -56,9 +56,11 @@ impl Pass {
let Some(element_ty) = types.get(element_ty_id) else { continue };
let element_name = &element_ty.name;

let is_blittable = matches!(managed_conversion.managed_conversion(element_ty_id), Some(ManagedConversion::AsIs | ManagedConversion::To));
let Some(element_conversion) = managed_conversion.managed_conversion(element_ty_id) else {
continue;
};

let rendered = if is_blittable {
let rendered = if element_conversion == ManagedConversion::AsIs {
let mut context = Context::new();
context.insert("name", &ty.name);
context.insert("element_type", element_name);
Expand All @@ -74,6 +76,12 @@ impl Pass {
context.insert("unmanaged_element_type", &unmanaged_name);
context.insert("create_entry_point", &helpers.create_entry_point);
context.insert("destroy_entry_point", &helpers.destroy_entry_point);
let (element_to_unmanaged, element_to_managed) = match element_conversion {
ManagedConversion::Into => ("IntoUnmanaged", "IntoManaged"),
_ => ("AsUnmanaged", "ToManaged"),
};
context.insert("element_to_unmanaged", element_to_unmanaged);
context.insert("element_to_managed", element_to_managed);
templates.render("rust/pattern/vec/marshalling.cs", &context)?
};

Expand Down
4 changes: 2 additions & 2 deletions crates/backend_csharp/templates/rust/pattern/slice/fast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ public unsafe {{ element_type }} this[int i]
{{ _fns_decorators_all | indent(width = 8) }}
get
{
if (i >= Count) throw new IndexOutOfRangeException();
if (i < 0 || (ulong)i >= _len) throw new IndexOutOfRangeException();
return Unsafe.Read<{{ element_type }}>((void*)IntPtr.Add(_data, i * Unsafe.SizeOf<{{ element_type }}>()));
}
{% if is_mut %}
{{ _fns_decorators_all | indent(width = 8) }}
set
{
if (i >= Count) throw new IndexOutOfRangeException();
if (i < 0 || (ulong)i >= _len) throw new IndexOutOfRangeException();
Unsafe.Write<{{ element_type }}>((void*)IntPtr.Add(_data, i * Unsafe.SizeOf<{{ element_type }}>()), value);
}
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@ public partial class {{ name }} : IDisposable
/// The number of elements in this slice.
public int Count => (int) _len;

{% if has_indexer %}
/// Gets the element at the given index, marshalling from its unmanaged form.
public unsafe {{ element_type }} this[int i]
{
{{ _fns_decorators_all | indent(width = 8) }}
get
{
if (i >= (int) _len) throw new IndexOutOfRangeException();
if (i < 0 || (ulong)i >= _len) throw new IndexOutOfRangeException();
if (_data == IntPtr.Zero) { throw new NullReferenceException(); }
var size = sizeof({{ unmanaged_element_type }});
var size = Marshal.SizeOf<{{ unmanaged_element_type }}>();
var ptr = IntPtr.Add(_data, i * size);
var unmanaged = Marshal.PtrToStructure<{{ unmanaged_element_type }}>(ptr);
return unmanaged.{{ element_to_managed }}();
}
}
{% endif %}

{{ _fns_decorators_all | indent }}
{{ name }}() { }
Expand All @@ -40,7 +42,7 @@ public unsafe {{ element_type }} this[int i]
public static unsafe {{ name }} From({{ element_type }}[] managed)
{
var rval = new {{ name }}();
var size = sizeof({{ unmanaged_element_type }});
var size = Marshal.SizeOf<{{ unmanaged_element_type }}>();
rval._data = Marshal.AllocHGlobal(size * managed.Length);
rval._len = (ulong) managed.Length;
for (var i = 0; i < managed.Length; ++i)
Expand Down
2 changes: 1 addition & 1 deletion crates/backend_csharp/templates/rust/pattern/vec/fast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ public unsafe {{ element_type }} this[int i]
{{ _fns_decorators_all | indent(width = 8) }}
get
{
if (i >= Count) throw new IndexOutOfRangeException();
if (_ptr == IntPtr.Zero) throw new NullReferenceException();
if (i < 0 || (ulong)i >= _len) throw new IndexOutOfRangeException();
return Marshal.PtrToStructure<{{ element_type }}>(new IntPtr(_ptr.ToInt64() + i * sizeof({{ element_type }})));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public partial class {{ name }} : IDisposable
var _temp = new {{ unmanaged_element_type }}[_data.Length];
for (var i = 0; i < _data.Length; ++i)
{
_temp[i] = _data[i].IntoUnmanaged();
_temp[i] = _data[i].{{ element_to_unmanaged }}();
}
fixed (void* _data_ptr = _temp)
{
Expand All @@ -32,10 +32,10 @@ public unsafe {{ element_type }} this[int i]
{{ _fns_decorators_all | indent(width = 8) }}
get
{
if (i >= Count) throw new IndexOutOfRangeException();
if (_ptr == IntPtr.Zero) throw new NullReferenceException();
if (i < 0 || (ulong)i >= _len) throw new IndexOutOfRangeException();
var _element = Marshal.PtrToStructure<{{ unmanaged_element_type }}>(new IntPtr(_ptr.ToInt64() + i * sizeof({{ unmanaged_element_type }})));
return _element.IntoManaged();
return _element.{{ element_to_managed }}();
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions crates/backend_csharp/tests/output/patterns/slice.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
use interoptopus::{ffi, function};

#[ffi]
#[derive(Clone)]
pub struct Attribute<'a> {
pub bytes: ffi::Slice<'a, u8>,
}

#[ffi(export = unique)]
pub fn sum_slice(values: ffi::Slice<u32>) -> u32 {
values.iter().sum()
}

#[ffi(export = unique)]
pub fn count_attributes(values: ffi::Slice<Attribute>) -> u32 {
values.len() as u32
}

#[test]
fn basic() {
test_output!("Interop.cs", [function!(sum_slice)]);
}

#[test]
fn non_blittable() {
test_output!("Interop.cs", [function!(count_attributes)]);
}
Git LFS file not shown
Git LFS file not shown
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using My.Company;
using My.Company.Common;
using Xunit;
Expand All @@ -13,6 +14,24 @@ public void pattern_ffi_slice_1()
Assert.Equal(100_000u, result);
}

[Fact]
public void slice_rejects_negative_indices()
{
using var data = new byte[] { 1, 2, 3 }.Slice();
Assert.Throws<IndexOutOfRangeException>(() => _ = data[-1]);
}

[Fact]
public void pattern_ffi_slice_of_structs_from_native_memory()
{
Interop.pattern_ffi_slice_of_structs_callback(attributes =>
{
var attribute = attributes[0];
Assert.Equal(3, attribute.bytes.Count);
Assert.Equal(2, attribute.bytes[1]);
});
}


[Fact]
public void pattern_ffi_slice_2()
Expand Down
Git LFS file not shown
1 change: 1 addition & 0 deletions crates/reference_project/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ pub fn inventory() -> RustInventory {
.register(function!(patterns::slice::pattern_ffi_slice_8))
.register(function!(patterns::slice::pattern_ffi_slice_9))
.register(function!(patterns::slice::pattern_ffi_slice_in_struct))
.register(function!(patterns::slice::pattern_ffi_slice_of_structs_callback))
.register(function!(patterns::slice::pattern_ffi_slice_delegate))
.register(function!(patterns::slice::pattern_ffi_slice_delegate_huge))
.register(function!(patterns::option::pattern_ffi_option_1))
Expand Down
8 changes: 8 additions & 0 deletions crates/reference_project/src/patterns/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ callback!(CallbackSliceMut(slice: SliceMut<'_, u8>));
callback!(CallbackU8(value: u8) -> u8);
callback!(CallbackCharArray2(value: CharArray));
callback!(CallbackFFISlice(slice: ffi::Slice<u8>) -> u8);
callback!(CallbackSliceUseSliceByteInStruct(slice: ffi::Slice<UseSliceByteInStruct>));

#[ffi]
pub fn pattern_ffi_slice_1(ffi_slice: Slice<u32>) -> u32 {
Expand Down Expand Up @@ -100,3 +101,10 @@ pub struct UseSliceByteInStruct<'a> {
pub fn pattern_ffi_slice_in_struct(x: UseSliceByteInStruct) -> u32 {
x.bytes.as_slice().len() as u32
}

#[ffi]
pub fn pattern_ffi_slice_of_structs_callback(callback: CallbackSliceUseSliceByteInStruct) {
let bytes = [1, 2, 3];
let values = [UseSliceByteInStruct { bytes: ffi::Slice::from_slice(&bytes) }];
callback.call(ffi::Slice::from_slice(&values));
}
Loading