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

Move to using the new Roslyn IIncrementalGenerator API for better in-VS performance #1374

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d11a4f9
First pass converting to V2 API.
jkoritzinsky May 25, 2021
7be13c3
Use the WrapGenerator API to enable more scenarios for testing.
jkoritzinsky May 25, 2021
781eea1
Add one more early filter for now.
jkoritzinsky May 25, 2021
d45efe9
Merge branch 'feature/DllImportGenerator' into incremental-generator
jkoritzinsky Jun 18, 2021
0ea9933
Update incremental generators to approved API.
jkoritzinsky Jun 18, 2021
4d3581a
Fix unsupported TFM diagnostics.
jkoritzinsky Jun 18, 2021
44fc621
Use our own custom objects for representing types at the TypePosition…
jkoritzinsky Jun 28, 2021
e1226d2
Add TypePositionInfo record constructor and simplify the API.
jkoritzinsky Jun 28, 2021
ee8f2ea
Build out some infrastructure for testing incrementality of the sourc…
jkoritzinsky Jun 28, 2021
447ab47
Merge branch 'feature/DllImportGenerator' into incremental-generator
jkoritzinsky Jun 28, 2021
02ba8ae
Refactor TypePositionInfo/MarshallingGenerator creation into a separa…
jkoritzinsky Jun 29, 2021
a38ced3
Refactor comparers. Update to public Roslyn build + API changes. Add …
jkoritzinsky Jun 30, 2021
e11e1af
Refactor comparers. Update to public Roslyn build + API changes. Add …
jkoritzinsky Jun 30, 2021
b5ebd83
Merge branch 'feature/DllImportGenerator' into incremental-generator
jkoritzinsky Jul 2, 2021
e0f5c3b
Merge branch 'incremental-generator' into incremental-generator-tpi
jkoritzinsky Jul 2, 2021
21ba952
Fix PreserveSig handling.
jkoritzinsky Jul 27, 2021
231e323
Upgrade tooling to be able to build from the CLI and VS2022 with the …
jkoritzinsky Jul 27, 2021
19f5802
Merge branch 'incremental-generator' into incremental-generator-tpi
jkoritzinsky Jul 27, 2021
af42e1b
Reorganize file and remove unneeded record (will always compare as di…
jkoritzinsky Aug 2, 2021
4931e3a
Make more methods static
jkoritzinsky Aug 2, 2021
d067c78
Merge branch 'incremental-generator' into incremental-generator-tpi
jkoritzinsky Aug 2, 2021
3d9744b
Merge branch 'feature/DllImportGenerator' of github.com:dotnet/runtim…
jkoritzinsky Aug 2, 2021
5e173a0
Make ReturnNativeIdentifier get-only.
jkoritzinsky Aug 2, 2021
3d725b9
Move BoundGenerator to be a nested implementation detail of StubCodeG…
jkoritzinsky Aug 2, 2021
82b02e8
Cleanup whitespace.
jkoritzinsky Aug 2, 2021
d8933f2
PR feedback.
jkoritzinsky Aug 3, 2021
a29b161
Update ref assemblies to match the SDK we're using.
jkoritzinsky Aug 3, 2021
2b37003
Construct GeneratedDllImportData from locals instead of using `with` …
jkoritzinsky Aug 3, 2021
7208fc2
Merge branch 'feature/DllImportGenerator' into incremental-generator-tpi
jkoritzinsky Aug 6, 2021
3c89ab4
Update to released Preview 7 version.
jkoritzinsky Aug 27, 2021
20b9ac7
Use runtime version when appropriate. Update global.json
jkoritzinsky Aug 28, 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
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<DebugType>embedded</DebugType>
<DebugSymbols>true</DebugSymbols>
<LangVersion>9</LangVersion>
<LangVersion>10</LangVersion>
<IsPackable>true</IsPackable>

<!-- Set this property to false if you don't want to use the runtime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<PropertyGroup>
<AssemblyName>Microsoft.Interop.Ancillary</AssemblyName>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<RootNamespace>System.Runtime.InteropServices</RootNamespace>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
3 changes: 2 additions & 1 deletion DllImportGenerator/Benchmarks/Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\DllImportGenerator\Microsoft.Interop.DllImportGenerator.props" />
<Import Project="$(RepoRoot)DllImportGenerator\DllImportGenerator\Microsoft.Interop.DllImportGenerator.props" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>Preview</LangVersion>
<Configurations>Debug;Release;Release_Forwarders</Configurations>
<DllImportGenerator_GenerateForwarders Condition="'$(Configuration)' == 'Release_Forwarders'">true</DllImportGenerator_GenerateForwarders>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using static Microsoft.Interop.DllImportGenerator;

namespace DllImportGenerator.UnitTests
{
public class IncrementalGenerationTests
{
public const string RequiresIncrementalSyntaxTreeModifySupport = "The GeneratorDriver treats all SyntaxTree replace operations on a Compilation as an Add/Remove operation instead of a Modify operation"
+ ", so all cached results based on that input are thrown out. As a result, we cannot validate that unrelated changes within the same SyntaxTree do not cause regeneration.";

[Fact]
public async Task AddingNewUnrelatedType_DoesNotRegenerateSource()
{
string source = CodeSnippets.BasicParametersAndModifiers<int>();

Compilation comp1 = await TestUtils.CreateCompilation(source);

Microsoft.Interop.DllImportGenerator generator = new();
GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new IIncrementalGenerator[] { generator });

driver = driver.RunGenerators(comp1);

generator.IncrementalTracker = new IncrementalityTracker();

Compilation comp2 = comp1.AddSyntaxTrees(CSharpSyntaxTree.ParseText("struct Foo {}", new CSharpParseOptions(LanguageVersion.Preview)));
driver.RunGenerators(comp2);

Assert.Collection(generator.IncrementalTracker.ExecutedSteps,
step =>
{
Assert.Equal(IncrementalityTracker.StepName.CalculateStubInformation, step.Step);
});
}

[Fact(Skip = RequiresIncrementalSyntaxTreeModifySupport)]
public async Task AppendingUnrelatedSource_DoesNotRegenerateSource()
{
string source = $"namespace NS{{{CodeSnippets.BasicParametersAndModifiers<int>()}}}";

SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview));

Compilation comp1 = await TestUtils.CreateCompilation(new[] { syntaxTree });

Microsoft.Interop.DllImportGenerator generator = new();
GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator });

