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

Add TFM and version support query to marshaller interface. #61064

Merged
merged 4 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ internal static class Comparers
/// <summary>
/// Comparer for an individual generated stub source as a syntax tree and the generated diagnostics for the stub.
/// </summary>
public static readonly IEqualityComparer<(MemberDeclarationSyntax Syntax, ImmutableArray<Diagnostic> Diagnostics)> GeneratedSyntax = new CustomValueTupleElementComparer<MemberDeclarationSyntax, ImmutableArray<Diagnostic>>(new SyntaxEquivalentComparer(), new ImmutableArraySequenceEqualComparer<Diagnostic>(EqualityComparer<Diagnostic>.Default));
public static readonly IEqualityComparer<(MemberDeclarationSyntax Syntax, ImmutableArray<Diagnostic> Diagnostics)> GeneratedSyntax = new CustomValueTupleElementComparer<MemberDeclarationSyntax, ImmutableArray<Diagnostic>>(SyntaxEquivalentComparer.Instance, new ImmutableArraySequenceEqualComparer<Diagnostic>(EqualityComparer<Diagnostic>.Default));

/// <summary>
/// Comparer for the context used to generate a stub and the original user-provided syntax that triggered stub creation.
/// </summary>
public static readonly IEqualityComparer<(MethodDeclarationSyntax Syntax, DllImportGenerator.IncrementalStubGenerationContext StubContext)> CalculatedContextWithSyntax = new CustomValueTupleElementComparer<MethodDeclarationSyntax, DllImportGenerator.IncrementalStubGenerationContext>(new SyntaxEquivalentComparer(), EqualityComparer<DllImportGenerator.IncrementalStubGenerationContext>.Default);
public static readonly IEqualityComparer<(MethodDeclarationSyntax Syntax, DllImportGenerator.IncrementalStubGenerationContext StubContext)> CalculatedContextWithSyntax = new CustomValueTupleElementComparer<MethodDeclarationSyntax, DllImportGenerator.IncrementalStubGenerationContext>(SyntaxEquivalentComparer.Instance, EqualityComparer<DllImportGenerator.IncrementalStubGenerationContext>.Default);
}

/// <summary>
Expand Down Expand Up @@ -63,6 +63,10 @@ public int GetHashCode(ImmutableArray<T> obj)

internal class SyntaxEquivalentComparer : IEqualityComparer<SyntaxNode>
{
public static readonly SyntaxEquivalentComparer Instance = new();

private SyntaxEquivalentComparer() { }

public bool Equals(SyntaxNode x, SyntaxNode y)
{
return x.IsEquivalentTo(y);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,20 @@ public sealed class DllImportGenerator : IIncrementalGenerator
private const string GeneratedDllImport = nameof(GeneratedDllImport);
private const string GeneratedDllImportAttribute = nameof(GeneratedDllImportAttribute);

private static readonly Version s_minimumSupportedFrameworkVersion = new Version(5, 0);

internal sealed record IncrementalStubGenerationContext(DllImportStubContext StubContext, ImmutableArray<AttributeSyntax> ForwardedAttributes, GeneratedDllImportData DllImportData, ImmutableArray<Diagnostic> Diagnostics)
internal sealed record IncrementalStubGenerationContext(
StubEnvironment Environment,
DllImportStubContext StubContext,
ImmutableArray<AttributeSyntax> ForwardedAttributes,
GeneratedDllImportData DllImportData,
ImmutableArray<Diagnostic> Diagnostics)
{
public bool Equals(IncrementalStubGenerationContext? other)
{
return other is not null
&& StubEnvironment.AreCompilationSettingsEqual(Environment, other.Environment)
&& StubContext.Equals(other.StubContext)
&& DllImportData.Equals(other.DllImportData)
&& ForwardedAttributes.SequenceEqual(other.ForwardedAttributes, (IEqualityComparer<AttributeSyntax>)new SyntaxEquivalentComparer())
&& ForwardedAttributes.SequenceEqual(other.ForwardedAttributes, (IEqualityComparer<AttributeSyntax>)SyntaxEquivalentComparer.Instance)
&& Diagnostics.SequenceEqual(other.Diagnostics);
}

Expand Down Expand Up @@ -93,28 +97,28 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, refReturnMethod.Syntax.GetLocation(), "ref return", refReturnMethod.Symbol.ToDisplayString()));
});

