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 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
8 changes: 6 additions & 2 deletions docs/design/libraries/DllImportGenerator/Compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ Documentation on compatibility guidance and the current state. The version headi

## Version 1

The focus of version 1 is to support `NetCoreApp`. This implies that anything not needed by `NetCoreApp` is subject to change. Only .NET 6+ is supported.
The focus of version 1 is to support `NetCoreApp`. This implies that anything not needed by `NetCoreApp` is subject to change.

### Fallback mechanism

In the event a marshaller would generate code that has a specific target framework or version requirement that is not satisfied, the generator will instead produce a normal `DllImportAttribute` declaration. This fallback mechanism enables the use of `GeneratedDllImportAttribute` in most circumstances and permits the conversion from `DllImportAttribute` to `GeneratedDllImportAttribute` to be across most code bases. There are instances where the generator will not be able to handle signatures or configuration. For example, uses of `StringBuilder` are not supported in any form and consumers should retain uses of `DllImportAttribute`. Additionally, `GeneratedDllImportAttribute` cannot represent all settings available on `DllImportAttribute`—see below for details.

### Semantic changes compared to `DllImportAttribute`

Expand Down Expand Up @@ -92,4 +96,4 @@ Unlike the built-in system, the source generator does not support marshalling fo

## Version 0

This version is the built-in IL Stub generation system that is triggered whenever a method marked with `DllImport` is invoked.
This version is the built-in IL Stub generation system that is triggered whenever a method marked with `DllImportAttribute` is invoked.
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.

Loading