driver = driver.RunGenerators(comp1);

generator.IncrementalTracker = new IncrementalityTracker();

SyntaxTree newTree = syntaxTree.WithRootAndOptions(syntaxTree.GetCompilationUnitRoot().AddMembers(SyntaxFactory.ParseMemberDeclaration("struct Foo {}")!), syntaxTree.Options);

Compilation comp2 = comp1.ReplaceSyntaxTree(comp1.SyntaxTrees.First(), newTree);
driver.RunGenerators(comp2);

Assert.Collection(generator.IncrementalTracker.ExecutedSteps,
step =>
{
Assert.Equal(IncrementalityTracker.StepName.CalculateStubInformation, step.Step);
});
}

[Fact]
public async Task AddingFileWithNewGeneratedDllImport_DoesNotRegenerateOriginalMethod()
{
string source = CodeSnippets.BasicParametersAndModifiers<int>();

Compilation comp1 = await TestUtils.CreateCompilation(source);

Microsoft.Interop.DllImportGenerator generator = new();
GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator });

driver = driver.RunGenerators(comp1);

generator.IncrementalTracker = new IncrementalityTracker();

Compilation comp2 = comp1.AddSyntaxTrees(CSharpSyntaxTree.ParseText(CodeSnippets.BasicParametersAndModifiers<bool>(), new CSharpParseOptions(LanguageVersion.Preview)));
driver.RunGenerators(comp2);

Assert.Equal(2, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.CalculateStubInformation));
Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.GenerateSingleStub));
Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.NormalizeWhitespace));
Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.ConcatenateStubs));
Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.OutputSourceFile));
}

[Fact]
public async Task ReplacingFileWithNewGeneratedDllImport_DoesNotRegenerateStubsInOtherFiles()
{
string source = CodeSnippets.BasicParametersAndModifiers<int>();

Compilation comp1 = await TestUtils.CreateCompilation(new string[] { CodeSnippets.BasicParametersAndModifiers<int>(), CodeSnippets.BasicParametersAndModifiers<bool>() });

Microsoft.Interop.DllImportGenerator generator = new();
GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator });

driver = driver.RunGenerators(comp1);

generator.IncrementalTracker = new IncrementalityTracker();

Compilation comp2 = comp1.ReplaceSyntaxTree(comp1.SyntaxTrees.First(), CSharpSyntaxTree.ParseText(CodeSnippets.BasicParametersAndModifiers<ulong>(), new CSharpParseOptions(LanguageVersion.Preview)));
driver.RunGenerators(comp2);

Assert.Equal(2, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.CalculateStubInformation));
Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.GenerateSingleStub));
Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.NormalizeWhitespace));
Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.ConcatenateStubs));
Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.OutputSourceFile));
}

