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 the non-blittable type marshalling proposal #302

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
edcfded
First pass adding custom type marshaller.
jkoritzinsky Sep 25, 2020
40c8b87
Add support for a byref Value property and remove implementation rest…
jkoritzinsky Nov 3, 2020
fcdaa86
Add some negative tests for the custom native type marshalling.
jkoritzinsky Nov 4, 2020
086ef1c
Don't use stackalloc constructor if refkind is 'ref'.
jkoritzinsky Nov 4, 2020
f66d1d8
Add integration tests for non-blittable structs.
jkoritzinsky Nov 4, 2020
481bca3
Add more tests
jkoritzinsky Nov 4, 2020
60362ac
Merge branch 'feature/DllImportGenerator' of github.com:dotnet/runtim…
jkoritzinsky Nov 9, 2020
e26027d
Allow a GetPinnableReference call on the native type instead of allow…
jkoritzinsky Nov 12, 2020
7d729e1
Add missing cast.
jkoritzinsky Nov 13, 2020
e2e2f0e
Call GetPinnableReference.
jkoritzinsky Nov 13, 2020
b661b48
Assign stackalloc to pointer before creating span to work around life…
jkoritzinsky Nov 13, 2020
433275e
Merge branch 'feature/DllImportGenerator' of github.com:dotnet/runtim…
jkoritzinsky Nov 14, 2020
32c6336
Add custom message for errors.
jkoritzinsky Nov 14, 2020
7116872
Merge branch 'feature/DllImportGenerator' of github.com:dotnet/runtim…
jkoritzinsky Nov 16, 2020
f0b8241
Fix simple flags logic error.
jkoritzinsky Nov 16, 2020
4fae329
Move tests to CompileFails.cs
jkoritzinsky Nov 17, 2020
b359225
Add analyzer error for ref Value property.
jkoritzinsky Nov 17, 2020
563d39f
Update comments.
jkoritzinsky Nov 17, 2020
dfd4e26
Add simple MarshalUsing test
jkoritzinsky Nov 17, 2020
8bc0d70
Add more tests. Fix a bug with using a marshaller that supports pinni…
jkoritzinsky Nov 17, 2020
af8b53f
Fix typo in DNNE type hint attribute.
jkoritzinsky Nov 17, 2020
cba7503
Unify on boolean operators at the start of a line instead of at the e…
jkoritzinsky Nov 18, 2020
5be8022
Minor cleanup
jkoritzinsky Nov 19, 2020
28c1c58
Rename tests.
jkoritzinsky Nov 19, 2020
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
54 changes: 37 additions & 17 deletions DllImportGenerator/DllImportGenerator.UnitTests/CodeSnippets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -667,25 +667,55 @@ public nint Value
}
";

public static string CustomStructMarshallingByRefValuePropertyIn = @"
public static string CustomStructMarshallingNativeTypePinnable = @"
using System.Runtime.InteropServices;
using System;

[NativeMarshalling(typeof(Native))]
class S
{
public byte c;
}

unsafe struct Native
unsafe ref struct Native
{
private S value;
private byte* ptr;
private Span<byte> stackBuffer;

public Native(S s) : this()
{
value = s;
ptr = (byte*)Marshal.AllocCoTaskMem(sizeof(byte));
*ptr = s.c;
}

public ref byte Value { get => ref value.c; }
public Native(S s, Span<byte> buffer) : this()
{
stackBuffer = buffer;
stackBuffer[0] = s.c;
}

public ref byte GetPinnableReference() => ref (ptr != null ? ref *ptr : ref stackBuffer.GetPinnableReference());

public S ToManaged()
{
return new S { c = *ptr };
}

public byte* Value
{
get => ptr != null ? ptr : throw new InvalidOperationException();
set => ptr = value;
}

public void FreeNative()
{
if (ptr != null)
{
Marshal.FreeCoTaskMem(ptr);
}
}

public const int StackBufferSize = 1;
}

partial class Test
Expand All @@ -697,13 +727,11 @@ public static partial void Method(
}
";

