Skip to content
Permalink
Browse files

Pool analyzer diagnostic reporters (#39034)

This has shown up as a significant source of allocations. The design
here is that when an analyzer reports a diagnostic we may need to filter
or alter it based on some configuration. Previously we stored the
configuration information by closing over captured variables, but that
could be very expensive if it's being done for every analyzer
invocation. Since we can only really execute one analyzer per running
thread on the machine, the actual number of concurrent objects we need
to create is approximately the number of running threads. Pooling is a
good solution to this problem.
  • Loading branch information...
agocke committed Oct 9, 2019
1 parent 5a24d13 commit e2879fc070f658b7be3b09960228bd946f5183b8
@@ -0,0 +1,119 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Diagnostics
{
internal partial class AnalyzerExecutor
{
/// <summary>
/// Pooled object that carries the info needed to process
/// a reported diagnostic from a syntax node action.
/// </summary>
private sealed class AnalyzerDiagnosticReporter
{
public readonly Action<Diagnostic> AddDiagnosticAction;

private static readonly ObjectPool<AnalyzerDiagnosticReporter> s_objectPool =
new ObjectPool<AnalyzerDiagnosticReporter>(() => new AnalyzerDiagnosticReporter(), 10);

public static AnalyzerDiagnosticReporter GetInstance(
SyntaxTree contextTree,
TextSpan? span,
Compilation compilation,
DiagnosticAnalyzer analyzer,
bool isSyntaxDiagnostic,
Action<Diagnostic> addNonCategorizedDiagnosticOpt,
Action<Diagnostic, DiagnosticAnalyzer, bool> addCategorizedLocalDiagnosticOpt,
Action<Diagnostic, DiagnosticAnalyzer> addCategorizedNonLocalDiagnosticOpt,
Func<Diagnostic, DiagnosticAnalyzer, Compilation, CancellationToken, bool> shouldSuppressGeneratedCodeDiagnostic,
CancellationToken cancellationToken)
{
var item = s_objectPool.Allocate();
item._contextTree = contextTree;
item._span = span;
item._compilation = compilation;
item._analyzer = analyzer;
item._isSyntaxDiagnostic = isSyntaxDiagnostic;
item._addNonCategorizedDiagnosticOpt = addNonCategorizedDiagnosticOpt;
item._addCategorizedLocalDiagnosticOpt = addCategorizedLocalDiagnosticOpt;
item._addCategorizedNonLocalDiagnosticOpt = addCategorizedNonLocalDiagnosticOpt;
item._shouldSuppressGeneratedCodeDiagnostic = shouldSuppressGeneratedCodeDiagnostic;
item._cancellationToken = cancellationToken;
return item;
}

public void Free()
{
_contextTree = default!;
_span = default;
_compilation = default!;
_analyzer = default!;
_isSyntaxDiagnostic = default;
_addNonCategorizedDiagnosticOpt = default!;
_addCategorizedLocalDiagnosticOpt = default!;
_addCategorizedNonLocalDiagnosticOpt = default!;
_shouldSuppressGeneratedCodeDiagnostic = default!;
_cancellationToken = default;
s_objectPool.Free(this);
}

private SyntaxTree _contextTree;
private TextSpan? _span;
private Compilation _compilation;
private DiagnosticAnalyzer _analyzer;
private bool _isSyntaxDiagnostic;
private Action<Diagnostic> _addNonCategorizedDiagnosticOpt;
private Action<Diagnostic, DiagnosticAnalyzer, bool> _addCategorizedLocalDiagnosticOpt;
private Action<Diagnostic, DiagnosticAnalyzer> _addCategorizedNonLocalDiagnosticOpt;
private Func<Diagnostic, DiagnosticAnalyzer, Compilation, CancellationToken, bool> _shouldSuppressGeneratedCodeDiagnostic;
private CancellationToken _cancellationToken;

// Pooled objects are initialized in their GetInstance method
#pragma warning disable 8618
private AnalyzerDiagnosticReporter()
{
AddDiagnosticAction = AddDiagnostic;
}
#pragma warning restore 8618

private void AddDiagnostic(Diagnostic diagnostic)
{
if (_shouldSuppressGeneratedCodeDiagnostic(diagnostic, _analyzer, _compilation, _cancellationToken))
{
return;
}

if (_addCategorizedLocalDiagnosticOpt == null)
{
RoslynDebug.Assert(_addNonCategorizedDiagnosticOpt != null);
_addNonCategorizedDiagnosticOpt(diagnostic);
return;
}

Debug.Assert(_addNonCategorizedDiagnosticOpt == null);
RoslynDebug.Assert(_addCategorizedNonLocalDiagnosticOpt != null);

if (diagnostic.Location.IsInSource &&
_contextTree == diagnostic.Location.SourceTree &&
(!_span.HasValue || _span.Value.IntersectsWith(diagnostic.Location.SourceSpan)))
{
_addCategorizedLocalDiagnosticOpt(diagnostic, _analyzer, _isSyntaxDiagnostic);
}
else
{
_addCategorizedNonLocalDiagnosticOpt(diagnostic, _analyzer);
}
}
}
}
}
@@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics
/// <summary>
/// Contains the core execution logic for callbacks into analyzers.
/// </summary>
internal class AnalyzerExecutor
internal partial class AnalyzerExecutor
{
private const string DiagnosticCategory = "Compiler";

@@ -663,7 +663,7 @@ public void MarkSymbolEndAnalysisComplete(ISymbol symbol, DiagnosticAnalyzer ana
return;
}

var addDiagnostic = GetAddDiagnostic(semanticModel.SyntaxTree, analyzer, isSyntaxDiagnostic: false);
var diagReporter = GetAddDiagnostic(semanticModel.SyntaxTree, analyzer, isSyntaxDiagnostic: false);
Func<Diagnostic, bool> isSupportedDiagnostic = d => IsSupportedDiagnostic(analyzer, d);

foreach (var semanticModelAction in semanticModelActions)
@@ -672,7 +672,7 @@ public void MarkSymbolEndAnalysisComplete(ISymbol symbol, DiagnosticAnalyzer ana
{
_cancellationToken.ThrowIfCancellationRequested();

var context = new SemanticModelAnalysisContext(semanticModel, _analyzerOptions, addDiagnostic,
var context = new SemanticModelAnalysisContext(semanticModel, _analyzerOptions, diagReporter.AddDiagnosticAction,
isSupportedDiagnostic, _cancellationToken);

// Catch Exception from action.
@@ -685,6 +685,8 @@ public void MarkSymbolEndAnalysisComplete(ISymbol symbol, DiagnosticAnalyzer ana
analyzerStateOpt?.ProcessedActions.Add(semanticModelAction);
}
}

diagReporter.Free();
}

/// <summary>
@@ -740,7 +742,7 @@ public void MarkSymbolEndAnalysisComplete(ISymbol symbol, DiagnosticAnalyzer ana
return;
}

var addDiagnostic = GetAddDiagnostic(tree, analyzer, isSyntaxDiagnostic: true);
var diagReporter = GetAddDiagnostic(tree, analyzer, isSyntaxDiagnostic: true);
Func<Diagnostic, bool> isSupportedDiagnostic = d => IsSupportedDiagnostic(analyzer, d);

foreach (var syntaxTreeAction in syntaxTreeActions)
@@ -749,7 +751,7 @@ public void MarkSymbolEndAnalysisComplete(ISymbol symbol, DiagnosticAnalyzer ana
{
_cancellationToken.ThrowIfCancellationRequested();

var context = new SyntaxTreeAnalysisContext(tree, _analyzerOptions, addDiagnostic, isSupportedDiagnostic, _compilation, _cancellationToken);
var context = new SyntaxTreeAnalysisContext(tree, _analyzerOptions, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, _compilation, _cancellationToken);

// Catch Exception from action.
ExecuteAndCatchIfThrows(
@@ -761,6 +763,8 @@ public void MarkSymbolEndAnalysisComplete(ISymbol symbol, DiagnosticAnalyzer ana
analyzerStateOpt?.ProcessedActions.Add(syntaxTreeAction);
}
}

diagReporter.Free();
}

private void ExecuteSyntaxNodeAction<TLanguageKindEnum>(
@@ -962,7 +966,7 @@ public void MarkSymbolEndAnalysisComplete(ISymbol symbol, DiagnosticAnalyzer ana
blockEndActions.AddAll(endActions);
}

var addDiagnostic = GetAddDiagnostic(semanticModel.SyntaxTree, declaredNode.FullSpan, analyzer, isSyntaxDiagnostic: false);
var diagReporter = GetAddDiagnostic(semanticModel.SyntaxTree, declaredNode.FullSpan, analyzer, isSyntaxDiagnostic: false);
Func<Diagnostic, bool> isSupportedDiagnostic = d => IsSupportedDiagnostic(analyzer, d);

try
@@ -1036,20 +1040,22 @@ public void MarkSymbolEndAnalysisComplete(ISymbol symbol, DiagnosticAnalyzer ana
{
var executableNodeActionsByKind = GetNodeActionsByKind(syntaxNodeActions);
var syntaxNodesToAnalyze = (IEnumerable<SyntaxNode>)getNodesToAnalyze(executableBlocks);
ExecuteSyntaxNodeActions(syntaxNodesToAnalyze, executableNodeActionsByKind, analyzer, declaredSymbol, semanticModel, getKind, addDiagnostic, isSupportedDiagnostic, analyzerStateOpt?.ExecutableNodesAnalysisState as SyntaxNodeAnalyzerStateData);
ExecuteSyntaxNodeActions(syntaxNodesToAnalyze, executableNodeActionsByKind, analyzer, declaredSymbol, semanticModel, getKind, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, analyzerStateOpt?.ExecutableNodesAnalysisState as SyntaxNodeAnalyzerStateData);
}
else if (operationActions != null)
{
var operationActionsByKind = GetOperationActionsByKind(operationActions);
var operationsToAnalyze = (IEnumerable<IOperation>)getNodesToAnalyze(executableBlocks);
ExecuteOperationActions(operationsToAnalyze, operationActionsByKind, analyzer, declaredSymbol, semanticModel, addDiagnostic, isSupportedDiagnostic, analyzerStateOpt?.ExecutableNodesAnalysisState as OperationAnalyzerStateData);
ExecuteOperationActions(operationsToAnalyze, operationActionsByKind, analyzer, declaredSymbol, semanticModel, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, analyzerStateOpt?.ExecutableNodesAnalysisState as OperationAnalyzerStateData);
}
}

executableNodeActions.Free();

ExecuteBlockActions(blockActions, declaredNode, declaredSymbol, analyzer, semanticModel, operationBlocks, addDiagnostic, isSupportedDiagnostic, analyzerStateOpt);
ExecuteBlockActions(blockEndActions, declaredNode, declaredSymbol, analyzer, semanticModel, operationBlocks, addDiagnostic, isSupportedDiagnostic, analyzerStateOpt);
ExecuteBlockActions(blockActions, declaredNode, declaredSymbol, analyzer, semanticModel, operationBlocks, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, analyzerStateOpt);
ExecuteBlockActions(blockEndActions, declaredNode, declaredSymbol, analyzer, semanticModel, operationBlocks, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, analyzerStateOpt);

diagReporter.Free();
}

private void ExecuteBlockActions<TBlockAction, TNodeStateData>(
@@ -1190,9 +1196,10 @@ public void MarkSymbolEndAnalysisComplete(ISymbol symbol, DiagnosticAnalyzer ana
return;
}

var addDiagnostic = GetAddDiagnostic(model.SyntaxTree, filterSpan, analyzer, isSyntaxDiagnostic: false);
var diagReporter = GetAddDiagnostic(model.SyntaxTree, filterSpan, analyzer, isSyntaxDiagnostic: false);
Func<Diagnostic, bool> isSupportedDiagnostic = d => IsSupportedDiagnostic(analyzer, d);
ExecuteSyntaxNodeActions(nodesToAnalyze, nodeActionsByKind, analyzer, containingSymbol, model, getKind, addDiagnostic, isSupportedDiagnostic, analyzerStateOpt);
ExecuteSyntaxNodeActions(nodesToAnalyze, nodeActionsByKind, analyzer, containingSymbol, model, getKind, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, analyzerStateOpt);
diagReporter.Free();
}

private void ExecuteSyntaxNodeActions<TLanguageKindEnum>(
@@ -1330,9 +1337,10 @@ public void MarkSymbolEndAnalysisComplete(ISymbol symbol, DiagnosticAnalyzer ana
return;
}

var addDiagnostic = GetAddDiagnostic(model.SyntaxTree, filterSpan, analyzer, isSyntaxDiagnostic: false);
var diagReporter = GetAddDiagnostic(model.SyntaxTree, filterSpan, analyzer, isSyntaxDiagnostic: false);
Func<Diagnostic, bool> isSupportedDiagnostic = d => IsSupportedDiagnostic(analyzer, d);
ExecuteOperationActions(operationsToAnalyze, operationActionsByKind, analyzer, containingSymbol, model, addDiagnostic, isSupportedDiagnostic, analyzerStateOpt);
ExecuteOperationActions(operationsToAnalyze, operationActionsByKind, analyzer, containingSymbol, model, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, analyzerStateOpt);
diagReporter.Free();
}

private void ExecuteOperationActions(
@@ -1670,16 +1678,16 @@ private Action<Diagnostic> GetAddCompilationDiagnostic(DiagnosticAnalyzer analyz
};
}

private Action<Diagnostic> GetAddDiagnostic(SyntaxTree tree, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic)
private AnalyzerDiagnosticReporter GetAddDiagnostic(SyntaxTree tree, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic)
{
return GetAddDiagnostic(tree, null, _compilation, analyzer, isSyntaxDiagnostic,
return AnalyzerDiagnosticReporter.GetInstance(tree, null, _compilation, analyzer, isSyntaxDiagnostic,
_addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt,
_shouldSuppressGeneratedCodeDiagnostic, _cancellationToken);
}

private Action<Diagnostic> GetAddDiagnostic(SyntaxTree tree, TextSpan? span, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic)
private AnalyzerDiagnosticReporter GetAddDiagnostic(SyntaxTree tree, TextSpan? span, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic)
{
return GetAddDiagnostic(tree, span, _compilation, analyzer, false,
return AnalyzerDiagnosticReporter.GetInstance(tree, span, _compilation, analyzer, false,
_addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt,
_shouldSuppressGeneratedCodeDiagnostic, _cancellationToken);
}

0 comments on commit e2879fc

Please sign in to comment.
You can’t perform that action at this time.