[Fact]
public async Task ChangingMarshallingStrategy_RegeneratesStub()
{
string stubSource = CodeSnippets.BasicParametersAndModifiers("CustomType");

string customTypeImpl1 = "struct CustomType { System.IntPtr handle; }";

string customTypeImpl2 = "class CustomType : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { public CustomType():base(true){} protected override bool ReleaseHandle(){return true;} }";


Compilation comp1 = await TestUtils.CreateCompilation(stubSource);

SyntaxTree customTypeImpl1Tree = CSharpSyntaxTree.ParseText(customTypeImpl1, new CSharpParseOptions(LanguageVersion.Preview));
comp1 = comp1.AddSyntaxTrees(customTypeImpl1Tree);

Microsoft.Interop.DllImportGenerator generator = new();
GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator });

driver = driver.RunGenerators(comp1);

generator.IncrementalTracker = new IncrementalityTracker();

Compilation comp2 = comp1.ReplaceSyntaxTree(customTypeImpl1Tree, CSharpSyntaxTree.ParseText(customTypeImpl2, new CSharpParseOptions(LanguageVersion.Preview)));
driver.RunGenerators(comp2);

Assert.Collection(generator.IncrementalTracker.ExecutedSteps,
step =>
{
Assert.Equal(IncrementalityTracker.StepName.CalculateStubInformation, step.Step);
},
step =>
{
Assert.Equal(IncrementalityTracker.StepName.GenerateSingleStub, step.Step);
},
step =>
{
Assert.Equal(IncrementalityTracker.StepName.NormalizeWhitespace, step.Step);
},
step =>
{
Assert.Equal(IncrementalityTracker.StepName.ConcatenateStubs, step.Step);
},
step =>
{
Assert.Equal(IncrementalityTracker.StepName.OutputSourceFile, step.Step);
});
}

[Fact(Skip = RequiresIncrementalSyntaxTreeModifySupport)]
public async Task ChangingMarshallingAttributes_SameStrategy_DoesNotRegenerate()
{
string source = CodeSnippets.BasicParametersAndModifiers<bool>();

SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview));

Compilation comp1 = await TestUtils.CreateCompilation(new[] { syntaxTree });

Microsoft.Interop.DllImportGenerator generator = new();
GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator });

driver = driver.RunGenerators(comp1);

generator.IncrementalTracker = new IncrementalityTracker();

SyntaxTree newTree = syntaxTree.WithRootAndOptions(
syntaxTree.GetCompilationUnitRoot().AddMembers(
SyntaxFactory.ParseMemberDeclaration(
CodeSnippets.MarshalAsParametersAndModifiers<bool>(System.Runtime.InteropServices.UnmanagedType.Bool))!),
syntaxTree.Options);

Compilation comp2 = comp1.ReplaceSyntaxTree(comp1.SyntaxTrees.First(), newTree);
driver.RunGenerators(comp2);

Assert.Collection(generator.IncrementalTracker.ExecutedSteps,
step =>
{
Assert.Equal(IncrementalityTracker.StepName.CalculateStubInformation, step.Step);
},
step =>
{
Assert.Equal(IncrementalityTracker.StepName.GenerateSingleStub, step.Step);
});
}
}
}
56 changes: 40 additions & 16 deletions DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,26 @@ public static void AssertPreSourceGeneratorCompilation(Compilation comp)
/// <param name="outputKind">Output type</param>
/// <param name="allowUnsafe">Whether or not use of the unsafe keyword should be allowed</param>
/// <returns>The resulting compilation</returns>
public static async Task<Compilation> CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable<string>? preprocessorSymbols = null)
public static Task<Compilation> CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable<string>? preprocessorSymbols = null)
{
var (mdRefs, ancillary) = GetReferenceAssemblies();
return CreateCompilation(new[] { source }, outputKind, allowUnsafe, preprocessorSymbols);
}

return CSharpCompilation.Create("compilation",
new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview, preprocessorSymbols: preprocessorSymbols)) },
(await mdRefs.ResolveAsync(LanguageNames.CSharp, CancellationToken.None)).Add(ancillary),
new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe));
/// <summary>
/// Create a compilation given sources
/// </summary>
/// <param name="sources">Sources to compile</param>
/// <param name="outputKind">Output type</param>
/// <param name="allowUnsafe">Whether or not use of the unsafe keyword should be allowed</param>
/// <returns>The resulting compilation</returns>
public static Task<Compilation> CreateCompilation(string[] sources, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable<string>? preprocessorSymbols = null)
{
return CreateCompilation(
sources.Select(source =>
CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview, preprocessorSymbols: preprocessorSymbols))).ToArray(),
outputKind,
allowUnsafe,
preprocessorSymbols);
}