public static string CustomStructMarshallingByRefValuePropertyRefOutReturn = @"
using System.Runtime.InteropServices;

public static string CustomStructMarshallingByRefValueProperty = BasicParametersAndModifiers("S") + @"
[NativeMarshalling(typeof(Native))]
class S
{
public byte c;
public byte c = 0;
}

unsafe struct Native
Expand All @@ -717,14 +745,6 @@ public Native(S s) : this()

public ref byte Value { get => ref value.c; }
}

partial class Test
{
[GeneratedDllImport(""DoesNotExist"")]
public static partial S Method(
ref S sRef,
out S sOut);
}
";

public static string BasicParameterWithByRefModifier(string byRefKind, string typeName) => @$"
Expand Down
4 changes: 2 additions & 2 deletions DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public static IEnumerable<object[]> CodeSnippetsToCompile_NoDiagnostics()
yield return new[] { CodeSnippets.CustomStructMarshallingOptionalStackallocParametersAndModifiers };
yield return new[] { CodeSnippets.CustomStructMarshallingValuePropertyParametersAndModifiers };
yield return new[] { CodeSnippets.CustomStructMarshallingPinnableParametersAndModifiers };
yield return new[] { CodeSnippets.CustomStructMarshallingByRefValuePropertyIn };
yield return new[] { CodeSnippets.CustomStructMarshallingNativeTypePinnable };
}

public static IEnumerable<object[]> CodeSnippetsToCompile_WithDiagnostics()
Expand Down Expand Up @@ -176,7 +176,7 @@ public static IEnumerable<object[]> CodeSnippetsToCompile_WithDiagnostics()
yield return new[] { CodeSnippets.PreserveSigFalse<string[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<IntPtr[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<UIntPtr[]>() };
yield return new[] { CodeSnippets.CustomStructMarshallingByRefValuePropertyRefOutReturn };
yield return new[] { CodeSnippets.CustomStructMarshallingByRefValueProperty };
yield return new[] { CodeSnippets.CustomStructMarshallingManagedToNativeOnlyOutParameter };
yield return new[] { CodeSnippets.CustomStructMarshallingManagedToNativeOnlyReturnValue };
yield return new[] { CodeSnippets.CustomStructMarshallingNativeToManagedOnlyInParameter };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ public Native(S s) : this()
}";

await VerifyCS.VerifyAnalyzerAsync(source,
VerifyCS.Diagnostic(NativeTypeMustBePointerSizedOrByRefRule).WithSpan(24, 5, 24, 42).WithArguments("int", "S"));
VerifyCS.Diagnostic(NativeTypeMustBePointerSizedRule).WithSpan(24, 5, 24, 42).WithArguments("int", "S"));
}

[Fact]
Expand Down Expand Up @@ -499,7 +499,7 @@ public Native(S s) : this()
}

[Fact]
public async Task TypeWithGetPinnableReferenceByRefReturnType_DoesNotReportDiagnostic()
public async Task TypeWithGetPinnableReferenceByRefReturnType_ReportsDiagnostic()
{
string source = @"
using System;
Expand All @@ -525,7 +525,39 @@ public Native(S s) : this()
public ref byte Value { get => ref value.GetPinnableReference(); }
}";

await VerifyCS.VerifyAnalyzerAsync(source);
await VerifyCS.VerifyAnalyzerAsync(source,
VerifyCS.Diagnostic(NativeTypeMustBePointerSizedRule).WithSpan(22, 5, 22, 71).WithArguments("ref byte", "S"));
}

