Skip to content

Commit

Permalink
Merge pull request #46508 from mavasani/AnalyzerDriverSemanticModelPr…
Browse files Browse the repository at this point in the history
…ovider

Analyzer driver performance improvements
  • Loading branch information
mavasani authored Aug 14, 2020
2 parents 2c1be7c + 5389716 commit 897d928
Show file tree
Hide file tree
Showing 22 changed files with 490 additions and 304 deletions.
68 changes: 56 additions & 12 deletions src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,8 @@ private static CSharpCompilation Create(
options.SourceReferenceResolver,
CSharp.MessageProvider.Instance,
isSubmission,
state: null));
state: null),
semanticModelProvider: null);

if (syntaxTrees != null)
{
Expand All @@ -383,8 +384,9 @@ private CSharpCompilation(
ReferenceManager? referenceManager,
bool reuseReferenceManager,
SyntaxAndDeclarationManager syntaxAndDeclarations,
SemanticModelProvider? semanticModelProvider,
AsyncQueue<CompilationEvent>? eventQueue = null)
: this(assemblyName, options, references, previousSubmission, submissionReturnType, hostObjectType, isSubmission, referenceManager, reuseReferenceManager, syntaxAndDeclarations, SyntaxTreeCommonFeatures(syntaxAndDeclarations.ExternalSyntaxTrees), eventQueue)
: this(assemblyName, options, references, previousSubmission, submissionReturnType, hostObjectType, isSubmission, referenceManager, reuseReferenceManager, syntaxAndDeclarations, SyntaxTreeCommonFeatures(syntaxAndDeclarations.ExternalSyntaxTrees), semanticModelProvider, eventQueue)
{
}

Expand All @@ -400,8 +402,9 @@ private CSharpCompilation(
bool reuseReferenceManager,
SyntaxAndDeclarationManager syntaxAndDeclarations,
IReadOnlyDictionary<string, string> features,
SemanticModelProvider? semanticModelProvider,
AsyncQueue<CompilationEvent>? eventQueue = null)
: base(assemblyName, references, features, isSubmission, eventQueue)
: base(assemblyName, references, features, isSubmission, semanticModelProvider, eventQueue)
{
WellKnownMemberSignatureComparer = new WellKnownMembersSignatureComparer(this);
_options = options;
Expand Down Expand Up @@ -494,7 +497,8 @@ private static LanguageVersion CommonLanguageVersion(ImmutableArray<SyntaxTree>
this.IsSubmission,
_referenceManager,
reuseReferenceManager: true,
syntaxAndDeclarations: _syntaxAndDeclarations);
_syntaxAndDeclarations,
this.SemanticModelProvider);
}

private CSharpCompilation Update(
Expand All @@ -512,7 +516,8 @@ private CSharpCompilation Update(
this.IsSubmission,
referenceManager,
reuseReferenceManager,
syntaxAndDeclarations);
syntaxAndDeclarations,
this.SemanticModelProvider);
}

/// <summary>
Expand All @@ -534,7 +539,8 @@ private CSharpCompilation Update(
this.IsSubmission,
_referenceManager,
reuseReferenceManager: assemblyName == this.AssemblyName,
syntaxAndDeclarations: _syntaxAndDeclarations);
_syntaxAndDeclarations,
this.SemanticModelProvider);
}

/// <summary>
Expand Down Expand Up @@ -563,7 +569,8 @@ private CSharpCompilation Update(
this.IsSubmission,
referenceManager: null,
reuseReferenceManager: false,
syntaxAndDeclarations: _syntaxAndDeclarations);
_syntaxAndDeclarations,
this.SemanticModelProvider);
}

/// <summary>
Expand Down Expand Up @@ -602,7 +609,8 @@ public CSharpCompilation WithOptions(CSharpCompilationOptions options)
options.SourceReferenceResolver,
_syntaxAndDeclarations.MessageProvider,
_syntaxAndDeclarations.IsSubmission,
state: null));
state: null),
this.SemanticModelProvider);
}