/// <summary>
Expand All @@ -62,13 +74,12 @@ public static async Task<Compilation> CreateCompilation(string source, OutputKin
/// <param name="outputKind">Output type</param>
/// <param name="allowUnsafe">Whether or not use of the unsafe keyword should be allowed</param>
/// <returns>The resulting compilation</returns>
public static async Task<Compilation> CreateCompilation(string[] sources, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable<string>? preprocessorSymbols = null)
public static async Task<Compilation> CreateCompilation(SyntaxTree[] sources, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable<string>? preprocessorSymbols = null)
{
var (mdRefs, ancillary) = GetReferenceAssemblies();

return CSharpCompilation.Create("compilation",
sources.Select(source =>
CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview, preprocessorSymbols: preprocessorSymbols))).ToArray(),
sources,
(await mdRefs.ResolveAsync(LanguageNames.CSharp, CancellationToken.None)).Add(ancillary),
new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe));
}
Expand All @@ -81,10 +92,23 @@ public static async Task<Compilation> CreateCompilation(string[] sources, Output
/// <param name="outputKind">Output type</param>
/// <param name="allowUnsafe">Whether or not use of the unsafe keyword should be allowed</param>
/// <returns>The resulting compilation</returns>
public static async Task<Compilation> CreateCompilationWithReferenceAssemblies(string source, ReferenceAssemblies referenceAssemblies, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true)
public static Task<Compilation> CreateCompilationWithReferenceAssemblies(string source, ReferenceAssemblies referenceAssemblies, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true)
{
return CreateCompilationWithReferenceAssemblies(new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) }, referenceAssemblies, outputKind, allowUnsafe);
}

/// <summary>
/// Create a compilation given source and reference assemblies
/// </summary>
/// <param name="source">Source to compile</param>
/// <param name="referenceAssemblies">Reference assemblies to include</param>
/// <param name="outputKind">Output type</param>
/// <param name="allowUnsafe">Whether or not use of the unsafe keyword should be allowed</param>
/// <returns>The resulting compilation</returns>
public static async Task<Compilation> CreateCompilationWithReferenceAssemblies(SyntaxTree[] sources, ReferenceAssemblies referenceAssemblies, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true)
{
return CSharpCompilation.Create("compilation",
new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) },
sources,
(await referenceAssemblies.ResolveAsync(LanguageNames.CSharp, CancellationToken.None)),
new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe));
}
Expand All @@ -96,7 +120,7 @@ public static (ReferenceAssemblies, MetadataReference) GetReferenceAssemblies()
"net6.0",
new PackageIdentity(
"Microsoft.NETCore.App.Ref",
"6.0.0-preview.6.21317.4"),
"6.0.0-preview.7.21377.19"),
Path.Combine("ref", "net6.0"))
.WithNuGetConfigFilePath(Path.Combine(Path.GetDirectoryName(typeof(TestUtils).Assembly.Location)!, "NuGet.config"));

Expand All @@ -114,7 +138,7 @@ public static (ReferenceAssemblies, MetadataReference) GetReferenceAssemblies()
/// <param name="diagnostics">Resulting diagnostics</param>
/// <param name="generators">Source generator instances</param>
/// <returns>The resulting compilation</returns>
public static Compilation RunGenerators(Compilation comp, out ImmutableArray<Diagnostic> diagnostics, params ISourceGenerator[] generators)
public static Compilation RunGenerators(Compilation comp, out ImmutableArray<Diagnostic> diagnostics, params IIncrementalGenerator[] generators)
{
CreateDriver(comp, null, generators).RunGeneratorsAndUpdateCompilation(comp, out var d, out diagnostics);
return d;
Expand All @@ -127,15 +151,15 @@ public static Compilation RunGenerators(Compilation comp, out ImmutableArray<Dia
/// <param name="diagnostics">Resulting diagnostics</param>
/// <param name="generators">Source generator instances</param>
/// <returns>The resulting compilation</returns>
public static Compilation RunGenerators(Compilation comp, AnalyzerConfigOptionsProvider options, out ImmutableArray<Diagnostic> diagnostics, params ISourceGenerator[] generators)
public static Compilation RunGenerators(Compilation comp, AnalyzerConfigOptionsProvider options, out ImmutableArray<Diagnostic> diagnostics, params IIncrementalGenerator[] generators)
{
CreateDriver(comp, options, generators).RunGeneratorsAndUpdateCompilation(comp, out var d, out diagnostics);
return d;
}

private static GeneratorDriver CreateDriver(Compilation c, AnalyzerConfigOptionsProvider? options, ISourceGenerator[] generators)
public static GeneratorDriver CreateDriver(Compilation c, AnalyzerConfigOptionsProvider? options, IIncrementalGenerator[] generators)
=> CSharpGeneratorDriver.Create(
ImmutableArray.Create(generators),
ImmutableArray.Create(generators.Select(gen => gen.AsSourceGenerator()).ToArray()),
parseOptions: (CSharpParseOptions)c.SyntaxTrees.First().Options,
optionsProvider: options);
}
Expand Down
Loading