[Fact]
public async Task NativeTypeWithGetPinnableReferenceByRefReturnType_ReportsDiagnostic()
{
string source = @"
using System;
using System.Runtime.InteropServices;

[NativeMarshalling(typeof(Native))]
class S
{
public byte c;
}

unsafe struct Native
{
private S value;

public Native(S s) : this()
{
value = s;
}

public ref byte GetPinnableReference() => ref value.c;

public ref byte Value { get => ref GetPinnableReference(); }
}";

await VerifyCS.VerifyAnalyzerAsync(source,
VerifyCS.Diagnostic(NativeTypeMustBePointerSizedRule).WithSpan(22, 5, 22, 65).WithArguments("ref byte", "Native"));
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static class Ids
public const string NativeTypeMustBeNonNull = Prefix + "003";
public const string NativeTypeMustBeBlittable = Prefix + "004";
public const string GetPinnableReferenceReturnTypeBlittable = Prefix + "005";
public const string NativeTypeMustBePointerSizedOrByRef = Prefix + "006";
public const string NativeTypeMustBePointerSized = Prefix + "006";
public const string NativeTypeMustHaveRequiredShape = Prefix + "007";
public const string ValuePropertyMustHaveSetter = Prefix + "008";
public const string ValuePropertyMustHaveGetter = Prefix + "009";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ public class ManualTypeMarshallingAnalyzer : DiagnosticAnalyzer
isEnabledByDefault: true,
description: GetResourceString(nameof(Resources.GetPinnableReferenceReturnTypeBlittableDescription)));

public readonly static DiagnosticDescriptor NativeTypeMustBePointerSizedOrByRefRule =
public readonly static DiagnosticDescriptor NativeTypeMustBePointerSizedRule =
new DiagnosticDescriptor(
Ids.NativeTypeMustBePointerSizedOrByRef,
"NativeTypeMustBePointerSizedOrByRef",
GetResourceString(nameof(Resources.NativeTypeMustBePointerSizedOrByRefMessage)),
Ids.NativeTypeMustBePointerSized,
"NativeTypeMustBePointerSized",
GetResourceString(nameof(Resources.NativeTypeMustBePointerSizedMessage)),
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: GetResourceString(nameof(Resources.NativeTypeMustBePointerSizedOrByRefDescription)));
description: GetResourceString(nameof(Resources.NativeTypeMustBePointerSizedDescription)));

public readonly static DiagnosticDescriptor NativeTypeMustHaveRequiredShapeRule =
new DiagnosticDescriptor(
Expand Down Expand Up @@ -141,7 +141,7 @@ public class ManualTypeMarshallingAnalyzer : DiagnosticAnalyzer
NativeTypeMustBeNonNullRule,
NativeTypeMustBeBlittableRule,
GetPinnableReferenceReturnTypeBlittableRule,
NativeTypeMustBePointerSizedOrByRefRule,
NativeTypeMustBePointerSizedRule,
NativeTypeMustHaveRequiredShapeRule,
ValuePropertyMustHaveSetterRule,
ValuePropertyMustHaveGetterRule,
Expand Down Expand Up @@ -242,7 +242,7 @@ public void AnalyzeTypeDefinition(SymbolAnalysisContext context)
}
else if (nativeMarshallingAttributeData is not null)
{
AnalyzeNativeMarshalerType(context, type, nativeMarshallingAttributeData, validateGetPinnableReference: true, validateAllScenarioSupport: true);
AnalyzeNativeMarshalerType(context, type, nativeMarshallingAttributeData, validateManagedGetPinnableReference: true, validateAllScenarioSupport: true);
}
}

Expand Down Expand Up @@ -283,7 +283,7 @@ public void AnalyzeReturnType(SymbolAnalysisContext context)
}
}

private void AnalyzeNativeMarshalerType(SymbolAnalysisContext context, ITypeSymbol type, AttributeData nativeMarshalerAttributeData, bool validateGetPinnableReference, bool validateAllScenarioSupport)
private void AnalyzeNativeMarshalerType(SymbolAnalysisContext context, ITypeSymbol type, AttributeData nativeMarshalerAttributeData, bool validateManagedGetPinnableReference, bool validateAllScenarioSupport)
{
if (nativeMarshalerAttributeData.ConstructorArguments[0].IsNull)
{
Expand Down Expand Up @@ -370,37 +370,43 @@ valueProperty is not null
type.ToDisplayString()));
}

