-
Notifications
You must be signed in to change notification settings - Fork 226
Perf/generator #8212
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
Perf/generator #8212
Changes from 13 commits
c7a95d3
3523147
04f79ff
6a4a589
89660be
7616424
489f81c
ff6f5b4
312ff75
9422b03
ca2a9e5
fb07d1c
30c9815
baaddfa
46dbf1e
5cf0d50
c430fea
5ce316d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
333fred marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,13 +2,14 @@ | |
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Diagnostics; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using Microsoft.AspNetCore.Razor.Language; | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
|
||
| namespace Microsoft.NET.Sdk.Razor.SourceGenerators | ||
| { | ||
|
|
@@ -66,69 +67,82 @@ public void Initialize(IncrementalGeneratorInitializationContext context) | |
| var generatedDeclarationCode = componentFiles | ||
| .Combine(importFiles.Collect()) | ||
| .Combine(razorSourceGeneratorOptions) | ||
| .WithLambdaComparer((old, @new) => (old.Right.Equals(@new.Right) && old.Left.Left.Equals(@new.Left.Left) && old.Left.Right.SequenceEqual(@new.Left.Right)), (a) => a.GetHashCode()) | ||
| .Select(static (pair, _) => | ||
| { | ||
|
|
||
| var ((sourceItem, importFiles), razorSourceGeneratorOptions) = pair; | ||
| RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStart(sourceItem.FilePath); | ||
| RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStart(sourceItem.RelativePhysicalPath); | ||
|
|
||
| var projectEngine = GetDeclarationProjectEngine(sourceItem, importFiles, razorSourceGeneratorOptions); | ||
|
|
||
| var codeGen = projectEngine.Process(sourceItem); | ||
|
|
||
| var result = codeGen.GetCSharpDocument().GeneratedCode; | ||
|
|
||
| RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStop(sourceItem.FilePath); | ||
| RazorSourceGeneratorEventSource.Log.GenerateDeclarationCodeStop(sourceItem.RelativePhysicalPath); | ||
|
|
||
| return result; | ||
| return (result, sourceItem.RelativePhysicalPath); | ||
| }); | ||
|
|
||
| var generatedDeclarationSyntaxTrees = generatedDeclarationCode | ||
| .Combine(parseOptions) | ||
| .Select(static (pair, _) => | ||
| .Select(static (pair, ct) => | ||
| { | ||
| var (generatedDeclarationCode, parseOptions) = pair; | ||
| return CSharpSyntaxTree.ParseText(generatedDeclarationCode, (CSharpParseOptions)parseOptions); | ||
| var ((generatedDeclarationCode, filePath), parseOptions) = pair; | ||
| return CSharpSyntaxTree.ParseText(generatedDeclarationCode, (CSharpParseOptions)parseOptions, filePath, cancellationToken: ct); | ||
| }); | ||
|
|
||
| var tagHelpersFromCompilation = compilation | ||
| .Combine(generatedDeclarationSyntaxTrees.Collect()) | ||
| var tagHelpersFromComponents = generatedDeclarationSyntaxTrees | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This allows us to only look at razor files that have changed to get their updated tag helpers, rather than re-discovering everything anytime a single file changes. |
||
| .Combine(compilation) | ||
| .Combine(razorSourceGeneratorOptions) | ||
| .Select(static (pair, _) => | ||
| .SelectMany(static (pair, ct) => | ||
| { | ||
| RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStart(); | ||
|
|
||
| var ((compilation, generatedDeclarationSyntaxTrees), razorSourceGeneratorOptions) = pair; | ||
| var ((generatedDeclarationSyntaxTree, compilation), razorSourceGeneratorOptions) = pair; | ||
| RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromComponentStart(generatedDeclarationSyntaxTree.FilePath); | ||
|
|
||
| var tagHelperFeature = new StaticCompilationTagHelperFeature(); | ||
| var discoveryProjectEngine = GetDiscoveryProjectEngine(compilation.References.ToImmutableArray(), tagHelperFeature); | ||
|
|
||
| var compilationWithDeclarations = compilation.AddSyntaxTrees(generatedDeclarationSyntaxTrees); | ||
| var compilationWithDeclarations = compilation.AddSyntaxTrees(generatedDeclarationSyntaxTree); | ||
|
|
||
| // try and find the specific root class this component is declaring, falling back to the assembly if for any reason the code is not in the shape we expect | ||
| ISymbol targetSymbol = compilationWithDeclarations.Assembly; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to look at the whole compilation with the component added (as it might reference stuff from csharp), but there's no need to actually discover anything else from the compilation, so only search the component itself. |
||
| var root = generatedDeclarationSyntaxTree.GetRoot(ct); | ||
333fred marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (root is CompilationUnitSyntax { Members: [NamespaceDeclarationSyntax { Members: [ClassDeclarationSyntax classSyntax, ..] }, ..] }) | ||
333fred marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| var declaredClass = compilationWithDeclarations.GetSemanticModel(generatedDeclarationSyntaxTree).GetDeclaredSymbol(classSyntax, ct); | ||
| Debug.Assert(declaredClass is null || declaredClass is { AllInterfaces: [{ Name: "IComponent" }, ..] }); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, based on our current code generation. If we change that this assert will potentially fire, but either we'll still do the optimization, or fallback to the less optimized case. The tag helpers will still be found correctly. |
||
| targetSymbol = declaredClass ?? targetSymbol; | ||
| } | ||
|
|
||
| tagHelperFeature.Compilation = compilationWithDeclarations; | ||
| tagHelperFeature.TargetSymbol = compilationWithDeclarations.Assembly; | ||
| tagHelperFeature.TargetSymbol = targetSymbol; | ||
|
|
||
| var result = (IList<TagHelperDescriptor>)tagHelperFeature.GetDescriptors(); | ||
| RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStop(); | ||
| var result = tagHelperFeature.GetDescriptors(); | ||
| RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromComponentStop(generatedDeclarationSyntaxTree.FilePath); | ||
| return result; | ||
| }) | ||
| .WithLambdaComparer(static (a, b) => | ||
| }); | ||
|
|
||
| var tagHelpersFromCompilation = compilation | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do a single compilation lookup here. If a user edits CSharp this runs without running any of the component stuff again. If the user edits a component, this no longer runs. |
||
| .Combine(razorSourceGeneratorOptions) | ||
| .Select(static (pair, _) => | ||
| { | ||
| if (a.Count != b.Count) | ||
| { | ||
| return false; | ||
| } | ||
| RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStart(); | ||
|
|
||
| for (var i = 0; i < a.Count; i++) | ||
| { | ||
| if (!a[i].Equals(b[i])) | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
| var (compilation, razorSourceGeneratorOptions) = pair; | ||
|
|
||
| var tagHelperFeature = new StaticCompilationTagHelperFeature(); | ||
| var discoveryProjectEngine = GetDiscoveryProjectEngine(compilation.References.ToImmutableArray(), tagHelperFeature); | ||
|
|
||
| return true; | ||
| }, getHashCode: static a => a.Count); | ||
| tagHelperFeature.Compilation = compilation; | ||
| tagHelperFeature.TargetSymbol = compilation.Assembly; | ||
|
|
||
| var result = tagHelperFeature.GetDescriptors(); | ||
| RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromCompilationStop(); | ||
| return result; | ||
| }); | ||
|
|
||
| var tagHelpersFromReferences = compilation | ||
| .Combine(razorSourceGeneratorOptions) | ||
|
|
@@ -171,7 +185,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) | |
| var tagHelperFeature = new StaticCompilationTagHelperFeature(); | ||
| var discoveryProjectEngine = GetDiscoveryProjectEngine(compilation.References.ToImmutableArray(), tagHelperFeature); | ||
|
|
||
| List<TagHelperDescriptor> descriptors = new(); | ||
| var descriptors = ImmutableArray.CreateBuilder<TagHelperDescriptor>(); | ||
333fred marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| tagHelperFeature.Compilation = compilation; | ||
| foreach (var reference in compilation.References) | ||
| { | ||
|
|
@@ -183,47 +197,84 @@ public void Initialize(IncrementalGeneratorInitializationContext context) | |
| } | ||
|
|
||
| RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromReferencesStop(); | ||
| return (ICollection<TagHelperDescriptor>)descriptors; | ||
| return descriptors.ToImmutable(); | ||
| }); | ||
|
|
||
| var allTagHelpers = tagHelpersFromCompilation | ||
| var allTagHelpers = tagHelpersFromComponents.Collect() | ||
| .Combine(tagHelpersFromCompilation) | ||
| .Combine(tagHelpersFromReferences) | ||
| .Select(static (pair, _) => | ||
| { | ||
| var (tagHelpersFromCompilation, tagHelpersFromReferences) = pair; | ||
| var count = tagHelpersFromCompilation.Count + tagHelpersFromReferences.Count; | ||
| var ((tagHelpersFromComponents, tagHelpersFromCompilation), tagHelpersFromReferences) = pair; | ||
| var count = tagHelpersFromCompilation.Length + tagHelpersFromReferences.Length + tagHelpersFromComponents.Length; | ||
| if (count == 0) | ||
| { | ||
| return Array.Empty<TagHelperDescriptor>(); | ||
| return ImmutableArray<TagHelperDescriptor>.Empty; | ||
| } | ||
|
|
||
| var allTagHelpers = new TagHelperDescriptor[count]; | ||
| tagHelpersFromCompilation.CopyTo(allTagHelpers, 0); | ||
| tagHelpersFromReferences.CopyTo(allTagHelpers, tagHelpersFromCompilation.Count); | ||
| var allTagHelpers = ImmutableArray.CreateBuilder<TagHelperDescriptor>(count); | ||
| allTagHelpers.AddRange(tagHelpersFromCompilation); | ||
| allTagHelpers.AddRange(tagHelpersFromReferences); | ||
| allTagHelpers.AddRange(tagHelpersFromComponents); | ||
|
|
||
| return allTagHelpers; | ||
| return allTagHelpers.ToImmutable(); | ||
| }); | ||
|
|
||
| var generatedOutput = sourceItems | ||
| .Combine(importFiles.Collect()) | ||
| .Combine(allTagHelpers) | ||
| .WithLambdaComparer((old, @new) => old.Left.Equals(@new.Left) && old.Right.SequenceEqual(@new.Right), (a) => a.GetHashCode()) | ||
| .Combine(razorSourceGeneratorOptions) | ||
| .Select(static (pair, _) => | ||
| { | ||
| var (((sourceItem, imports), allTagHelpers), razorSourceGeneratorOptions) = pair; | ||
| var ((sourceItem, imports), razorSourceGeneratorOptions) = pair; | ||
|
|
||
| RazorSourceGeneratorEventSource.Log.ParseRazorDocumentStart(sourceItem.RelativePhysicalPath); | ||
|
|
||
| var projectEngine = GetGenerationProjectEngine(sourceItem, imports, razorSourceGeneratorOptions); | ||
|
|
||
| var document = projectEngine.ProcessInitialParse(sourceItem); | ||
|
|
||
| RazorSourceGeneratorEventSource.Log.ParseRazorDocumentStop(sourceItem.RelativePhysicalPath); | ||
| return (projectEngine, sourceItem.RelativePhysicalPath, document); | ||
| }) | ||
|
|
||
| // Add the tag helpers in, but ignore if they've changed or not, only reprocessing the actual document changed | ||
| .Combine(allTagHelpers) | ||
| .WithLambdaComparer((old, @new) => old.Left.Equals(@new.Left), (item) => item.GetHashCode()) | ||
| .Select((pair, _) => | ||
| { | ||
| var ((projectEngine, filePath, codeDocument), allTagHelpers) = pair; | ||
| RazorSourceGeneratorEventSource.Log.RewriteTagHelpersStart(filePath); | ||
|
|
||
| RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStart(sourceItem.FilePath); | ||
| codeDocument = projectEngine.ProcessTagHelpers(codeDocument, allTagHelpers, checkForIdempotency: false); | ||
|
|
||
| // Add a generated suffix so tools, such as coverlet, consider the file to be generated | ||
| var hintName = GetIdentifierFromPath(sourceItem.RelativePhysicalPath) + ".g.cs"; | ||
| RazorSourceGeneratorEventSource.Log.RewriteTagHelpersStop(filePath); | ||
| return (projectEngine, filePath, codeDocument); | ||
| }) | ||
|
|
||
| // next we do a second parse, along with the helpers, but check for idempotency. If the tag helpers used on the previous parse match, the compiler can skip re-computing them | ||
| .Combine(allTagHelpers) | ||
| .Select((pair, _) => | ||
| { | ||
|
|
||
| var projectEngine = GetGenerationProjectEngine(allTagHelpers, sourceItem, imports, razorSourceGeneratorOptions); | ||
| var ((projectEngine, filePath, document), allTagHelpers) = pair; | ||
| RazorSourceGeneratorEventSource.Log.CheckAndRewriteTagHelpersStart(filePath); | ||
|
|
||
| var codeDocument = projectEngine.Process(sourceItem); | ||
| var csharpDocument = codeDocument.GetCSharpDocument(); | ||
| document = projectEngine.ProcessTagHelpers(document, allTagHelpers, checkForIdempotency: true); | ||
|
|
||
| RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStop(sourceItem.FilePath); | ||
| return (hintName, csharpDocument); | ||
| RazorSourceGeneratorEventSource.Log.CheckAndRewriteTagHelpersStop(filePath); | ||
| return (projectEngine, filePath, document); | ||
| }) | ||
|
|
||
| .Select((pair, _) => | ||
| { | ||
| var (projectEngine, filePath, document) = pair; | ||
| RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStart(filePath); | ||
| document = projectEngine.ProcessRemaining(document); | ||
| var csharpDocument = document.CodeDocument.GetCSharpDocument(); | ||
|
|
||
| RazorSourceGeneratorEventSource.Log.RazorCodeGenerateStop(filePath); | ||
| return (filePath, csharpDocument); | ||
| }) | ||
| .WithLambdaComparer(static (a, b) => | ||
| { | ||
|
|
@@ -238,7 +289,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) | |
|
|
||
| context.RegisterSourceOutput(generatedOutput, static (context, pair) => | ||
| { | ||
| var (hintName, csharpDocument) = pair; | ||
| var (filePath, csharpDocument) = pair; | ||
|
|
||
| // Add a generated suffix so tools, such as coverlet, consider the file to be generated | ||
| var hintName = GetIdentifierFromPath(filePath) + ".g.cs"; | ||
|
|
||
| RazorSourceGeneratorEventSource.Log.AddSyntaxTrees(hintName); | ||
| for (var i = 0; i < csharpDocument.Diagnostics.Count; i++) | ||
| { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.