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

Implement collection marshaller spec #1197

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
71796dd
Add marshaller for blittable collections.
jkoritzinsky May 26, 2021
f4fe656
Implement contiguous non-blittable collection marshalling.
jkoritzinsky May 27, 2021
a0835cc
Implement array marshaller in source following the collections model.…
jkoritzinsky May 27, 2021
688b745
Merge branch 'feature/DllImportGenerator' of github.com:dotnet/runtim…
jkoritzinsky May 27, 2021
7ceeadf
Move arrays over to collection-based marshalling.
jkoritzinsky May 28, 2021
d26f772
Fix unit test failures and delete dead code.
jkoritzinsky May 28, 2021
1dd69a0
Fix integration tests. Extend collection marshallers design with an a…
jkoritzinsky May 28, 2021
8c82105
Implement element cleanup.
jkoritzinsky May 28, 2021
51a5e0d
Add support for parsing count info and indirection info out of Marsha…
jkoritzinsky May 28, 2021
cbf11f0
Handle open generics in NativeMarshallingAttribute.
jkoritzinsky May 28, 2021
c147791
Implement element type lookup.
jkoritzinsky Jun 1, 2021
fd837d8
Add positive unit tests for collection marshalling.
jkoritzinsky Jun 1, 2021
c0ae812
Get tests passing and update resource string.
jkoritzinsky Jun 1, 2021
25fc6f7
Add test for elementindirectionlevel processing.
jkoritzinsky Jun 1, 2021
d270851
Refactor marshalling attribute parsing into its own type to enable be…
jkoritzinsky Jun 2, 2021
2ebb57f
Add recursion protection for CountElementName and marshalling attribu…
jkoritzinsky Jun 2, 2021
a745e84
Use ImmutableHashSet to enable fast lookup and automatic "pop" behavi…
jkoritzinsky Jun 2, 2021
d9a9308
Add tests with basic collection marshalling.
jkoritzinsky Jun 2, 2021
9a350c5
Remove commented out code.
jkoritzinsky Jun 2, 2021
da6a851
Merge branch 'feature/DllImportGenerator' of github.com:dotnet/runtim…
jkoritzinsky Jun 2, 2021
d8b0174
Merge branch 'feature/DllImportGenerator' of github.com:dotnet/runtim…
jkoritzinsky Jun 3, 2021
7d2a90d
Generate intermediate cleanup statements even if there is no FreeNati…
jkoritzinsky Jun 3, 2021
992cca4
Add a nice big comment block explaining the GetIdentifiers logic arou…
jkoritzinsky Jun 5, 2021
99b8f74
Fix edge case for stackalloc size check.
jkoritzinsky Jun 5, 2021
d8829e5
Introduce local for easier debuggability.
jkoritzinsky Jun 5, 2021
d1cd185
Introduce constants.
jkoritzinsky Jun 5, 2021
945552b
Seal attributes.
jkoritzinsky Jun 5, 2021
e2a15ac
string.Empty instead of null.
jkoritzinsky Jun 7, 2021
594aa68
Use a combination of the Strategy and Decoration patterns to implemen…
jkoritzinsky Jun 8, 2021
a6eeac9
Use enum instead of a bool.
jkoritzinsky Jun 8, 2021
0845015
Seal more types.
jkoritzinsky Jun 8, 2021
a5efcd9
PR feedback.
jkoritzinsky Jun 8, 2021
2836c29
Fix various test failures around element marshalling, by-value conten…
jkoritzinsky Jun 8, 2021
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
217 changes: 217 additions & 0 deletions DllImportGenerator/Ancillary.Interop/ArrayMarshaller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@