/// <summary>
Expand Down Expand Up @@ -633,7 +641,32 @@ public CSharpCompilation WithScriptCompilationInfo(CSharpScriptCompilationInfo?
isSubmission: info != null,
_referenceManager,
reuseReferenceManager,
syntaxAndDeclarations: _syntaxAndDeclarations);
_syntaxAndDeclarations,
this.SemanticModelProvider);
}

/// <summary>
/// Returns a new compilation with the given semantic model provider.
/// </summary>
internal override Compilation WithSemanticModelProvider(SemanticModelProvider? semanticModelProvider)
{
if (this.SemanticModelProvider == semanticModelProvider)
{
return this;
}

return new CSharpCompilation(
this.AssemblyName,
_options,
this.ExternalReferences,
this.PreviousSubmission,
this.SubmissionReturnType,
this.HostObjectType,
this.IsSubmission,
_referenceManager,
reuseReferenceManager: true,
_syntaxAndDeclarations,
semanticModelProvider);
}

/// <summary>
Expand All @@ -651,8 +684,9 @@ internal override Compilation WithEventQueue(AsyncQueue<CompilationEvent>? event
this.IsSubmission,
_referenceManager,
reuseReferenceManager: true,
syntaxAndDeclarations: _syntaxAndDeclarations,
eventQueue: eventQueue);
_syntaxAndDeclarations,
this.SemanticModelProvider,
eventQueue);
}

#endregion
Expand Down Expand Up @@ -2085,9 +2119,19 @@ internal void AddModuleInitializerMethod(MethodSymbol method)
throw new ArgumentException(CSharpResources.SyntaxTreeNotFound, nameof(syntaxTree));
}

return new SyntaxTreeSemanticModel(this, (SyntaxTree)syntaxTree, ignoreAccessibility);
SemanticModel? model = null;
if (SemanticModelProvider != null)
{
model = SemanticModelProvider.GetSemanticModel(syntaxTree, this, ignoreAccessibility);
Debug.Assert(model != null);
}

return model ?? CreateSemanticModel(syntaxTree, ignoreAccessibility);
}

internal override SemanticModel CreateSemanticModel(SyntaxTree syntaxTree, bool ignoreAccessibility)
=> new SyntaxTreeSemanticModel(this, syntaxTree, ignoreAccessibility);

// When building symbols from the declaration table (lazily), or inside a type, or when
// compiling a method body, we may not have a BinderContext in hand for the enclosing
// scopes. Therefore, we build them when needed (and cache them) using a ContextBuilder.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace Microsoft.CodeAnalysis.CSharp
/// <summary>
/// Allows asking semantic questions about a tree of syntax nodes in a Compilation. Typically,
/// an instance is obtained by a call to <see cref="Compilation"/>.<see
/// cref="Compilation.GetSemanticModel"/>.
/// cref="Compilation.GetSemanticModel(SyntaxTree, bool)"/>.
/// </summary>
/// <remarks>
/// <para>An instance of <see cref="CSharpSemanticModel"/> caches local symbols and semantic
Expand Down
40 changes: 17 additions & 23 deletions src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1084,32 +1084,26 @@ private void CompileMethod(

if (diagsWritten && !methodSymbol.IsImplicitlyDeclared && _compilation.EventQueue != null)
{
Lazy<SemanticModel> lazySemanticModel = null;

if (body != null)
// If compilation has a caching semantic model provider, then cache the already-computed bound tree
// onto the semantic model and store it on the event.
SyntaxTreeSemanticModel semanticModelWithCachedBoundNodes = null;
if (body != null &&
forSemanticModel.Syntax is { } semanticModelSyntax &&
_compilation.SemanticModelProvider is CachingSemanticModelProvider cachingSemanticModelProvider)
{
lazySemanticModel = new Lazy<SemanticModel>(() =>
{
var syntax = body.Syntax;
var semanticModel = (SyntaxTreeSemanticModel)_compilation.GetSemanticModel(syntax.SyntaxTree);

if (forSemanticModel.Syntax is { } semanticModelSyntax)
{
semanticModel.GetOrAddModel(semanticModelSyntax,
(rootSyntax) =>
{
Debug.Assert(rootSyntax == forSemanticModel.Syntax);
return MethodBodySemanticModel.Create(semanticModel,
methodSymbol,
forSemanticModel);
});
}

return semanticModel;
});
var syntax = body.Syntax;
semanticModelWithCachedBoundNodes = (SyntaxTreeSemanticModel)cachingSemanticModelProvider.GetSemanticModel(syntax.SyntaxTree, _compilation);
semanticModelWithCachedBoundNodes.GetOrAddModel(semanticModelSyntax,
(rootSyntax) =>
{
Debug.Assert(rootSyntax == forSemanticModel.Syntax);
return MethodBodySemanticModel.Create(semanticModelWithCachedBoundNodes,
methodSymbol,
forSemanticModel);
});
}

_compilation.EventQueue.TryEnqueue(new SymbolDeclaredCompilationEvent(_compilation, methodSymbol.GetPublicSymbol(), lazySemanticModel));
_compilation.EventQueue.TryEnqueue(new SymbolDeclaredCompilationEvent(_compilation, methodSymbol.GetPublicSymbol(), semanticModelWithCachedBoundNodes));
}

// Don't lower if we're not emitting or if there were errors.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
Expand Down Expand Up @@ -3747,5 +3748,38 @@ static ImmutableArray<Diagnostic> getReportedDiagnostics(AnalysisResult analysis
}
}
}