IMethodSymbol? getPinnableReferenceMethod = type.GetMembers("GetPinnableReference")
.OfType<IMethodSymbol>()
.FirstOrDefault(m => m is { Parameters: { Length: 0 } } and ({ ReturnsByRef: true } or { ReturnsByRefReadonly: true }));
if (validateGetPinnableReference && getPinnableReferenceMethod is not null)
IMethodSymbol? managedGetPinnableReferenceMethod = ManualTypeMarshallingHelper.FindGetPinnableReference(type);
if (validateManagedGetPinnableReference && managedGetPinnableReferenceMethod is not null)
{
if (!getPinnableReferenceMethod.ReturnType.IsConsideredBlittable())
if (!managedGetPinnableReferenceMethod.ReturnType.IsConsideredBlittable())
{
context.ReportDiagnostic(Diagnostic.Create(GetPinnableReferenceReturnTypeBlittableRule, getPinnableReferenceMethod.DeclaringSyntaxReferences[0].GetSyntax().GetLocation()));
context.ReportDiagnostic(Diagnostic.Create(GetPinnableReferenceReturnTypeBlittableRule, managedGetPinnableReferenceMethod.DeclaringSyntaxReferences[0].GetSyntax().GetLocation()));
}
// Validate that the Value property is a pointer-sized primitive type
// or a byref to the same type as GetPinnableReference returns.
// Validate that our marshaler supports scenarios where GetPinnableReference cannot be used.
if (validateAllScenarioSupport && (!hasConstructor || valueProperty is { GetMethod: null }))
{
context.ReportDiagnostic(Diagnostic.Create(GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule, nativeMarshalerAttributeData.ApplicationSyntaxReference!.GetSyntax().GetLocation(), type.ToDisplayString()));
}
}

if ((validateManagedGetPinnableReference && managedGetPinnableReferenceMethod is not null) ||
ManualTypeMarshallingHelper.FindGetPinnableReference(marshalerType) is not null)
{
// Validate that the Value property is a pointer-sized primitive type.
if (valueProperty is null ||
(valueProperty.Type is not (
IPointerTypeSymbol _ or
{ SpecialType: SpecialType.System_IntPtr } or
{ SpecialType: SpecialType.System_UIntPtr }) &&
!((valueProperty.ReturnsByRef || valueProperty.ReturnsByRefReadonly) &&
SymbolEqualityComparer.Default.Equals(getPinnableReferenceMethod.ReturnType, valueProperty.Type))))
{ SpecialType: SpecialType.System_UIntPtr })))
{
context.ReportDiagnostic(Diagnostic.Create(NativeTypeMustBePointerSizedOrByRefRule,
valueProperty is not null
? GetSyntaxReferenceForDiagnostic(valueProperty).GetSyntax().GetLocation()
: GetSyntaxReferenceForDiagnostic(nativeType).GetSyntax().GetLocation(),
nativeType.ToDisplayString(),
type.ToDisplayString()));
}
ITypeSymbol typeWithGetPinnableReference = managedGetPinnableReferenceMethod is not null
? type
: marshalerType;

