diff --git a/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs index b299b637fc11a..5eaacc8f204c0 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs @@ -19,6 +19,18 @@ namespace Roslyn.Utilities { internal static partial class EnumerableExtensions { + public static int Count(this IEnumerable source, Func predicate, TArg arg) + { + var count = 0; + foreach (var v in source) + { + if (predicate(v, arg)) + count++; + } + + return count; + } + public static IEnumerable Do(this IEnumerable source, Action action) { if (source == null) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index b8f4c75cf1aae..c7f9578ffe043 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -222,13 +222,6 @@ internal async Task VerifySolutionStateSerializationAsync(Solution solution, Che SolutionStateEqual(solutionObjectFromSolution, solutionObjectFromSyncObject); } - private static void AssertChecksumCollectionEqual( - ChecksumsAndIds collection1, ChecksumsAndIds collection2) - { - AssertChecksumCollectionEqual(collection1.Checksums, collection2.Checksums); - AssertEx.Equal(collection1.Ids, collection2.Ids); - } - private static void AssertChecksumCollectionEqual( ChecksumCollection collection1, ChecksumCollection collection2) { @@ -252,16 +245,17 @@ internal void SolutionCompilationStateEqual(SolutionCompilationStateChecksums so if (solutionObject1.FrozenSourceGeneratedDocumentIdentities.HasValue) AssertChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocumentIdentities.Value, solutionObject2.FrozenSourceGeneratedDocumentIdentities!.Value); - Assert.Equal(solutionObject1.FrozenSourceGeneratedDocumentTexts.HasValue, solutionObject2.FrozenSourceGeneratedDocumentTexts.HasValue); - if (solutionObject1.FrozenSourceGeneratedDocumentTexts.HasValue) - AssertChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocumentTexts.Value, solutionObject2.FrozenSourceGeneratedDocumentTexts!.Value); + Assert.Equal(solutionObject1.FrozenSourceGeneratedDocuments.HasValue, solutionObject2.FrozenSourceGeneratedDocuments.HasValue); + if (solutionObject1.FrozenSourceGeneratedDocuments.HasValue) + AssertDocumentChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocuments.Value, solutionObject2.FrozenSourceGeneratedDocuments!.Value); } internal void SolutionStateEqual(SolutionStateChecksums solutionObject1, SolutionStateChecksums solutionObject2) { Assert.Equal(solutionObject1.Checksum, solutionObject2.Checksum); Assert.Equal(solutionObject1.Attributes, solutionObject2.Attributes); - AssertChecksumCollectionEqual(solutionObject1.Projects, solutionObject2.Projects); + AssertEx.Equals(solutionObject1.Projects.Ids, solutionObject2.Projects.Ids); + AssertChecksumCollectionEqual(solutionObject1.Projects.Checksums, solutionObject2.Projects.Checksums); AssertChecksumCollectionEqual(solutionObject1.AnalyzerReferences, solutionObject2.AnalyzerReferences); ProjectStatesEqual(ToProjectObjects(solutionObject1.Projects.Checksums), ToProjectObjects(solutionObject2.Projects.Checksums)); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs index 474b4bb7e2f1a..23b49cca4c0dc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs @@ -2,7 +2,6 @@ // 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 Microsoft.CodeAnalysis.Shared.Extensions; @@ -11,36 +10,15 @@ namespace Microsoft.CodeAnalysis.Serialization; /// -/// A paired list of IDs (either s or s), and the checksums for their -/// corresponding s or s). +/// A paired list of s, and the checksums for their corresponding 's . /// -internal readonly struct ChecksumsAndIds +internal readonly struct ProjectChecksumsAndIds { public readonly ChecksumCollection Checksums; - public readonly ImmutableArray Ids; + public readonly ImmutableArray Ids; - private static readonly Func s_readId; - private static readonly Action s_writeTo; - - static ChecksumsAndIds() - { - if (typeof(TId) == typeof(ProjectId)) - { - s_readId = reader => (TId)(object)ProjectId.ReadFrom(reader); - s_writeTo = (writer, id) => ((ProjectId)(object)id!).WriteTo(writer); - } - else if (typeof(TId) == typeof(DocumentId)) - { - s_readId = reader => (TId)(object)DocumentId.ReadFrom(reader); - s_writeTo = (writer, id) => ((DocumentId)(object)id!).WriteTo(writer); - } - else - { - throw ExceptionUtilities.Unreachable(); - } - } - - public ChecksumsAndIds(ChecksumCollection checksums, ImmutableArray ids) + public ProjectChecksumsAndIds(ChecksumCollection checksums, ImmutableArray ids) { Contract.ThrowIfTrue(ids.Length != checksums.Children.Length); @@ -54,32 +32,37 @@ public ChecksumsAndIds(ChecksumCollection checksums, ImmutableArray ids) public void WriteTo(ObjectWriter writer) { this.Checksums.WriteTo(writer); - writer.WriteArray(this.Ids, s_writeTo); + writer.WriteArray(this.Ids, static (writer, p) => p.WriteTo(writer)); } - public static ChecksumsAndIds ReadFrom(ObjectReader reader) + public static ProjectChecksumsAndIds ReadFrom(ObjectReader reader) { return new( ChecksumCollection.ReadFrom(reader), - reader.ReadArray(s_readId)); + reader.ReadArray(static reader => ProjectId.ReadFrom(reader))); } public Enumerator GetEnumerator() => new(this); - public struct Enumerator(ChecksumsAndIds checksumsAndIds) + public struct Enumerator(ProjectChecksumsAndIds checksumsAndIds) { - private readonly ChecksumsAndIds _checksumsAndIds = checksumsAndIds; + private readonly ProjectChecksumsAndIds _checksumsAndIds = checksumsAndIds; private int _index = -1; public bool MoveNext() => ++_index < _checksumsAndIds.Length; - public (Checksum checksum, TId id) Current + public readonly (Checksum checksum, ProjectId id) Current => (_checksumsAndIds.Checksums.Children[_index], _checksumsAndIds.Ids[_index]); } } +/// +/// A paired list of s, and the checksums for their corresponding 's and checksums. +/// internal readonly struct DocumentChecksumsAndIds { public readonly Checksum Checksum; @@ -133,7 +116,7 @@ public struct Enumerator(DocumentChecksumsAndIds checksumsAndIds) public bool MoveNext() => ++_index < _checksumsAndIds.Length; - public (Checksum attributeChecksum, Checksum textChecksum, DocumentId id) Current + public readonly (Checksum attributeChecksum, Checksum textChecksum, DocumentId id) Current => (_checksumsAndIds.AttributeChecksums.Children[_index], _checksumsAndIds.TextChecksums.Children[_index], _checksumsAndIds.Ids[_index]); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs index 6f225f909ac20..990d4467db2a7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs @@ -145,7 +145,7 @@ public Task GetDependentChecksumAsync(SolutionCompilationState compila private async Task ComputeDependentChecksumAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken) => Checksum.Create( await UnderlyingTracker.GetDependentChecksumAsync(compilationState, cancellationToken).ConfigureAwait(false), - (await _replacementDocumentStates.GetChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false)).Checksum); + (await _replacementDocumentStates.GetDocumentChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false)).Checksum); public MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index af37ec21138f8..82600b5d6b1da 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; @@ -125,7 +126,7 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo } ChecksumCollection? frozenSourceGeneratedDocumentIdentities = null; - ChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; + DocumentChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes = default; if (FrozenSourceGeneratedDocumentStates != null) @@ -134,7 +135,7 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo var identityChecksums = FrozenSourceGeneratedDocumentStates.SelectAsArray( static (s, arg) => arg.serializer.CreateChecksum(s.Identity, cancellationToken: arg.cancellationToken), (serializer, cancellationToken)); - frozenSourceGeneratedDocumentTexts = await FrozenSourceGeneratedDocumentStates.GetTextChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false); + frozenSourceGeneratedDocumentTexts = await FrozenSourceGeneratedDocumentStates.GetDocumentChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false); frozenSourceGeneratedDocumentIdentities = new ChecksumCollection(identityChecksums); frozenSourceGeneratedDocumentGenerationDateTimes = FrozenSourceGeneratedDocumentStates.SelectAsArray(d => d.GenerationDateTime); } @@ -163,8 +164,12 @@ static Checksum GetVersionMapChecksum(SolutionCompilationState @this) // We want the projects in sorted order so we can generate the checksum for the // source-generation-execution-map consistently. var sortedProjectIds = SolutionState.GetOrCreateSortedProjectIds(@this.SolutionState.ProjectIds); + var supportedCount = sortedProjectIds.Count( + static (projectId, @this) => RemoteSupportedLanguages.IsSupported(@this.SolutionState.GetRequiredProjectState(projectId).Language), + @this); - using var _ = ArrayBuilder.GetInstance(out var checksums); + // For each project, we'll add one checksum for the project id and one for the version map. + using var _ = ArrayBuilder.GetInstance(2 * supportedCount, out var checksums); foreach (var projectId in sortedProjectIds) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 161d55e65cc93..2b3138742a0e0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -7,11 +7,10 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Resources; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -23,18 +22,18 @@ public SolutionCompilationStateChecksums( Checksum solutionState, Checksum sourceGeneratorExecutionVersionMap, // These arrays are all the same length if present, and reference the same documents in the same order. - ChecksumsAndIds? frozenSourceGeneratedDocumentTexts, + DocumentChecksumsAndIds? frozenSourceGeneratedDocuments, ChecksumCollection? frozenSourceGeneratedDocumentIdentities, ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes) { // For the frozen source generated document info, we expect two either have both checksum collections or neither, and they // should both be the same length as there is a 1:1 correspondence between them. - Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities.HasValue == frozenSourceGeneratedDocumentTexts.HasValue); - Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities?.Count == frozenSourceGeneratedDocumentTexts?.Length); + Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities.HasValue == frozenSourceGeneratedDocuments.HasValue); + Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities?.Count == frozenSourceGeneratedDocuments?.Length); SolutionState = solutionState; SourceGeneratorExecutionVersionMap = sourceGeneratorExecutionVersionMap; - FrozenSourceGeneratedDocumentTexts = frozenSourceGeneratedDocumentTexts; + FrozenSourceGeneratedDocuments = frozenSourceGeneratedDocuments; FrozenSourceGeneratedDocumentIdentities = frozenSourceGeneratedDocumentIdentities; FrozenSourceGeneratedDocumentGenerationDateTimes = frozenSourceGeneratedDocumentGenerationDateTimes; @@ -44,7 +43,7 @@ public SolutionCompilationStateChecksums( SolutionState, SourceGeneratorExecutionVersionMap, FrozenSourceGeneratedDocumentIdentities?.Checksum ?? Checksum.Null, - frozenSourceGeneratedDocumentTexts?.Checksum ?? Checksum.Null); + frozenSourceGeneratedDocuments?.Checksum ?? Checksum.Null); } public Checksum Checksum { get; } @@ -54,7 +53,7 @@ public SolutionCompilationStateChecksums( /// /// Checksums of the SourceTexts of the frozen documents directly. Not checksums of their DocumentStates. /// - public ChecksumsAndIds? FrozenSourceGeneratedDocumentTexts { get; } + public DocumentChecksumsAndIds? FrozenSourceGeneratedDocuments { get; } public ChecksumCollection? FrozenSourceGeneratedDocumentIdentities { get; } // note: intentionally not part of the identity contract of this type. @@ -66,7 +65,7 @@ public void AddAllTo(HashSet checksums) checksums.AddIfNotNullChecksum(this.SolutionState); checksums.AddIfNotNullChecksum(this.SourceGeneratorExecutionVersionMap); this.FrozenSourceGeneratedDocumentIdentities?.AddAllTo(checksums); - this.FrozenSourceGeneratedDocumentTexts?.Checksums.AddAllTo(checksums); + this.FrozenSourceGeneratedDocuments?.AddAllTo(checksums); } public void Serialize(ObjectWriter writer) @@ -80,7 +79,7 @@ public void Serialize(ObjectWriter writer) writer.WriteBoolean(this.FrozenSourceGeneratedDocumentIdentities.HasValue); if (FrozenSourceGeneratedDocumentIdentities.HasValue) { - this.FrozenSourceGeneratedDocumentTexts!.Value.WriteTo(writer); + this.FrozenSourceGeneratedDocuments!.Value.WriteTo(writer); this.FrozenSourceGeneratedDocumentIdentities.Value.WriteTo(writer); writer.WriteArray(this.FrozenSourceGeneratedDocumentGenerationDateTimes, static (w, d) => w.WriteInt64(d.Ticks)); } @@ -93,13 +92,13 @@ public static SolutionCompilationStateChecksums Deserialize(ObjectReader reader) var sourceGeneratorExecutionVersionMap = Checksum.ReadFrom(reader); var hasFrozenSourceGeneratedDocuments = reader.ReadBoolean(); - ChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; + DocumentChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; ChecksumCollection? frozenSourceGeneratedDocumentIdentities = null; ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes = default; if (hasFrozenSourceGeneratedDocuments) { - frozenSourceGeneratedDocumentTexts = ChecksumsAndIds.ReadFrom(reader); + frozenSourceGeneratedDocumentTexts = DocumentChecksumsAndIds.ReadFrom(reader); frozenSourceGeneratedDocumentIdentities = ChecksumCollection.ReadFrom(reader); frozenSourceGeneratedDocumentGenerationDateTimes = reader.ReadArray(r => new DateTime(r.ReadInt64())); } @@ -137,7 +136,7 @@ public async Task FindAsync( if (compilationState.FrozenSourceGeneratedDocumentStates != null) { Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); - Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentTexts.HasValue); + Contract.ThrowIfFalse(FrozenSourceGeneratedDocuments.HasValue); // This could either be the checksum for the text (which we'll use our regular helper for first)... if (assetPath.IncludeSolutionFrozenSourceGeneratedDocumentText) @@ -155,7 +154,7 @@ await ChecksumCollection.FindAsync( if (documentId != null) { // If the caller is asking for a specific document, we can just look it up directly. - var index = FrozenSourceGeneratedDocumentTexts.Value.Ids.IndexOf(documentId); + var index = FrozenSourceGeneratedDocuments.Value.Ids.IndexOf(documentId); if (index >= 0) { var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value.Children[index]; @@ -174,7 +173,7 @@ await ChecksumCollection.FindAsync( var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value[0]; if (searchingChecksumsLeft.Remove(identityChecksum)) { - var id = FrozenSourceGeneratedDocumentTexts.Value.Ids[i]; + var id = FrozenSourceGeneratedDocuments.Value.Ids[i]; Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); result[identityChecksum] = state.Identity; } @@ -206,7 +205,7 @@ await ChecksumCollection.FindAsync( internal sealed class SolutionStateChecksums( ProjectId? projectConeId, Checksum attributes, - ChecksumsAndIds projects, + ProjectChecksumsAndIds projects, ChecksumCollection analyzerReferences) { private ProjectCone? _projectCone; @@ -221,7 +220,7 @@ internal sealed class SolutionStateChecksums( public ProjectId? ProjectConeId { get; } = projectConeId; public Checksum Attributes { get; } = attributes; - public ChecksumsAndIds Projects { get; } = projects; + public ProjectChecksumsAndIds Projects { get; } = projects; public ChecksumCollection AnalyzerReferences { get; } = analyzerReferences; // Acceptably not threadsafe. ProjectCone is a class, and the runtime guarantees anyone will see this field fully @@ -259,7 +258,7 @@ public static SolutionStateChecksums Deserialize(ObjectReader reader) var result = new SolutionStateChecksums( projectConeId: reader.ReadBoolean() ? ProjectId.ReadFrom(reader) : null, attributes: Checksum.ReadFrom(reader), - projects: ChecksumsAndIds.ReadFrom(reader), + projects: ProjectChecksumsAndIds.ReadFrom(reader), analyzerReferences: ChecksumCollection.ReadFrom(reader)); Contract.ThrowIfFalse(result.Checksum == checksum); return result; @@ -534,11 +533,12 @@ public static ChecksumCollection GetOrCreateChecksumCollection( references, static (references, tuple) => { - using var _ = ArrayBuilder.GetInstance(references.Count, out var checksums); + var checksums = new Checksum[references.Count]; + var index = 0; foreach (var reference in references) - checksums.Add(tuple.serializer.CreateChecksum(reference, tuple.cancellationToken)); + checksums[index++] = tuple.serializer.CreateChecksum(reference, tuple.cancellationToken); - return new ChecksumCollection(checksums.ToImmutableAndClear()); + return new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(checksums)); }, (serializer, cancellationToken)); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 8a948306662b6..11d2d2593caec 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -10,6 +10,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -128,42 +129,18 @@ public IEnumerable GetStatesInCompilationOrder() } public ImmutableArray SelectAsArray(Func selector) - { - // Directly use ImmutableArray.Builder as we know the final size - var builder = ImmutableArray.CreateBuilder(_map.Count); - - foreach (var (_, state) in _map) - { - builder.Add(selector(state)); - } - - return builder.MoveToImmutable(); - } + => SelectAsArray( + static (state, selector) => selector(state), + selector); public ImmutableArray SelectAsArray(Func selector, TArg arg) { - // Directly use ImmutableArray.Builder as we know the final size - var builder = ImmutableArray.CreateBuilder(_map.Count); - - foreach (var (_, state) in _map) - { - builder.Add(selector(state, arg)); - } - - return builder.MoveToImmutable(); - } - - public async ValueTask> SelectAsArrayAsync(Func> selector, TArg arg, CancellationToken cancellationToken) - { - // Directly use ImmutableArray.Builder as we know the final size - var builder = ImmutableArray.CreateBuilder(_map.Count); - + var result = new TValue[_map.Count]; + var index = 0; foreach (var (_, state) in _map) - { - builder.Add(await selector(state, arg, cancellationToken).ConfigureAwait(true)); - } + result[index++] = selector(state, arg); - return builder.MoveToImmutable(); + return ImmutableCollectionsMarshal.AsImmutableArray(result); } public TextDocumentStates AddRange(ImmutableArray states) @@ -311,48 +288,26 @@ public int Compare(DocumentId? x, DocumentId? y) } } - public async ValueTask> GetChecksumsAndIdsAsync(CancellationToken cancellationToken) - { - var documentTextChecksums = await SelectAsArrayAsync( - static async (state, _, cancellationToken) => await state.GetChecksumAsync(cancellationToken).ConfigureAwait(false), - arg: default(VoidResult), - cancellationToken).ConfigureAwait(false); - - var documentChecksums = new ChecksumCollection(documentTextChecksums); - return new(documentChecksums, SelectAsArray(static s => s.Id)); - } - - public async ValueTask> GetTextChecksumsAndIdsAsync(CancellationToken cancellationToken) - { - var documentTextChecksums = await SelectAsArrayAsync( - static async (state, _, cancellationToken) => - { - var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - return stateChecksums.Text; - }, - arg: default(VoidResult), - cancellationToken).ConfigureAwait(false); - - var documentChecksums = new ChecksumCollection(documentTextChecksums); - return new(documentChecksums, SelectAsArray(static s => s.Id)); - } - public async ValueTask GetDocumentChecksumsAndIdsAsync(CancellationToken cancellationToken) { - using var _1 = ArrayBuilder.GetInstance(_map.Count, out var attributeChecksums); - using var _2 = ArrayBuilder.GetInstance(_map.Count, out var textChecksums); + var attributeChecksums = new Checksum[_map.Count]; + var textChecksums = new Checksum[_map.Count]; + var documentIds = new DocumentId[_map.Count]; - foreach (var (_, state) in _map) + var index = 0; + foreach (var (documentId, state) in _map) { var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - attributeChecksums.Add(stateChecksums.Info); - textChecksums.Add(stateChecksums.Text); + attributeChecksums[index] = stateChecksums.Info; + textChecksums[index] = stateChecksums.Text; + documentIds[index] = documentId; + index++; } return new( - new ChecksumCollection(attributeChecksums.ToImmutableAndClear()), - new ChecksumCollection(textChecksums.ToImmutableAndClear()), - SelectAsArray(static s => s.Id)); + new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(attributeChecksums)), + new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(textChecksums)), + ImmutableCollectionsMarshal.AsImmutableArray(documentIds)); } public void AddDocumentIdsWithFilePath(ref TemporaryArray temporaryArray, string filePath) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 50b8520138855..652ad566b191a 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -34,23 +34,19 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu var solutionAttributes = await GetAssetAsync(AssetPathKind.SolutionAttributes, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); await GetAssetAsync(AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); - // Fetch all the project state checksums up front. That allows gettign all the data in a single call, and + // Fetch all the project state checksums up front. That allows getting all the data in a single call, and // enables parallel fetching of the projects below. - using var _1 = ArrayBuilder.GetInstance(solutionChecksums.Projects.Length, out var allProjectStateChecksums); - await this.GetAssetsAsync>( + using var _1 = ArrayBuilder>.GetInstance(solutionChecksums.Projects.Length, out var projectsTasks); + await this.GetAssetHelper().GetAssetsAsync( AssetPathKind.ProjectStateChecksums, solutionChecksums.Projects.Checksums, - static (_, projectStateChecksums, allProjectStateChecksums) => allProjectStateChecksums.Add(projectStateChecksums), - allProjectStateChecksums, + static (_, projectStateChecksums, tuple) => tuple.projectsTasks.Add(tuple.@this.CreateProjectInfoAsync(projectStateChecksums, tuple.cancellationToken)), + (@this: this, projectsTasks, cancellationToken), cancellationToken).ConfigureAwait(false); - // Fetch the projects in parallel. - using var _2 = ArrayBuilder>.GetInstance(solutionChecksums.Projects.Length, out var projectsTasks); - foreach (var projectStateChecksum in allProjectStateChecksums) - projectsTasks.Add(CreateProjectInfoAsync(projectStateChecksum, cancellationToken)); - var analyzerReferences = await this.GetAssetsArrayAsync(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + // Fetch the projects in parallel. var projects = await Task.WhenAll(projectsTasks).ConfigureAwait(false); return SolutionInfo.Create( solutionAttributes.Id, @@ -71,43 +67,44 @@ public async Task CreateProjectInfoAsync(ProjectStateChecksums proj var compilationOptions = attributes.FixUpCompilationOptions( await GetAssetAsync(new(AssetPathKind.ProjectCompilationOptions, projectId), projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); - var parseOptions = await GetAssetAsync(new(AssetPathKind.ProjectParseOptions, projectId), projectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false); + var parseOptionsTask = GetAssetAsync(new(AssetPathKind.ProjectParseOptions, projectId), projectChecksums.ParseOptions, cancellationToken); - var projectReferences = await this.GetAssetsArrayAsync(new(AssetPathKind.ProjectProjectReferences, projectId), projectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false); - var metadataReferences = await this.GetAssetsArrayAsync(new(AssetPathKind.ProjectMetadataReferences, projectId), projectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false); - var analyzerReferences = await this.GetAssetsArrayAsync(new(AssetPathKind.ProjectAnalyzerReferences, projectId), projectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + var projectReferencesTask = this.GetAssetsArrayAsync(new(AssetPathKind.ProjectProjectReferences, projectId), projectChecksums.ProjectReferences, cancellationToken); + var metadataReferencesTask = this.GetAssetsArrayAsync(new(AssetPathKind.ProjectMetadataReferences, projectId), projectChecksums.MetadataReferences, cancellationToken); + var analyzerReferencesTask = this.GetAssetsArrayAsync(new(AssetPathKind.ProjectAnalyzerReferences, projectId), projectChecksums.AnalyzerReferences, cancellationToken); // Attempt to fetch all the documents for this project in bulk. This will allow for all the data to be fetched // efficiently. We can then go and create the DocumentInfos for each document in the project. await SynchronizeProjectDocumentsAsync(projectChecksums, cancellationToken).ConfigureAwait(false); - var documentInfos = await CreateDocumentInfosAsync(projectChecksums.Documents).ConfigureAwait(false); - var additionalDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AdditionalDocuments).ConfigureAwait(false); - var analyzerConfigDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AnalyzerConfigDocuments).ConfigureAwait(false); + var documentInfosTask = CreateDocumentInfosAsync(projectChecksums.Documents); + var additionalDocumentInfosTask = CreateDocumentInfosAsync(projectChecksums.AdditionalDocuments); + var analyzerConfigDocumentInfosTask = CreateDocumentInfosAsync(projectChecksums.AnalyzerConfigDocuments); return ProjectInfo.Create( attributes, compilationOptions, - parseOptions, - documentInfos, - projectReferences, - metadataReferences, - analyzerReferences, - additionalDocumentInfos, - analyzerConfigDocumentInfos, + await parseOptionsTask.ConfigureAwait(false), + await documentInfosTask.ConfigureAwait(false), + await projectReferencesTask.ConfigureAwait(false), + await metadataReferencesTask.ConfigureAwait(false), + await analyzerReferencesTask.ConfigureAwait(false), + await additionalDocumentInfosTask.ConfigureAwait(false), + await analyzerConfigDocumentInfosTask.ConfigureAwait(false), hostObjectType: null); // TODO: https://github.com/dotnet/roslyn/issues/62804 async Task> CreateDocumentInfosAsync(DocumentChecksumsAndIds checksumsAndIds) { - using var _ = ArrayBuilder.GetInstance(checksumsAndIds.Length, out var documentInfos); + var documentInfos = new DocumentInfo[checksumsAndIds.Length]; + var index = 0; foreach (var (attributeChecksum, textChecksum, documentId) in checksumsAndIds) { cancellationToken.ThrowIfCancellationRequested(); - documentInfos.Add(await CreateDocumentInfoAsync(documentId, attributeChecksum, textChecksum, cancellationToken).ConfigureAwait(false)); + documentInfos[index++] = await CreateDocumentInfoAsync(documentId, attributeChecksum, textChecksum, cancellationToken).ConfigureAwait(false); } - return documentInfos.ToImmutableAndClear(); + return ImmutableCollectionsMarshal.AsImmutableArray(documentInfos); } } @@ -152,6 +149,18 @@ public async Task CreateDocumentInfoAsync( // TODO: do we need version? return new DocumentInfo(attributes, textLoader, documentServiceProvider: null); } + + public AssetHelper GetAssetHelper() + => new(this); + + public readonly struct AssetHelper(AbstractAssetProvider assetProvider) + { + public Task GetAssetsAsync(AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) + => assetProvider.GetAssetsAsync(assetPath, checksums, callback, arg, cancellationToken); + + public Task GetAssetsAsync(AssetPath assetPath, ChecksumCollection checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) + => assetProvider.GetAssetsAsync(assetPath, checksums, callback, arg, cancellationToken); + } } internal static class AbstractAssetProviderExtensions @@ -188,14 +197,14 @@ public static async Task> GetAssetsArrayAsync( using var _1 = PooledHashSet.GetInstance(out var checksumSet); checksumSet.AddAll(checksums.Children); - using var _2 = ArrayBuilder.GetInstance(checksumSet.Count, out var builder); + var builder = ImmutableArray.CreateBuilder(checksumSet.Count); - await assetProvider.GetAssetsAsync>( + await assetProvider.GetAssetHelper().GetAssetsAsync( assetPath, checksumSet, static (checksum, asset, builder) => builder.Add(asset), builder, cancellationToken).ConfigureAwait(false); - return builder.ToImmutableAndClear(); + return builder.MoveToImmutable(); } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 06c57485f348a..e754650d6939a 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -5,15 +5,12 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; @@ -43,7 +40,7 @@ public override async ValueTask GetAssetAsync( checksums.Add(checksum); using var _2 = ArrayBuilder.GetInstance(1, out var builder); - await this.GetAssetsAsync>( + await this.GetAssetHelper().GetAssetsAsync( assetPath, checksums, static (_, asset, builder) => builder.Add(asset), builder, cancellationToken).ConfigureAwait(false); @@ -107,7 +104,7 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() await Task.WhenAll(tasks).ConfigureAwait(false); using var _3 = ArrayBuilder.GetInstance(out var allProjectStateChecksums); - await this.GetAssetsAsync>( + await this.GetAssetHelper().GetAssetsAsync( AssetPathKind.ProjectStateChecksums, solutionStateChecksum.Projects.Checksums, static (_, asset, allProjectStateChecksums) => allProjectStateChecksums.Add(asset), diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index d65562f56b02c..ca3315194f902 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -88,18 +88,18 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca } if (newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.HasValue && - newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentTexts.HasValue && + newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.HasValue && !newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes.IsDefault) { var newSolutionFrozenSourceGeneratedDocumentIdentities = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value; - var newSolutionFrozenSourceGeneratedDocumentTexts = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentTexts.Value; - var count = newSolutionFrozenSourceGeneratedDocumentTexts.Checksums.Count; + var newSolutionFrozenSourceGeneratedDocuments = newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value; + var count = newSolutionFrozenSourceGeneratedDocuments.Ids.Length; using var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity identity, DateTime generationDateTime, SourceText text)>.GetInstance(count, out var frozenDocuments); for (var i = 0; i < count; i++) { - var frozenDocumentId = newSolutionFrozenSourceGeneratedDocumentTexts.Ids[i]; - var frozenDocumentTextChecksum = newSolutionFrozenSourceGeneratedDocumentTexts.Checksums[i]; + var frozenDocumentId = newSolutionFrozenSourceGeneratedDocuments.Ids[i]; + var frozenDocumentTextChecksum = newSolutionFrozenSourceGeneratedDocuments.TextChecksums[i]; var frozenDocumentIdentity = newSolutionFrozenSourceGeneratedDocumentIdentities[i]; var identity = await _assetProvider.GetAssetAsync( @@ -223,7 +223,7 @@ private async Task UpdateProjectsAsync( using var _5 = PooledHashSet.GetInstance(out var newChecksumsToSync); newChecksumsToSync.AddRange(newProjectIdToChecksum.Values); - await _assetProvider.GetAssetsAsync>( + await _assetProvider.GetAssetHelper().GetAssetsAsync( assetPath: AssetPathKind.ProjectStateChecksums, newChecksumsToSync, static (checksum, newProjectStateChecksum, newProjectIdToStateChecksums) => { @@ -267,8 +267,7 @@ await _assetProvider.GetAssetsAsync( assetPath: AssetPathKind.ProjectCompilationOptions, projectItemChecksums, cancellationToken).ConfigureAwait(false); } - using var _2 = ArrayBuilder.GetInstance(out var projectInfos); - using var _3 = ArrayBuilder.GetInstance(out var projectStateChecksumsToAdd); + using var _2 = ArrayBuilder.GetInstance(out var projectStateChecksumsToAdd); // added project foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) @@ -281,6 +280,7 @@ await _assetProvider.GetAssetsAsync( // efficiently in bulk and in parallel. await _assetProvider.SynchronizeProjectAssetsAsync(projectStateChecksumsToAdd, cancellationToken).ConfigureAwait(false); + using var _3 = ArrayBuilder.GetInstance(projectStateChecksumsToAdd.Count, out var projectInfos); foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) { if (!oldProjectIdToStateChecksums.ContainsKey(projectId)) diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 5a5a17d0021a4..6d7e72d98ef17 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; @@ -37,15 +38,15 @@ protected override IRemoteSourceGenerationService CreateService(in ServiceConstr var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)>.GetInstance(documentStates.Ids.Count, out var result); - + var result = new (SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)[documentStates.Ids.Count]; + var index = 0; foreach (var (id, state) in documentStates.States) { Contract.ThrowIfFalse(id.IsSourceGenerated); - result.Add((state.Identity, state.GetContentIdentity(), state.GenerationDateTime)); + result[index++] = (state.Identity, state.GetContentIdentity(), state.GenerationDateTime); } - return result.ToImmutableAndClear(); + return ImmutableCollectionsMarshal.AsImmutableArray(result); }, cancellationToken); } @@ -57,17 +58,17 @@ public ValueTask> GetContentsAsync( var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(documentIds.Length, out var result); - + var result = new string[documentIds.Length]; + var index = 0; foreach (var id in documentIds) { Contract.ThrowIfFalse(id.IsSourceGenerated); var state = documentStates.GetRequiredState(id); var text = await state.GetTextAsync(cancellationToken).ConfigureAwait(false); - result.Add(text.ToString()); + result[index++] = text.ToString(); } - return result.ToImmutableAndClear(); + return ImmutableCollectionsMarshal.AsImmutableArray(result); }, cancellationToken); } @@ -115,7 +116,7 @@ public async ValueTask HasGeneratorsAsync( // the host will cache it. We'll only actually fetch something new and compute something new when an actual new // analyzer reference is added. using var _2 = ArrayBuilder.GetInstance(checksums.Count, out var analyzerReferences); - await assetProvider.GetAssetsAsync>( + await assetProvider.GetAssetHelper().GetAssetsAsync( projectId, checksums, static (_, analyzerReference, analyzerReferences) => analyzerReferences.Add(analyzerReference),