[Fact]
public void TestSemanticModelProvider()
{
var tree = CSharpSyntaxTree.ParseText(@"class C { }");
Compilation compilation = CreateCompilation(new[] { tree });

var semanticModelProvider = new MySemanticModelProvider();
compilation = compilation.WithSemanticModelProvider(semanticModelProvider);

// Verify semantic model provider is used by Compilation.GetSemanticModel API
var model = compilation.GetSemanticModel(tree);
semanticModelProvider.VerifyCachedModel(tree, model);

// Verify semantic model provider is used by CSharpCompilation.GetSemanticModel API
model = ((CSharpCompilation)compilation).GetSemanticModel(tree, ignoreAccessibility: false);
semanticModelProvider.VerifyCachedModel(tree, model);
}

private sealed class MySemanticModelProvider : SemanticModelProvider
{
private readonly ConcurrentDictionary<SyntaxTree, SemanticModel> _cache = new ConcurrentDictionary<SyntaxTree, SemanticModel>();

public override SemanticModel GetSemanticModel(SyntaxTree tree, Compilation compilation, bool ignoreAccessibility = false)
{
return _cache.GetOrAdd(tree, compilation.CreateSemanticModel(tree, ignoreAccessibility));
}

public void VerifyCachedModel(SyntaxTree tree, SemanticModel model)
{
Assert.Same(model, _cache[tree]);
}
}
}
}
36 changes: 32 additions & 4 deletions src/Compilers/Core/Portable/Compilation/Compilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,15 @@ internal Compilation(
ImmutableArray<MetadataReference> references,
IReadOnlyDictionary<string, string> features,
bool isSubmission,
SemanticModelProvider? semanticModelProvider,
AsyncQueue<CompilationEvent>? eventQueue)
{
RoslynDebug.Assert(!references.IsDefault);
RoslynDebug.Assert(features != null);

this.AssemblyName = name;
this.ExternalReferences = references;
this.SemanticModelProvider = semanticModelProvider;
this.EventQueue = eventQueue;

_lazySubmissionSlotIndex = isSubmission ? SubmissionSlotIndexToBeAllocated : SubmissionSlotIndexNotApplicable;
Expand Down Expand Up @@ -195,6 +197,11 @@ public Compilation Clone()
/// </summary>
internal abstract Compilation WithEventQueue(AsyncQueue<CompilationEvent>? eventQueue);

/// <summary>
/// Returns a new compilation with a given semantic model provider.
/// </summary>
internal abstract Compilation WithSemanticModelProvider(SemanticModelProvider semanticModelProvider);

/// <summary>
/// Gets a new <see cref="SemanticModel"/> for the specified syntax tree.
/// </summary>
Expand All @@ -203,12 +210,28 @@ public Compilation Clone()
/// True if the SemanticModel should ignore accessibility rules when answering semantic questions.
/// </param>
public SemanticModel GetSemanticModel(SyntaxTree syntaxTree, bool ignoreAccessibility = false)
{
return CommonGetSemanticModel(syntaxTree, ignoreAccessibility);
}
=> CommonGetSemanticModel(syntaxTree, ignoreAccessibility);

/// <summary>
/// Gets a <see cref="SemanticModel"/> for the given <paramref name="syntaxTree"/>.
/// If <see cref="SemanticModelProvider"/> is non-null, it attempts to use <see cref="SemanticModelProvider.GetSemanticModel(SyntaxTree, Compilation, bool)"/>
/// to get a semantic model. Otherwise, it creates a new semantic model using <see cref="CreateSemanticModel(SyntaxTree, bool)"/>.
/// </summary>
/// <param name="syntaxTree"></param>
/// <param name="ignoreAccessibility"></param>
/// <returns></returns>
protected abstract SemanticModel CommonGetSemanticModel(SyntaxTree syntaxTree, bool ignoreAccessibility);

/// <summary>
/// Creates a new <see cref="SemanticModel"/> for the given <paramref name="syntaxTree"/>.
/// Unlike the <see cref="GetSemanticModel(SyntaxTree, bool)"/> and <see cref="CommonGetSemanticModel(SyntaxTree, bool)"/>,
/// it does not attempt to use the <see cref="SemanticModelProvider"/> to get a semantic model, but instead always creates a new semantic model.
/// </summary>
/// <param name="syntaxTree"></param>
/// <param name="ignoreAccessibility"></param>
/// <returns></returns>
internal abstract SemanticModel CreateSemanticModel(SyntaxTree syntaxTree, bool ignoreAccessibility);

/// <summary>
/// Returns a new INamedTypeSymbol representing an error type with the given name and arity
/// in the given optional container.
Expand Down Expand Up @@ -501,10 +524,15 @@ public bool ContainsSyntaxTree(SyntaxTree syntaxTree)

protected abstract bool CommonContainsSyntaxTree(SyntaxTree? syntaxTree);

/// <summary>
/// Optional semantic model provider for this compilation.
/// </summary>
internal SemanticModelProvider? SemanticModelProvider { get; }

/// <summary>
/// The event queue that this compilation was created with.
/// </summary>
internal readonly AsyncQueue<CompilationEvent>? EventQueue;
internal AsyncQueue<CompilationEvent>? EventQueue { get; }

#endregion

Expand Down
20 changes: 20 additions & 0 deletions src/Compilers/Core/Portable/Compilation/SemanticModelProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Provides semantic models for syntax trees in a compilation.
/// This provider can be attached to a compilation, see <see cref="Compilation.SemanticModelProvider"/>.
/// </summary>
internal abstract class SemanticModelProvider
{
/// <summary>
/// Gets a <see cref="SemanticModel"/> for the given <paramref name="tree"/> that belongs to the given <paramref name="compilation"/>.
/// </summary>
public abstract SemanticModel GetSemanticModel(SyntaxTree tree, Compilation compilation, bool ignoreAccessibility = false);
}
}
1 change: 0 additions & 1 deletion src/Compilers/Core/Portable/Diagnostic/Diagnostic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,6 @@ internal static Diagnostic Create(DiagnosticInfo info)
var suppressMessageState = new SuppressMessageAttributeState(compilation);
if (!suppressMessageState.IsDiagnosticSuppressed(
this,
getSemanticModel: (compilation, tree) => compilation.GetSemanticModel(tree),
out attribute))
{
attribute = null;
Expand Down
Loading

0 comments on commit 897d928

Please sign in to comment.