diff --git a/Compilers.sln b/Compilers.sln index 44db2147d97a6..0dac82037acb6 100644 --- a/Compilers.sln +++ b/Compilers.sln @@ -162,6 +162,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "vbc-arm64", "src\Compilers\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.EndToEnd.UnitTests", "src\Compilers\CSharp\Test\EndToEnd\Microsoft.CodeAnalysis.CSharp.EndToEnd.UnitTests.csproj", "{F3D9264A-7CAE-4265-AF48-0C863301F51E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Experimental", "src\Compilers\Core\Experimental\Microsoft.CodeAnalysis.Experimental.csproj", "{C5968646-6AFF-4414-928C-2524CC43FABE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -416,6 +418,10 @@ Global {F3D9264A-7CAE-4265-AF48-0C863301F51E}.Debug|Any CPU.Build.0 = Debug|Any CPU {F3D9264A-7CAE-4265-AF48-0C863301F51E}.Release|Any CPU.ActiveCfg = Release|Any CPU {F3D9264A-7CAE-4265-AF48-0C863301F51E}.Release|Any CPU.Build.0 = Release|Any CPU + {C5968646-6AFF-4414-928C-2524CC43FABE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5968646-6AFF-4414-928C-2524CC43FABE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5968646-6AFF-4414-928C-2524CC43FABE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5968646-6AFF-4414-928C-2524CC43FABE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -491,6 +497,7 @@ Global {810B02AD-2EA5-4422-88AC-B71B8AB0DF0B} = {C65C6143-BED3-46E6-869E-9F0BE6E84C37} {48C93F90-8776-4847-96D8-127B896D6C80} = {C65C6143-BED3-46E6-869E-9F0BE6E84C37} {F3D9264A-7CAE-4265-AF48-0C863301F51E} = {32A48625-F0AD-419D-828B-A50BDABA38EA} + {C5968646-6AFF-4414-928C-2524CC43FABE} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6F599E08-A9EA-4FAA-897F-5D824B0210E6} diff --git a/Roslyn.sln b/Roslyn.sln index 7b3342cc3ad33..eee1369e40f99 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -511,6 +511,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Net.Compilers.Too EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.EndToEnd.UnitTests", "src\Compilers\CSharp\Test\EndToEnd\Microsoft.CodeAnalysis.CSharp.EndToEnd.UnitTests.csproj", "{C247414A-8946-4BAB-BE1F-C82B90C63EF6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Experimental", "src\Compilers\Core\Experimental\Microsoft.CodeAnalysis.Experimental.csproj", "{FE1E0430-D5F0-47CE-8AE1-C41631F72C12}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1241,6 +1243,10 @@ Global {C247414A-8946-4BAB-BE1F-C82B90C63EF6}.Debug|Any CPU.Build.0 = Debug|Any CPU {C247414A-8946-4BAB-BE1F-C82B90C63EF6}.Release|Any CPU.ActiveCfg = Release|Any CPU {C247414A-8946-4BAB-BE1F-C82B90C63EF6}.Release|Any CPU.Build.0 = Release|Any CPU + {FE1E0430-D5F0-47CE-8AE1-C41631F72C12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE1E0430-D5F0-47CE-8AE1-C41631F72C12}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE1E0430-D5F0-47CE-8AE1-C41631F72C12}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE1E0430-D5F0-47CE-8AE1-C41631F72C12}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1473,6 +1479,7 @@ Global {6131713D-DFB4-49B5-8010-50071FED3E85} = {C52D8057-43AF-40E6-A01B-6CDBB7301985} {A9A8ADE5-F123-4109-9FA4-4B92F1657043} = {C52D8057-43AF-40E6-A01B-6CDBB7301985} {C247414A-8946-4BAB-BE1F-C82B90C63EF6} = {32A48625-F0AD-419D-828B-A50BDABA38EA} + {FE1E0430-D5F0-47CE-8AE1-C41631F72C12} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} diff --git a/src/Compilers/Core/Experimental/GeneratorExtensions.cs b/src/Compilers/Core/Experimental/GeneratorExtensions.cs new file mode 100644 index 0000000000000..65bfffd4e7da7 --- /dev/null +++ b/src/Compilers/Core/Experimental/GeneratorExtensions.cs @@ -0,0 +1,33 @@ +// 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.Threading; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.Experimental +{ + public static partial class ExperimentalApis + { + public static void RegisterHostOutput(ref this IncrementalGeneratorInitializationContext @this, IncrementalValuesProvider source, Action action) + { + source.Node.RegisterOutput(new HostOutputNode(source.Node, action)); + } + + public static ImmutableArray<(string Key, string Value)> GetHostOutputs(this GeneratorRunResult runResult) => runResult.HostOutputs; + } + + public readonly struct HostProductionContext + { + internal readonly ArrayBuilder<(string, string)> Outputs; + + internal HostProductionContext(ArrayBuilder<(string, string)> outputs) + { + Outputs = outputs; + } + + public void AddOutput(string name, string value) => Outputs.Add((name, value)); + } +} diff --git a/src/Compilers/Core/Experimental/HostOutputNode.cs b/src/Compilers/Core/Experimental/HostOutputNode.cs new file mode 100644 index 0000000000000..160359fe2d0e8 --- /dev/null +++ b/src/Compilers/Core/Experimental/HostOutputNode.cs @@ -0,0 +1,96 @@ +// 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.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Text; +using System.Threading; +using Microsoft.CodeAnalysis.Experimental; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; +using TOutput = System.Collections.Immutable.ImmutableArray<(string, string)>; + +namespace Microsoft.CodeAnalysis +{ + internal sealed class HostOutputNode : IIncrementalGeneratorOutputNode, IIncrementalGeneratorNode + { + private readonly IIncrementalGeneratorNode _source; + + private readonly Action _action; + + public HostOutputNode(IIncrementalGeneratorNode source, Action action) + { + _source = source; + _action = action; + } + + public IncrementalGeneratorOutputKind Kind => (IncrementalGeneratorOutputKind)0b100000; // several steps higher than IncrementalGeneratorOutputKind.Implementation + + public NodeStateTable UpdateStateTable(DriverStateTable.Builder graphState, NodeStateTable previousTable, CancellationToken cancellationToken) + { + string stepName = "HostOutput"; + var sourceTable = graphState.GetLatestStateTableForNode(_source); + if (sourceTable.IsCached) + { + if (graphState.DriverState.TrackIncrementalSteps) + { + return previousTable.CreateCachedTableWithUpdatedSteps(sourceTable, stepName, EqualityComparer.Default); + } + return previousTable; + } + + var nodeTable = graphState.CreateTableBuilder(previousTable, stepName, EqualityComparer.Default); + foreach (var entry in sourceTable) + { + var inputs = nodeTable.TrackIncrementalSteps ? ImmutableArray.Create((entry.Step!, entry.OutputIndex)) : default; + if (entry.State == EntryState.Removed) + { + nodeTable.TryRemoveEntries(TimeSpan.Zero, inputs); + } + else if (entry.State != EntryState.Cached || !nodeTable.TryUseCachedEntries(TimeSpan.Zero, inputs)) + { + ArrayBuilder<(string, string)> output = ArrayBuilder<(string, string)>.GetInstance(); + HostProductionContext context = new HostProductionContext(output); + var stopwatch = SharedStopwatch.StartNew(); + _action(context, entry.Item, cancellationToken); + nodeTable.AddEntry(output.ToImmutableAndFree(), EntryState.Added, stopwatch.Elapsed, inputs, EntryState.Added); + } + } + + return nodeTable.ToImmutableAndFree(); + } + + public void AppendOutputs(IncrementalExecutionContext context, CancellationToken cancellationToken) + { + // get our own state table + Debug.Assert(context.TableBuilder is not null); + var table = context.TableBuilder.GetLatestStateTableForNode(this); + + // add each non-removed entry to the context + foreach (var (list, state, _, _) in table) + { + if (state != EntryState.Removed) + { + context.HostOutputBuilder.AddRange(list); + } + } + + if (context.GeneratorRunStateBuilder.RecordingExecutedSteps) + { + context.GeneratorRunStateBuilder.RecordStepsFromOutputNodeUpdate(table); + } + } + + + IIncrementalGeneratorNode IIncrementalGeneratorNode.WithComparer(IEqualityComparer comparer) => throw ExceptionUtilities.Unreachable; + + public IIncrementalGeneratorNode WithTrackingName(string name) => throw ExceptionUtilities.Unreachable; + + void IIncrementalGeneratorNode.RegisterOutput(IIncrementalGeneratorOutputNode output) => throw ExceptionUtilities.Unreachable; + + + } +} diff --git a/src/Compilers/Core/Experimental/Microsoft.CodeAnalysis.Experimental.csproj b/src/Compilers/Core/Experimental/Microsoft.CodeAnalysis.Experimental.csproj new file mode 100644 index 0000000000000..a71001198782c --- /dev/null +++ b/src/Compilers/Core/Experimental/Microsoft.CodeAnalysis.Experimental.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp3.1;netstandard2.0 + + true + Microsoft.CodeAnalysis.Experimental + + This package provides access to experimental Microsoft .NET Compiler Platform ("Roslyn") APIs. + The APIs exposed in this package are not supported in any way, and may be changed or removed without notice. + + + + + + + + diff --git a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj index 9cfc181b99223..6a493ecfbafd4 100644 --- a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj @@ -45,6 +45,7 @@ + diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs index aff21470866e5..cfa13bb7f0015 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @@ -163,7 +163,8 @@ public GeneratorDriverRunResult GetRunResult() generatedSources: getGeneratorSources(generatorState), elapsedTime: generatorState.ElapsedTime, namedSteps: generatorState.ExecutedSteps, - outputSteps: generatorState.OutputSteps)); + outputSteps: generatorState.OutputSteps, + hostOutputs: generatorState.HostOutputs)); return new GeneratorDriverRunResult(results, _state.RunTime); static ImmutableArray getGeneratorSources(GeneratorState generatorState) @@ -293,10 +294,10 @@ internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, Diagnos { // We do not support incremental step tracking for v1 generators, as the pipeline is implicitly defined. var context = UpdateOutputs(generatorState.OutputNodes, IncrementalGeneratorOutputKind.Source | IncrementalGeneratorOutputKind.Implementation, new GeneratorRunStateTable.Builder(state.TrackIncrementalSteps), cancellationToken, driverStateBuilder); - (var sources, var generatorDiagnostics, var generatorRunStateTable) = context.ToImmutableAndFree(); + (var sources, var generatorDiagnostics, var generatorRunStateTable, var hostOutputs) = context.ToImmutableAndFree(); generatorDiagnostics = FilterDiagnostics(compilation, generatorDiagnostics, driverDiagnostics: diagnosticsBag, cancellationToken); - stateBuilder[i] = generatorState.WithResults(ParseAdditionalSources(state.Generators[i], sources, cancellationToken), generatorDiagnostics, generatorRunStateTable.ExecutedSteps, generatorRunStateTable.OutputSteps, generatorTimer.Elapsed); + stateBuilder[i] = generatorState.WithResults(ParseAdditionalSources(state.Generators[i], sources, cancellationToken), generatorDiagnostics, generatorRunStateTable.ExecutedSteps, generatorRunStateTable.OutputSteps, hostOutputs, generatorTimer.Elapsed); } catch (UserFunctionException ufe) { diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs index 72ae1a1daa884..1ddc1cc5692a4 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorState.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; + namespace Microsoft.CodeAnalysis { /// @@ -20,6 +21,7 @@ internal readonly struct GeneratorState ImmutableArray.Empty, ImmutableDictionary>.Empty, ImmutableDictionary>.Empty, + ImmutableArray<(string, string)>.Empty, exception: null, elapsedTime: TimeSpan.Zero); @@ -27,11 +29,30 @@ internal readonly struct GeneratorState /// Creates a new generator state that contains information, constant trees and an execution pipeline /// public GeneratorState(ImmutableArray postInitTrees, ImmutableArray inputNodes, ImmutableArray outputNodes) - : this(postInitTrees, inputNodes, outputNodes, ImmutableArray.Empty, ImmutableArray.Empty, ImmutableDictionary>.Empty, ImmutableDictionary>.Empty, exception: null, elapsedTime: TimeSpan.Zero) + : this(postInitTrees, + inputNodes, + outputNodes, + ImmutableArray.Empty, + ImmutableArray.Empty, + ImmutableDictionary>.Empty, + ImmutableDictionary>.Empty, + ImmutableArray<(string, string)>.Empty, + exception: null, + elapsedTime: TimeSpan.Zero) { } - private GeneratorState(ImmutableArray postInitTrees, ImmutableArray inputNodes, ImmutableArray outputNodes, ImmutableArray generatedTrees, ImmutableArray diagnostics, ImmutableDictionary> executedSteps, ImmutableDictionary> outputSteps, Exception? exception, TimeSpan elapsedTime) + private GeneratorState( + ImmutableArray postInitTrees, + ImmutableArray inputNodes, + ImmutableArray outputNodes, + ImmutableArray generatedTrees, + ImmutableArray diagnostics, + ImmutableDictionary> executedSteps, + ImmutableDictionary> outputSteps, + ImmutableArray<(string Key, string Value)> hostOutputs, + Exception? exception, + TimeSpan elapsedTime) { this.Initialized = true; this.PostInitTrees = postInitTrees; @@ -41,6 +62,7 @@ private GeneratorState(ImmutableArray postInitTrees, Immuta this.Diagnostics = diagnostics; this.ExecutedSteps = executedSteps; this.OutputSteps = outputSteps; + this.HostOutputs = hostOutputs; this.Exception = exception; this.ElapsedTime = elapsedTime; } @@ -49,6 +71,7 @@ public GeneratorState WithResults(ImmutableArray generatedT ImmutableArray diagnostics, ImmutableDictionary> executedSteps, ImmutableDictionary> outputSteps, + ImmutableArray<(string Key, string Value)> hostOutputs, TimeSpan elapsedTime) { return new GeneratorState(this.PostInitTrees, @@ -58,6 +81,7 @@ public GeneratorState WithResults(ImmutableArray generatedT diagnostics, executedSteps, outputSteps, + hostOutputs, exception: null, elapsedTime); } @@ -71,6 +95,7 @@ public GeneratorState WithError(Exception exception, Diagnostic error, TimeSpan ImmutableArray.Create(error), ImmutableDictionary>.Empty, ImmutableDictionary>.Empty, + ImmutableArray<(string, string)>.Empty, exception, elapsedTime); } @@ -94,5 +119,7 @@ public GeneratorState WithError(Exception exception, Diagnostic error, TimeSpan internal ImmutableDictionary> ExecutedSteps { get; } internal ImmutableDictionary> OutputSteps { get; } + + internal ImmutableArray<(string Key, string Value)> HostOutputs { get; } } } diff --git a/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs b/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs index 56988c5b19c25..aeecf8e0cf1a0 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Text; using System.Threading; @@ -166,16 +167,19 @@ internal readonly struct IncrementalExecutionContext internal readonly GeneratorRunStateTable.Builder GeneratorRunStateBuilder; + internal readonly ArrayBuilder<(string Key, string Value)> HostOutputBuilder; + public IncrementalExecutionContext(DriverStateTable.Builder? tableBuilder, GeneratorRunStateTable.Builder generatorRunStateBuilder, AdditionalSourcesCollection sources) { TableBuilder = tableBuilder; GeneratorRunStateBuilder = generatorRunStateBuilder; Sources = sources; + HostOutputBuilder = ArrayBuilder<(string, string)>.GetInstance(); Diagnostics = DiagnosticBag.GetInstance(); } - internal (ImmutableArray sources, ImmutableArray diagnostics, GeneratorRunStateTable executedSteps) ToImmutableAndFree() - => (Sources.ToImmutableAndFree(), Diagnostics.ToReadOnlyAndFree(), GeneratorRunStateBuilder.ToImmutableAndFree()); + internal (ImmutableArray sources, ImmutableArray diagnostics, GeneratorRunStateTable executedSteps, ImmutableArray<(string Key, string Value)> hostOutputs) ToImmutableAndFree() + => (Sources.ToImmutableAndFree(), Diagnostics.ToReadOnlyAndFree(), GeneratorRunStateBuilder.ToImmutableAndFree(), HostOutputBuilder.ToImmutableAndFree()); internal void Free() { diff --git a/src/Compilers/Core/Portable/SourceGeneration/RunResults.cs b/src/Compilers/Core/Portable/SourceGeneration/RunResults.cs index dda8cb5da2406..c95d9c2f966fb 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/RunResults.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/RunResults.cs @@ -77,7 +77,15 @@ public ImmutableArray GeneratedTrees /// public readonly struct GeneratorRunResult { - internal GeneratorRunResult(ISourceGenerator generator, ImmutableArray generatedSources, ImmutableArray diagnostics, ImmutableDictionary> namedSteps, ImmutableDictionary> outputSteps, Exception? exception, TimeSpan elapsedTime) + internal GeneratorRunResult( + ISourceGenerator generator, + ImmutableArray generatedSources, + ImmutableArray diagnostics, + ImmutableDictionary> namedSteps, + ImmutableDictionary> outputSteps, + ImmutableArray<(string Key, string Value)> hostOutputs, + Exception? exception, + TimeSpan elapsedTime) { Debug.Assert(exception is null || (generatedSources.IsEmpty && diagnostics.Length == 1)); @@ -86,6 +94,7 @@ internal GeneratorRunResult(ISourceGenerator generator, ImmutableArray public ImmutableArray Diagnostics { get; } + internal ImmutableArray<(string Key, string Value)> HostOutputs { get; } + /// /// An instance that was thrown by the generator, or null if the generator completed without error. ///