using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace System.Runtime.InteropServices.GeneratedMarshalling
{
public unsafe ref struct ArrayMarshaller<T>
{
private T[]? managedArray;
private readonly int sizeOfNativeElement;
private IntPtr allocatedMemory;

public ArrayMarshaller(int sizeOfNativeElement)
:this()
{
this.sizeOfNativeElement = sizeOfNativeElement;
}

public ArrayMarshaller(T[]? managed, int sizeOfNativeElement)
{
allocatedMemory = default;
this.sizeOfNativeElement = sizeOfNativeElement;
if (managed is null)
{
managedArray = null;
NativeValueStorage = default;
return;
}
managedArray = managed;
this.sizeOfNativeElement = sizeOfNativeElement;
// Always allocate at least one byte when the array is zero-length.
int spaceToAllocate = Math.Max(managed.Length * sizeOfNativeElement, 1);
allocatedMemory = Marshal.AllocCoTaskMem(spaceToAllocate);
NativeValueStorage = new Span<byte>((void*)allocatedMemory, spaceToAllocate);
}

public ArrayMarshaller(T[]? managed, Span<byte> stackSpace, int sizeOfNativeElement)
{
allocatedMemory = default;
this.sizeOfNativeElement = sizeOfNativeElement;
if (managed is null)
{
managedArray = null;
NativeValueStorage = default;
return;
}
managedArray = managed;
// Always allocate at least one byte when the array is zero-length.
int spaceToAllocate = Math.Max(managed.Length * sizeOfNativeElement, 1);
if (spaceToAllocate <= stackSpace.Length)
{
NativeValueStorage = stackSpace[0..spaceToAllocate];
}
else
{
allocatedMemory = Marshal.AllocCoTaskMem(spaceToAllocate);
NativeValueStorage = new Span<byte>((void*)allocatedMemory, spaceToAllocate);
}
}

/// <summary>
/// Stack-alloc threshold set to 256 bytes to enable small arrays to be passed on the stack.
/// Number kept small to ensure that P/Invokes with a lot of array parameters doesn't
/// blow the stack since this is a new optimization in the code-generated interop.
/// </summary>
public const int StackBufferSize = 0x200;

public Span<T> ManagedValues => managedArray;

public Span<byte> NativeValueStorage { get; private set; }

public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(NativeValueStorage);

public void SetUnmarshalledCollectionLength(int length)
{
managedArray = new T[length];
}

public byte* Value
{
get
{
Debug.Assert(managedArray is null || allocatedMemory != IntPtr.Zero);
return (byte*)allocatedMemory;
}
set
{
if (value == null)
{
managedArray = null;
NativeValueStorage = default;
}
else
{
allocatedMemory = (IntPtr)value;
NativeValueStorage = new Span<byte>(value, (managedArray?.Length ?? 0) * sizeOfNativeElement);
}
}
}

public T[]? ToManaged() => managedArray;

public void FreeNative()
{
if (allocatedMemory != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(allocatedMemory);
}
}
}

public unsafe ref struct PtrArrayMarshaller<T> where T : unmanaged
{
private T*[]? managedArray;
private readonly int sizeOfNativeElement;
private IntPtr allocatedMemory;

public PtrArrayMarshaller(int sizeOfNativeElement)
: this()
{
this.sizeOfNativeElement = sizeOfNativeElement;
}

public PtrArrayMarshaller(T*[]? managed, int sizeOfNativeElement)
{
allocatedMemory = default;
this.sizeOfNativeElement = sizeOfNativeElement;
if (managed is null)
{
managedArray = null;
NativeValueStorage = default;
return;
}
managedArray = managed;
this.sizeOfNativeElement = sizeOfNativeElement;
// Always allocate at least one byte when the array is zero-length.
int spaceToAllocate = Math.Max(managed.Length * sizeOfNativeElement, 1);
allocatedMemory = Marshal.AllocCoTaskMem(spaceToAllocate);
NativeValueStorage = new Span<byte>((void*)allocatedMemory, spaceToAllocate);
}

public PtrArrayMarshaller(T*[]? managed, Span<byte> stackSpace, int sizeOfNativeElement)
{
allocatedMemory = default;
this.sizeOfNativeElement = sizeOfNativeElement;
if (managed is null)
{
managedArray = null;
NativeValueStorage = default;
return;
}
managedArray = managed;
// Always allocate at least one byte when the array is zero-length.
int spaceToAllocate = Math.Max(managed.Length * sizeOfNativeElement, 1);
if (spaceToAllocate <= stackSpace.Length)
{
NativeValueStorage = stackSpace[0..spaceToAllocate];
}
else
{
allocatedMemory = Marshal.AllocCoTaskMem(spaceToAllocate);
NativeValueStorage = new Span<byte>((void*)allocatedMemory, spaceToAllocate);
}
}

/// <summary>
/// Stack-alloc threshold set to 256 bytes to enable small arrays to be passed on the stack.
/// Number kept small to ensure that P/Invokes with a lot of array parameters doesn't
/// blow the stack since this is a new optimization in the code-generated interop.
/// </summary>
public const int StackBufferSize = 0x200;

public Span<IntPtr> ManagedValues => Unsafe.As<IntPtr[]>(managedArray);

public Span<byte> NativeValueStorage { get; private set; }

public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(NativeValueStorage);

public void SetUnmarshalledCollectionLength(int length)
{
managedArray = new T*[length];
}

public byte* Value
{
get
{
Debug.Assert(managedArray is null || allocatedMemory != IntPtr.Zero);
return (byte*)allocatedMemory;
}
set
{
if (value == null)
{
managedArray = null;
NativeValueStorage = default;
}
else
{
allocatedMemory = (IntPtr)value;
NativeValueStorage = new Span<byte>(value, (managedArray?.Length ?? 0) * sizeOfNativeElement);
}

}
}

public T*[]? ToManaged() => managedArray;

public void FreeNative()
{
if (allocatedMemory != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(allocatedMemory);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
namespace System.Runtime.InteropServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
class GeneratedMarshallingAttribute : Attribute
sealed class GeneratedMarshallingAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Struct)]
public class BlittableTypeAttribute : Attribute
public sealed class BlittableTypeAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)]
public class NativeMarshallingAttribute : Attribute
public sealed class NativeMarshallingAttribute : Attribute
{
public NativeMarshallingAttribute(Type nativeType)
{
Expand All @@ -22,14 +22,36 @@ public NativeMarshallingAttribute(Type nativeType)
public Type NativeType { get; }
}

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.Field)]
public class MarshalUsingAttribute : Attribute
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.Field, AllowMultiple = true)]
public sealed class MarshalUsingAttribute : Attribute
{
public MarshalUsingAttribute()
{
CountElementName = string.Empty;
}

public MarshalUsingAttribute(Type nativeType)
:this()
{
NativeType = nativeType;
}

public Type NativeType { get; }
public Type? NativeType { get; }

public string CountElementName { get; set; }

public int ConstantElementCount { get; set; }

public int ElementIndirectionLevel { get; set; }

public const string ReturnsCountValue = "return-value";
}

[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)]
public sealed class GenericContiguousCollectionMarshallerAttribute : Attribute
{
public GenericContiguousCollectionMarshallerAttribute()
{
}
}
}
Loading