| File: SourceGeneration\SyntaxStore.cs | Web Access |
| Project: src\roslyn\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis) |
// 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. using System; using System.Collections.Immutable; using System.Diagnostics; using System.Threading; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { internal sealed class SyntaxStore { private readonly StateTableStore _tables; private readonly Compilation? _compilation; internal static readonly SyntaxStore Empty = new SyntaxStore(StateTableStore.Empty, compilation: null); private SyntaxStore(StateTableStore tables, Compilation? compilation) { _tables = tables; _compilation = compilation; } public Builder ToBuilder(Compilation compilation, ImmutableArray<SyntaxInputNode> syntaxInputNodes, bool enableTracking, CancellationToken cancellationToken) => new Builder(compilation, syntaxInputNodes, enableTracking, this, cancellationToken); public sealed class Builder { private readonly ImmutableDictionary<SyntaxInputNode, Exception>.Builder _syntaxExceptions = ImmutableDictionary.CreateBuilder<SyntaxInputNode, Exception>(); private readonly ImmutableDictionary<SyntaxInputNode, TimeSpan>.Builder _syntaxTimes = ImmutableDictionary.CreateBuilder<SyntaxInputNode, TimeSpan>(); private readonly StateTableStore.Builder _tableBuilder = new StateTableStore.Builder(); private readonly Compilation _compilation; private readonly ImmutableArray<SyntaxInputNode> _syntaxInputNodes; private readonly bool _enableTracking; private readonly SyntaxStore _previous; private readonly CancellationToken _cancellationToken; internal Builder(Compilation compilation, ImmutableArray<SyntaxInputNode> syntaxInputNodes, bool enableTracking, SyntaxStore previousStore, CancellationToken cancellationToken) { _compilation = compilation; _syntaxInputNodes = syntaxInputNodes; _enableTracking = enableTracking; _previous = previousStore; _cancellationToken = cancellationToken; } public IStateTable GetSyntaxInputTable(SyntaxInputNode syntaxInputNode, NodeStateTable<SyntaxTree> syntaxTreeTable) { Debug.Assert(_syntaxInputNodes.Contains(syntaxInputNode)); // when we don't have a value for this node, we update all the syntax inputs at once if (!_tableBuilder.Contains(syntaxInputNode)) { // CONSIDER: when the compilation is the same as previous, the syntax trees must also be the same. // if we have a previous state table for a node, we can just short circuit knowing that it is up to date // This step isn't part of the tree, so we can skip recording. var compilationIsCached = _compilation == _previous._compilation; // get a builder for each input node var syntaxInputBuilders = ArrayBuilder<(SyntaxInputNode node, ISyntaxInputBuilder builder)>.GetInstance(_syntaxInputNodes.Length); var freedBuilderCount = 0; try { foreach (var node in _syntaxInputNodes) { // We don't cache the tracked incremental steps in a manner that we can easily rehydrate between runs, // so we disable the cached compilation perf optimization when incremental step tracking is enabled. if (compilationIsCached && !_enableTracking && _previous._tables.TryGetValue(node, out var previousStateTable)) { _tableBuilder.SetTable(node, previousStateTable); } else { syntaxInputBuilders.Add((node, node.GetBuilder(_previous._tables, _enableTracking))); _syntaxTimes[node] = TimeSpan.Zero; } } if (syntaxInputBuilders.Count > 0) { // Ensure that even if the node that caused the update was cached, we can still adjust it to take into account other nodes that weren't _syntaxTimes[syntaxInputNode] = TimeSpan.Zero; // at this point we need to grab the syntax trees from the new compilation, and optionally diff them against the old ones NodeStateTable<SyntaxTree> syntaxTreeState = syntaxTreeTable; // update each tree for the builders, sharing the semantic model foreach (var (tree, state, syntaxTreeIndex, stepInfo) in syntaxTreeState) { var root = new Lazy<SyntaxNode>(() => tree.GetRoot(_cancellationToken)); var model = state != EntryState.Removed ? new Lazy<SemanticModel>(() => _compilation.GetSemanticModel(tree)) : null; for (int i = 0; i < syntaxInputBuilders.Count; i++) { var currentNode = syntaxInputBuilders[i].node; try { var sw = SharedStopwatch.StartNew(); try { _cancellationToken.ThrowIfCancellationRequested(); syntaxInputBuilders[i].builder.VisitTree(root, state, model, _cancellationToken); } finally { var elapsed = sw.Elapsed; // if this node isn't the one that caused the update, ensure we remember it and remove the time it took from the requester if (currentNode != syntaxInputNode) { _syntaxTimes[syntaxInputNode] = _syntaxTimes[syntaxInputNode].Subtract(elapsed); _syntaxTimes[currentNode] = _syntaxTimes[currentNode].Add(elapsed); } } } catch (UserFunctionException ufe) { // we're evaluating this node ahead of time, so we can't just throw the exception // instead we'll hold onto it, and throw the exception when a downstream node actually // attempts to read the value _syntaxExceptions[currentNode] = ufe; var builder = syntaxInputBuilders[i].builder; syntaxInputBuilders.RemoveAt(i); builder.FreeUnderlying(); i--; } } } // save the updated inputs while (freedBuilderCount < syntaxInputBuilders.Count) { (var node, ISyntaxInputBuilder builder) = syntaxInputBuilders[freedBuilderCount++]; builder.SaveStateAndFree(_tableBuilder); Debug.Assert(_tableBuilder.Contains(node)); } } } finally { while (freedBuilderCount < syntaxInputBuilders.Count) { syntaxInputBuilders[freedBuilderCount++].builder.FreeUnderlying(); } syntaxInputBuilders.Free(); } } // if we don't have an entry for this node, it must have thrown an exception if (!_tableBuilder.TryGetTable(syntaxInputNode, out var result)) { throw _syntaxExceptions[syntaxInputNode]; } return result; } /// <summary> /// Gets the adjustment to wall clock time that should be applied for a set of input nodes. /// </summary> /// <remarks> /// The syntax store updates all input nodes in parallel the first time an input node is asked to update, /// so that it can share the semantic model between multiple nodes and improve perf. /// /// Unfortunately that means that the first generator to request the results of a syntax node will incorrectly /// have its wall clock time contain the time of all other syntax nodes. And conversely other input nodes will /// not have the true time taken. /// /// This method gets the adjustment that should be applied to the wall clock time for a set of input nodes /// so that the correct time is attributed to each. /// </remarks> public TimeSpan GetRuntimeAdjustment(ImmutableArray<SyntaxInputNode> inputNodes) { TimeSpan result = TimeSpan.Zero; foreach (var node in inputNodes) { // only add if this node ran at all during this pass if (_syntaxTimes.TryGetValue(node, out var adjustment)) { result = result.Add(adjustment); } } return result; } public SyntaxStore ToImmutable() { return new SyntaxStore(_tableBuilder.ToImmutable(), _compilation); } } } }