Skip to content

Commit

Permalink
[LibraryImportGenerator] Fix pointer array marshalling (#67988)
Browse files Browse the repository at this point in the history
  • Loading branch information
elinor-fung committed Apr 18, 2022
1 parent 6cdb63a commit 74e1d4d
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 51 deletions.
23 changes: 9 additions & 14 deletions docs/design/libraries/LibraryImportGenerator/SpanMarshallers.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Introduce a marshaller kind named `LinearCollection`.

```diff
namespace System.Runtime.InteropServices
{
{
[AttributeUsage(AttributeTargets.Struct)]
public sealed class CustomTypeMarshallerAttribute : Attribute
{
Expand Down Expand Up @@ -154,16 +154,11 @@ public struct GenericContiguousCollectionMarshallerImpl<T, U, V,...>
/// <summary>
/// A span that points to the memory where the native values of the collection are stored after the native call.
/// </summary>
public ReadOnlySpan<TCollectionElement> GetNativeValuesSource(int length);
/// <summary>
/// A span that points to the memory where the native values of the collection should be stored.
/// </summary>
public Span<TCollectionElement> GetNativeValuesDestination();

public ReadOnlySpan<byte> GetNativeValuesSource(int length);
/// <summary>
/// A span that points to the memory where the native values of the collection should be stored.
/// </summary>
public unsafe Span<byte> NativeValueStorage { get; }
public Span<byte> GetNativeValuesDestination();

// The requirements on the TNative type are the same as when used with `NativeTypeMarshallingAttribute`.
// The property is required with the generic collection marshalling.
Expand All @@ -175,7 +170,7 @@ public struct GenericContiguousCollectionMarshallerImpl<T, U, V,...>

The constructors now require an additional `int` parameter specifying the native size of a collection element. The collection element type is represented as `TCollectionElement` above, and can be any type the marshaller defines. As the elements may be marshalled to types with different native sizes than managed, this enables the author of the generic collection marshaller to not need to know how to marshal the elements of the collection, just the collection structure itself.

When the elements of the collection are blittable, the marshaller will emit a block copy of the span `ManagedValues` to the destination `NativeValueStorage`. When the elements are not blittable, the marshaller will emit a loop that will marshal the elements of the managed span one at a time and store them in the `NativeValueStorage` span.
When the elements of the collection are blittable, the marshaller will emit a block copy of the span `GetManagedValuesSource`/`GetNativeValuesSource` to `GetNativeValuesDestination`/`GetManagedValuesDestination`. When the elements are not blittable, the marshaller will emit a loop that will marshal the elements of the managed span one at a time and store them in the `NativeValueStorage` span.

This would enable similar performance metrics as the current support for arrays as well as Design 1's support for the span types when the element type is blittable.

Expand Down Expand Up @@ -227,7 +222,7 @@ public ref struct SpanMarshaller<T>
private Span<T> managedCollection;

private int nativeElementSize;

private IntPtr Value { get; set; }

public SpanMarshaller(Span<T> collection, int nativeSizeOfElement)
Expand All @@ -239,7 +234,7 @@ public ref struct SpanMarshaller<T>
}

public ReadOnlySpan<T> GetManagedValuesSource() => managedCollection;

public Span<T> GetManagedValuesDestination(int length) => managedCollection = new T[length];

public unsafe Span<byte> GetNativeValuesDestination() => MemoryMarshal.CreateSpan(ref *(byte*)(Value), managedCollection.Length);
Expand All @@ -249,7 +244,7 @@ public ref struct SpanMarshaller<T>
public Span<T> ToManaged() => managedCollection;

public IntPtr ToNativeValue() => Value;

public void FromNativeValue(IntPtr value) => Value = value;

public void FreeNative()
Expand Down Expand Up @@ -328,8 +323,8 @@ public struct GenericContiguousCollectionMarshallerImpl<T, U, V,...>

- public ReadOnlySpan<TCollectionElement> GetManagedValuesSource();
- public Span<TCollectionElement> GetManagedValuesDestination(int length);
- public ReadOnlySpan<TCollectionElement> GetNativeValuesSource(int length);
- public Span<TCollectionElement> GetNativeValuesDestination();
- public ReadOnlySpan<byte> GetNativeValuesSource(int length);
- public Span<byte> GetNativeValuesDestination();

+ public ref byte GetOffsetForNativeValueAtIndex(int index);
+ public TCollectionElement GetManagedValueAtIndex(int index);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ private IEnumerable<StatementSyntax> GeneratePinningPath(TypePositionInfo info,
{
(string managedIdentifer, string nativeIdentifier) = context.GetIdentifiers(info);
string byRefIdentifier = $"__byref_{managedIdentifer}";
TypeSyntax arrayElementType = _elementType;

// The element type here is used only for refs/pointers. In the pointer array case, we use byte as the basic placeholder type,
// since we can't use pointer types in generic type parameters.
bool isPointerArray = info.ManagedType is SzArrayType arrayType && arrayType.ElementTypeInfo is PointerTypeInfo;
TypeSyntax arrayElementType = isPointerArray ? PredefinedType(Token(SyntaxKind.ByteKeyword)) : _elementType;
if (context.CurrentStage == StubCodeContext.Stage.Marshal)
{
// [COMPAT] We use explicit byref calculations here instead of just using a fixed statement
Expand Down Expand Up @@ -127,21 +131,31 @@ private IEnumerable<StatementSyntax> GeneratePinningPath(TypePositionInfo info,
}
if (context.CurrentStage == StubCodeContext.Stage.Pin)
{
// fixed (<nativeType> <nativeIdentifier> = &Unsafe.As<elementType, byte>(ref <byrefIdentifier>))
TypeSyntax nativeType = AsNativeType(info);

// We skip the Unsafe.As if the element type and native element type are equivalent (ignoring trivia differences)
// &<byrefIdentifier>
// or
// &Unsafe.As<elementType, nativeElementType>(ref <byrefIdentifier>)
TypeSyntax nativeElementType = nativeType is PointerTypeSyntax pointerType ? pointerType.ElementType : nativeType;
var initializer = arrayElementType.IsEquivalentTo(nativeElementType, topLevel: true)
? PrefixUnaryExpression(SyntaxKind.AddressOfExpression, IdentifierName(byRefIdentifier))
: PrefixUnaryExpression(SyntaxKind.AddressOfExpression,
InvocationExpression(
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
ParseTypeName(TypeNames.System_Runtime_CompilerServices_Unsafe),
GenericName("As").AddTypeArgumentListArguments(
arrayElementType,
nativeElementType)))
.AddArgumentListArguments(
Argument(IdentifierName(byRefIdentifier))
.WithRefKindKeyword(Token(SyntaxKind.RefKeyword))));

// fixed (<nativeType> <nativeIdentifier> = <initializer>)
yield return FixedStatement(
VariableDeclaration(AsNativeType(info), SingletonSeparatedList(
VariableDeclaration(nativeType, SingletonSeparatedList(
VariableDeclarator(nativeIdentifier)
.WithInitializer(EqualsValueClause(
PrefixUnaryExpression(SyntaxKind.AddressOfExpression,
InvocationExpression(
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
ParseTypeName(TypeNames.System_Runtime_CompilerServices_Unsafe),
GenericName("As").AddTypeArgumentListArguments(
arrayElementType,
PredefinedType(Token(SyntaxKind.ByteKeyword)))))
.AddArgumentListArguments(
Argument(IdentifierName(byRefIdentifier))
.WithRefKindKeyword(Token(SyntaxKind.RefKeyword)))))))),
.WithInitializer(EqualsValueClause(initializer)))),
EmptyStatement());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,13 @@ private CountInfo CreateCountInfo(AttributeData marshalUsingData, ImmutableHashS

ITypeSymbol? nativeValueType = ManualTypeMarshallingHelper.FindToNativeValueMethod(arrayMarshaller)?.ReturnType;

INamedTypeSymbol readOnlySpanOfT = _compilation.GetTypeByMetadataName(TypeNames.System_ReadOnlySpan_Metadata)!;
if (!ManualTypeMarshallingHelper.TryGetElementTypeFromLinearCollectionMarshaller(arrayMarshaller, readOnlySpanOfT, out elementType))
{
Debug.Fail("Runtime-provided array marshallers should have a valid shape");
return NoMarshallingInfo.Instance;
}

return new NativeLinearCollectionMarshallingInfo(
NativeMarshallingType: ManagedTypeInfo.CreateTypeInfoForTypeSymbol(arrayMarshaller),
NativeValueType: nativeValueType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(nativeValueType) : null,
Expand Down
Loading

0 comments on commit 74e1d4d

Please sign in to comment.