IncrementalValueProvider<(Compilation compilation, bool isSupported, Version targetFrameworkVersion)> compilationAndTargetFramework = context.CompilationProvider
IncrementalValueProvider<(Compilation compilation, TargetFramework targetFramework, Version targetFrameworkVersion)> compilationAndTargetFramework = context.CompilationProvider
.Select(static (compilation, ct) =>
{
bool isSupported = IsSupportedTargetFramework(compilation, out Version targetFrameworkVersion);
return (compilation, isSupported, targetFrameworkVersion);
TargetFramework fmk = DetermineTargetFramework(compilation, out Version targetFrameworkVersion);
return (compilation, fmk, targetFrameworkVersion);
});

context.RegisterSourceOutput(
compilationAndTargetFramework
.Combine(methodsToGenerate.Collect()),
static (context, data) =>
{
if (!data.Left.isSupported && data.Right.Any())
if (data.Left.targetFramework is TargetFramework.Unknown && data.Right.Any())
{
// We don't block source generation when the TFM is unsupported.
// We don't block source generation when the TFM is unknown.
// This allows a user to copy generated source and use it as a starting point
// for manual marshalling if desired.
context.ReportDiagnostic(
Diagnostic.Create(
GeneratorDiagnostics.TargetFrameworkNotSupported,
Location.None,
s_minimumSupportedFrameworkVersion.ToString(2)));
data.Left.targetFrameworkVersion));
}
});

Expand All @@ -127,7 +131,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
static (data, ct) =>
new StubEnvironment(
data.Left.compilation,
data.Left.isSupported,
data.Left.targetFramework,
data.Left.targetFrameworkVersion,
data.Left.compilation.SourceModule.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute),
data.Right)
Expand Down Expand Up @@ -312,20 +316,21 @@ private static MemberDeclarationSyntax WrapMethodInContainingScopes(DllImportStu
return toPrint;
}

private static bool IsSupportedTargetFramework(Compilation compilation, out Version version)
private static TargetFramework DetermineTargetFramework(Compilation compilation, out Version version)
{
IAssemblySymbol systemAssembly = compilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly;
version = systemAssembly.Identity.Version;

return systemAssembly.Identity.Name switch
{
// .NET Framework
"mscorlib" => false,
"mscorlib" => TargetFramework.Framework,
// .NET Standard
"netstandard" => false,
"netstandard" => TargetFramework.Standard,
// .NET Core (when version < 5.0) or .NET
"System.Runtime" or "System.Private.CoreLib" => version >= s_minimumSupportedFrameworkVersion,
_ => false,
"System.Runtime" or "System.Private.CoreLib" =>
(version.Major < 5) ? TargetFramework.Core : TargetFramework.Net,
_ => TargetFramework.Unknown,
};
}

Expand All @@ -339,7 +344,6 @@ private static GeneratedDllImportData ProcessGeneratedDllImportAttribute(Attribu
throw new InvalidProgramException();
}


// Default values for these properties are based on the
// documented semanatics of DllImportAttribute:
// - https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute
Expand Down Expand Up @@ -473,7 +477,7 @@ private static IncrementalStubGenerationContext CalculateStubInformation(IMethod
// Create the stub.
var dllImportStub = DllImportStubContext.Create(symbol, stubDllImportData, environment, generatorDiagnostics, ct);

return new IncrementalStubGenerationContext(dllImportStub, additionalAttributes.ToImmutableArray(), stubDllImportData, generatorDiagnostics.Diagnostics.ToImmutableArray());
return new IncrementalStubGenerationContext(environment, dllImportStub, additionalAttributes.ToImmutableArray(), stubDllImportData, generatorDiagnostics.Diagnostics.ToImmutableArray());
}

