Skip to content

Commit

Permalink
Move to using the new Roslyn IIncrementalGenerator API for better in-…
Browse files Browse the repository at this point in the history
…VS performance (#1374)
  • Loading branch information
jkoritzinsky authored Sep 8, 2021
1 parent cd76a36 commit 90c617d
Show file tree
Hide file tree
Showing 28 changed files with 1,140 additions and 597 deletions.
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

0 comments on commit 90c617d

Please sign in to comment.