// Validate that our marshaler supports scenarios where GetPinnableReference cannot be used.
if (validateAllScenarioSupport && (!hasConstructor || valueProperty is { GetMethod: null }))
{
context.ReportDiagnostic(Diagnostic.Create(GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule, nativeMarshalerAttributeData.ApplicationSyntaxReference!.GetSyntax().GetLocation(), type.ToDisplayString()));
bool valuePropertyIsRefReturn = valueProperty is { ReturnsByRef : true } or { ReturnsByRefReadonly: true };
context.ReportDiagnostic(Diagnostic.Create(NativeTypeMustBePointerSizedRule,
valueProperty is not null
? GetSyntaxReferenceForDiagnostic(valueProperty).GetSyntax().GetLocation()
: GetSyntaxReferenceForDiagnostic(nativeType).GetSyntax().GetLocation(),
valuePropertyIsRefReturn
? $"ref {nativeType.ToDisplayString()}"
: nativeType.ToDisplayString(),
typeWithGetPinnableReference.ToDisplayString()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class CustomNativeTypeMarshaler : IMarshallingGenerator
private readonly SupportedMarshallingMethods _marshallingMethods;
private readonly bool _hasFreeNative;
private readonly bool _useValueProperty;
private readonly bool _valuePropertyRequiresPinning;
private readonly bool _marshalerTypePinnable;

public CustomNativeTypeMarshaler(NativeMarshallingAttributeInfo marshallingInfo)
{
Expand All @@ -28,7 +28,7 @@ public CustomNativeTypeMarshaler(NativeMarshallingAttributeInfo marshallingInfo)
_marshallingMethods = marshallingInfo.MarshallingMethods;
_hasFreeNative = ManualTypeMarshallingHelper.HasFreeNativeMethod(marshallingInfo.NativeMarshallingType);
_useValueProperty = marshallingInfo.ValuePropertyType != null;
_valuePropertyRequiresPinning = marshallingInfo.ValuePropertyRequiresPinning;
_marshalerTypePinnable = marshallingInfo.NativeTypePinnable;
}

public CustomNativeTypeMarshaler(GeneratedNativeMarshallingAttributeInfo marshallingInfo)
Expand All @@ -37,7 +37,7 @@ public CustomNativeTypeMarshaler(GeneratedNativeMarshallingAttributeInfo marshal
_marshallingMethods = SupportedMarshallingMethods.ManagedToNative | SupportedMarshallingMethods.NativeToManaged;
_hasFreeNative = true;
_useValueProperty = false;
_valuePropertyRequiresPinning = false;
_marshalerTypePinnable = false;
}

public TypeSyntax AsNativeType(TypePositionInfo info)
Expand Down Expand Up @@ -102,7 +102,7 @@ public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeCont
switch (context.CurrentStage)
{
case StubCodeContext.Stage.Setup:
if (!_valuePropertyRequiresPinning)
if (!_marshalerTypePinnable || info.IsByRef && info.RefKind != RefKind.In)
{
yield return LocalDeclarationStatement(
VariableDeclaration(
Expand Down Expand Up @@ -154,7 +154,7 @@ public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeCont
ObjectCreationExpression(_nativeLocalTypeSyntax)
.WithArgumentList(ArgumentList(SeparatedList(arguments)))));

if (_useValueProperty && !_valuePropertyRequiresPinning)
if (_useValueProperty && !_marshalerTypePinnable)
{
// <nativeIdentifier> = <marshalerIdentifier>.Value;
yield return ExpressionStatement(
Expand All @@ -168,22 +168,17 @@ public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeCont
}
break;
case StubCodeContext.Stage.Pin:
if (_valuePropertyRequiresPinning)
if (_marshalerTypePinnable && (!info.IsByRef || info.RefKind == RefKind.In))
{
// A value property that requires pinning can only be used as a by value or in parameter.
Debug.Assert(info.RefKind != RefKind.Out && !info.IsManagedReturnPosition);

// fixed (<_nativeTypeSyntax> <nativeIdentifier> = &<marshalerIdentifier>.Value)
// fixed (<_nativeTypeSyntax> <nativeIdentifier> = &<marshalerIdentifier>)
yield return FixedStatement(
VariableDeclaration(
_nativeTypeSyntax,
SingletonSeparatedList(
VariableDeclarator(nativeIdentifier)
.WithInitializer(EqualsValueClause(
PrefixUnaryExpression(SyntaxKind.AddressOfExpression,
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(marshalerIdentifier),
IdentifierName(ManualTypeMarshallingHelper.ValuePropertyName))))))),
IdentifierName(marshalerIdentifier)))))),
EmptyStatement());
}
break;
Expand Down Expand Up @@ -232,9 +227,22 @@ public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeCont

public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context)
{
return info.IsManagedReturnPosition
|| !(context.PinningSupported && (_marshallingMethods & SupportedMarshallingMethods.Pinning) != 0)
|| !_valuePropertyRequiresPinning;
if (info.IsManagedReturnPosition || info.IsByRef && info.RefKind != RefKind.In)
{
return true;
}
if (context.PinningSupported)
{
if (!info.IsByRef && (_marshallingMethods & SupportedMarshallingMethods.Pinning) == 0)
{
return false;
}
else if (_marshalerTypePinnable)
{
return false;
}
}
return true;
}
}
}
Loading