private (MemberDeclarationSyntax, ImmutableArray<Diagnostic>) GenerateSource(
Expand All @@ -490,12 +494,16 @@ private static IncrementalStubGenerationContext CalculateStubInformation(IMethod

// Generate stub code
var stubGenerator = new PInvokeStubCodeGenerator(
dllImportStub.Environment,
dllImportStub.StubContext.ElementTypeInformation,
dllImportStub.DllImportData.SetLastError && !options.GenerateForwarders,
(elementInfo, ex) => diagnostics.ReportMarshallingNotSupported(originalSyntax, elementInfo, ex.NotSupportedDetails),
dllImportStub.StubContext.GeneratorFactory);

if (stubGenerator.StubIsBasicForwarder)
// Check if the generator should produce a forwarder stub - regular DllImport.
// This is done if the signature is blittable or the target framework is not supported.
if (stubGenerator.StubIsBasicForwarder
|| !stubGenerator.SupportsTargetFramework)
{
return (PrintForwarderStub(originalSyntax, dllImportStub), dllImportStub.Diagnostics.AddRange(diagnostics.Diagnostics));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,27 @@ namespace Microsoft.Interop
{
internal record StubEnvironment(
Compilation Compilation,
bool SupportedTargetFramework,
TargetFramework TargetFramework,
Version TargetFrameworkVersion,
bool ModuleSkipLocalsInit,
DllImportGeneratorOptions Options);
DllImportGeneratorOptions Options)
{
/// <summary>
/// Override for determining if two StubEnvironment instances are
/// equal. This intentionally excludes the Compilation instance
/// since that represents the actual compilation and not just the settings.
/// </summary>
/// <param name="env1">The first StubEnvironment</param>
/// <param name="env2">The second StubEnvironment</param>
/// <returns>True if the settings are equal, otherwise false.</returns>
public static bool AreCompilationSettingsEqual(StubEnvironment env1, StubEnvironment env2)
{
return env1.TargetFramework == env2.TargetFramework
&& env1.TargetFrameworkVersion == env2.TargetFrameworkVersion
&& env1.ModuleSkipLocalsInit == env2.ModuleSkipLocalsInit
&& env1.Options.Equals(env2.Options);
}
}

internal sealed class DllImportStubContext : IEquatable<DllImportStubContext>
{
Expand Down Expand Up @@ -164,7 +181,6 @@ private static (ImmutableArray<TypePositionInfo>, IMarshallingGeneratorFactory)
NativeIndex = typeInfos.Count
};
typeInfos.Add(typeInfo);

}

TypePositionInfo retTypeInfo = new(ManagedTypeInfo.CreateTypeInfoForTypeSymbol(method.ReturnType), marshallingAttributeParser.ParseMarshallingInfo(method.ReturnType, method.GetReturnTypeAttributes()));
Expand Down Expand Up @@ -234,9 +250,9 @@ public bool Equals(DllImportStubContext other)
return other is not null
&& StubTypeNamespace == other.StubTypeNamespace
&& ElementTypeInformation.SequenceEqual(other.ElementTypeInformation)
&& StubContainingTypes.SequenceEqual(other.StubContainingTypes, (IEqualityComparer<TypeDeclarationSyntax>)new SyntaxEquivalentComparer())
&& StubContainingTypes.SequenceEqual(other.StubContainingTypes, (IEqualityComparer<TypeDeclarationSyntax>)SyntaxEquivalentComparer.Instance)
&& StubReturnType.IsEquivalentTo(other.StubReturnType)
&& AdditionalAttributes.SequenceEqual(other.AdditionalAttributes, (IEqualityComparer<AttributeListSyntax>)new SyntaxEquivalentComparer())
&& AdditionalAttributes.SequenceEqual(other.AdditionalAttributes, (IEqualityComparer<AttributeListSyntax>)SyntaxEquivalentComparer.Instance)
&& Options.Equals(other.Options);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ private record struct BoundGenerator(TypePositionInfo TypeInfo, IMarshallingGene

public override bool AdditionalTemporaryStateLivesAcrossStages => true;

public bool SupportsTargetFramework { get; init; }

public bool StubIsBasicForwarder { get; init; }

/// <summary>
/// Identifier for managed return value
/// </summary>
Expand All @@ -58,16 +62,28 @@ private record struct BoundGenerator(TypePositionInfo TypeInfo, IMarshallingGene
private readonly List<BoundGenerator> _sortedMarshallers;
private readonly bool _stubReturnsVoid;

public bool StubIsBasicForwarder { get; }

public PInvokeStubCodeGenerator(
StubEnvironment environment,
IEnumerable<TypePositionInfo> argTypes,
bool setLastError,
Action<TypePositionInfo, MarshallingNotSupportedException> marshallingNotSupportedCallback,
IMarshallingGeneratorFactory generatorFactory)
{
_setLastError = setLastError;

// Support for SetLastError logic requires .NET 6+. Initialize the
// supports target framework value with this value.
if (_setLastError)
{
SupportsTargetFramework = environment.TargetFramework == TargetFramework.Net
&& environment.TargetFrameworkVersion.Major >= 6;
}
else
{
SupportsTargetFramework = true;
}

bool noMarshallingNeeded = true;
List<BoundGenerator> allMarshallers = new();
List<BoundGenerator> paramMarshallers = new();
bool foundNativeRetMarshaller = false;
Expand All @@ -78,6 +94,14 @@ public PInvokeStubCodeGenerator(
foreach (TypePositionInfo argType in argTypes)
{
BoundGenerator generator = CreateGenerator(argType);

// Check each marshaler if the current target framework is supported or not.
SupportsTargetFramework &= generator.Generator.IsSupported(environment.TargetFramework, environment.TargetFrameworkVersion);

// Check if generator is either blittable or just a forwarder.
noMarshallingNeeded &= generator is { Generator: BlittableMarshaller, TypeInfo: { IsByRef: false } }
or { Generator: Forwarder };

allMarshallers.Add(generator);
if (argType.IsManagedReturnPosition)
{
Expand Down Expand Up @@ -139,9 +163,7 @@ public PInvokeStubCodeGenerator(

StubIsBasicForwarder = !setLastError
&& managedRetMarshaller.TypeInfo.IsNativeReturnPosition // If the managed return has native return position, then it's the return for both.
&& _sortedMarshallers.All(
m => m is { Generator: BlittableMarshaller, TypeInfo: { IsByRef: false } }
or { Generator: Forwarder });
&& noMarshallingNeeded;

if (managedRetMarshaller.Generator.UsesNativeIdentifier(managedRetMarshaller.TypeInfo, this))
{
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -269,11 +269,11 @@
<value>Native type '{0}' has a stack-allocating constructor does not support marshalling in scenarios where stack allocation is impossible</value>
</data>
<data name="TargetFrameworkNotSupportedDescription" xml:space="preserve">
<value>P/Invoke source generation is only supported on .NET {0} or above. The generated source will not be compatible with other frameworks.</value>
<value>P/Invoke source generation is not supported on unknown target framework v{0}. The generated source will not be compatible with other frameworks.</value>
<comment>{0} is a version number</comment>
</data>
<data name="TargetFrameworkNotSupportedMessage" xml:space="preserve">
<value>'GeneratedDllImportAttribute' cannot be used for source-generated P/Invokes on the current target framework. Source-generated P/Invokes require .NET {0} or above.</value>
<value>'GeneratedDllImportAttribute' cannot be used for source-generated P/Invokes on an unknown target framework v{0}.</value>
<comment>{0} is a version number</comment>
</data>
<data name="TargetFrameworkNotSupportedTitle" xml:space="preserve">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -23,6 +24,11 @@ public ArrayMarshaller(IMarshallingGenerator manualMarshallingGenerator, TypeSyn
_options = options;
}

public bool IsSupported(TargetFramework target, Version version)
{
return target is TargetFramework.Net && version.Major >= 6;
}

public ArgumentSyntax AsArgument(TypePositionInfo info, StubCodeContext context)
{
if (IsPinningPathSupported(info, context))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;

using Microsoft.CodeAnalysis;
Expand All @@ -12,6 +13,8 @@ namespace Microsoft.Interop
{
public sealed class BlittableMarshaller : IMarshallingGenerator
{
public bool IsSupported(TargetFramework target, Version version) => true;

public TypeSyntax AsNativeType(TypePositionInfo info)
{
return info.ManagedType.Syntax;
Expand Down
Loading