From 8c247f44a0a690831510a107b30537c618d17ede Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 12:31:55 -0700 Subject: [PATCH 01/14] Cache values in dependent project finder --- .../FindReferences/DependentProjectsFinder.cs | 65 +++++++++++++++---- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index 380a3c3bf55f0..9fef504b01055 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -30,6 +30,13 @@ internal static partial class DependentProjectsFinder /// private static ImmutableDictionary s_metadataIdToAssemblyName = ImmutableDictionary.Empty; + private static readonly ConditionalWeakTable< + Solution, + Dictionary< + (IAssemblySymbol assembly, Project? sourceProject, SymbolVisibility visibility), + ImmutableArray<(Project project, bool hasInternalsAccess)>>> s_solutionToDependentProjectMap = new(); + private static readonly SemaphoreSlim s_gate = new(initialCount: 1); + public static async Task> GetDependentProjectsAsync( Solution solution, ImmutableArray symbols, IImmutableSet projects, CancellationToken cancellationToken) { @@ -128,24 +135,56 @@ private static async Task> GetDependentProjectsWorkerAsy SymbolVisibility visibility, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + var dictionary = s_solutionToDependentProjectMap.GetValue(solution, static _ => new()); - var dependentProjects = new HashSet<(Project, bool hasInternalsAccess)>(); + var key = (symbolOrigination.assembly, symbolOrigination.sourceProject, visibility); + ImmutableArray<(Project project, bool hasInternalsAccess)> dependentProjects; - // If a symbol was defined in source, then it is always visible to the project it - // was defined in. - if (symbolOrigination.sourceProject != null) - dependentProjects.Add((symbolOrigination.sourceProject, hasInternalsAccess: true)); + // Check cache first. + using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + if (dictionary.TryGetValue(key, out dependentProjects)) + return dependentProjects; + } + + // Compute if not in cache. + dependentProjects = await ComputeDependentProjectsWorkerAsync( + solution, symbolOrigination, visibility, cancellationToken).ConfigureAwait(false); + + // Try to add to cache, returning existing value if another thread already added it. + using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + if (dictionary.TryGetValue((symbolOrigination.assembly, symbolOrigination.sourceProject, visibility), out dependentProjects)) + return dependentProjects; - // If it's not private, then we need to find possible references. - if (visibility != SymbolVisibility.Private) - AddNonSubmissionDependentProjects(solution, symbolOrigination, dependentProjects, cancellationToken); + return dictionary.GetOrAdd(key, dependentProjects); + } + + static async Task> ComputeDependentProjectsWorkerAsync( + Solution solution, + (IAssemblySymbol assembly, Project? sourceProject) symbolOrigination, + SymbolVisibility visibility, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - // submission projects are special here. The fields generated inside the Script object is private, but - // further submissions can bind to them. - await AddSubmissionDependentProjectsAsync(solution, symbolOrigination.sourceProject, dependentProjects, cancellationToken).ConfigureAwait(false); + var dependentProjects = new HashSet<(Project, bool hasInternalsAccess)>(); - return [.. dependentProjects]; + // If a symbol was defined in source, then it is always visible to the project it + // was defined in. + if (symbolOrigination.sourceProject != null) + dependentProjects.Add((symbolOrigination.sourceProject, hasInternalsAccess: true)); + + // If it's not private, then we need to find possible references. + if (visibility != SymbolVisibility.Private) + AddNonSubmissionDependentProjects(solution, symbolOrigination, dependentProjects, cancellationToken); + + // submission projects are special here. The fields generated inside the Script object is private, but + // further submissions can bind to them. + await AddSubmissionDependentProjectsAsync(solution, symbolOrigination.sourceProject, dependentProjects, cancellationToken).ConfigureAwait(false); + + return [.. dependentProjects]; + } } private static async Task AddSubmissionDependentProjectsAsync( From 4344326a88c403cf1d4a53b496a895eb31809780 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 12:34:29 -0700 Subject: [PATCH 02/14] Pooling --- .../FindSymbols/FindReferences/DependentProjectsFinder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index 9fef504b01055..129901161ce38 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -168,7 +168,7 @@ private static async Task> GetDependentProjectsWorkerAsy { cancellationToken.ThrowIfCancellationRequested(); - var dependentProjects = new HashSet<(Project, bool hasInternalsAccess)>(); + using var _ = PooledHashSet<(Project, bool hasInternalsAccess)>.GetInstance(out var dependentProjects); // If a symbol was defined in source, then it is always visible to the project it // was defined in. From 149d93634cbe58a8aa6f6ab25ddabca500a84c19 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 12:43:43 -0700 Subject: [PATCH 03/14] Cache more --- .../FindReferences/DependentProjectsFinder.cs | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index 129901161ce38..02430ba4ba2b8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; @@ -28,14 +29,15 @@ internal static partial class DependentProjectsFinder /// Cache from the for a particular to the /// name of the defined by it. /// - private static ImmutableDictionary s_metadataIdToAssemblyName = ImmutableDictionary.Empty; + private static readonly Dictionary s_metadataIdToAssemblyName = new(); + private static readonly SemaphoreSlim s_metadataIdToAssemblyNameGate = new(initialCount: 1); private static readonly ConditionalWeakTable< Solution, Dictionary< (IAssemblySymbol assembly, Project? sourceProject, SymbolVisibility visibility), ImmutableArray<(Project project, bool hasInternalsAccess)>>> s_solutionToDependentProjectMap = new(); - private static readonly SemaphoreSlim s_gate = new(initialCount: 1); + private static readonly SemaphoreSlim s_solutionToDependentProjectMapGate = new(initialCount: 1); public static async Task> GetDependentProjectsAsync( Solution solution, ImmutableArray symbols, IImmutableSet projects, CancellationToken cancellationToken) @@ -141,7 +143,7 @@ private static async Task> GetDependentProjectsWorkerAsy ImmutableArray<(Project project, bool hasInternalsAccess)> dependentProjects; // Check cache first. - using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + using (await s_solutionToDependentProjectMapGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { if (dictionary.TryGetValue(key, out dependentProjects)) return dependentProjects; @@ -152,7 +154,7 @@ private static async Task> GetDependentProjectsWorkerAsy solution, symbolOrigination, visibility, cancellationToken).ConfigureAwait(false); // Try to add to cache, returning existing value if another thread already added it. - using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + using (await s_solutionToDependentProjectMapGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { if (dictionary.TryGetValue((symbolOrigination.assembly, symbolOrigination.sourceProject, visibility), out dependentProjects)) return dependentProjects; @@ -260,7 +262,7 @@ private static bool IsInternalsVisibleToAttribute(AttributeData attr) attrType.ContainingNamespace.ContainingNamespace.ContainingNamespace.ContainingNamespace?.IsGlobalNamespace == true; } - private static void AddNonSubmissionDependentProjects( + private static async Task AddNonSubmissionDependentProjectsAsync( Solution solution, (IAssemblySymbol assembly, Project? sourceProject) symbolOrigination, HashSet<(Project project, bool hasInternalsAccess)> dependentProjects, @@ -309,7 +311,7 @@ private static HashSet GetInternalsVisibleToSet(IAssemblySymbol assembly return set; } - private static bool HasReferenceTo( + private static async Task HasReferenceToAsync( (IAssemblySymbol assembly, Project? sourceProject) symbolOrigination, Project project, CancellationToken cancellationToken) @@ -323,10 +325,11 @@ private static bool HasReferenceTo( return project.ProjectReferences.Any(p => p.ProjectId == symbolOrigination.sourceProject.Id); // Otherwise, if the symbol is from metadata, see if the project's compilation references that metadata assembly. - return HasReferenceToAssembly(project, symbolOrigination.assembly.Name, cancellationToken); + return await HasReferenceToAssemblyAsync( + project, symbolOrigination.assembly.Name, cancellationToken).ConfigureAwait(false); } - private static bool HasReferenceToAssembly(Project project, string assemblyName, CancellationToken cancellationToken) + private static async Task HasReferenceToAssemblyAsync(Project project, string assemblyName, CancellationToken cancellationToken) { Contract.ThrowIfFalse(project.SupportsCompilation); @@ -346,14 +349,17 @@ private static bool HasReferenceToAssembly(Project project, string assemblyName, if (metadataId is null) continue; - if (!s_metadataIdToAssemblyName.TryGetValue(metadataId, out var name)) + using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - uncomputedReferences.Add((peReference, metadataId)); - continue; - } + if (!s_metadataIdToAssemblyName.TryGetValue(metadataId, out var name)) + { + uncomputedReferences.Add((peReference, metadataId)); + continue; + } - if (name == assemblyName) - return true; + if (name == assemblyName) + return true; + } } if (uncomputedReferences.Count == 0) @@ -365,16 +371,25 @@ private static bool HasReferenceToAssembly(Project project, string assemblyName, { cancellationToken.ThrowIfCancellationRequested(); - if (!s_metadataIdToAssemblyName.TryGetValue(metadataId, out var name)) + using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - // Defer creating the compilation till needed. - CreateCompilation(project, ref compilation); - if (compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol { Name: string metadataAssemblyName }) - name = ImmutableInterlocked.GetOrAdd(ref s_metadataIdToAssemblyName, metadataId, metadataAssemblyName); + if (s_metadataIdToAssemblyName.TryGetValue(metadataId, out var name) && name == assemblyName) + return true; } - if (name == assemblyName) - return true; + // Defer creating the compilation till needed. + CreateCompilation(project, ref compilation); + + if (compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol { Name: string metadataAssemblyName }) + { + using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + s_metadataIdToAssemblyName.TryAdd(metadataId, metadataAssemblyName); + if (metadataAssemblyName == assemblyName) + return true; + + } + } } return false; From 44ff6deb65a7d94d8d0f2cd4ecfe9fe25e330fd8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 12:45:29 -0700 Subject: [PATCH 04/14] Formatting --- .../FindSymbols/FindReferences/DependentProjectsFinder.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index 02430ba4ba2b8..cedefd8d3e192 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -179,7 +179,10 @@ private static async Task> GetDependentProjectsWorkerAsy // If it's not private, then we need to find possible references. if (visibility != SymbolVisibility.Private) - AddNonSubmissionDependentProjects(solution, symbolOrigination, dependentProjects, cancellationToken); + { + await AddNonSubmissionDependentProjectsAsync( + solution, symbolOrigination, dependentProjects, cancellationToken).ConfigureAwait(false); + } // submission projects are special here. The fields generated inside the Script object is private, but // further submissions can bind to them. @@ -276,7 +279,7 @@ private static async Task AddNonSubmissionDependentProjectsAsync( foreach (var project in solution.Projects) { if (!project.SupportsCompilation || - !HasReferenceTo(symbolOrigination, project, cancellationToken)) + !await HasReferenceToAsync(symbolOrigination, project, cancellationToken).ConfigureAwait(false)) { continue; } From 2209de9d3954335c16e64a1e059e8729528c5981 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 13:07:49 -0700 Subject: [PATCH 05/14] Update src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs --- .../FindSymbols/FindReferences/DependentProjectsFinder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index cedefd8d3e192..a97bc2dab9263 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -390,7 +390,6 @@ private static async Task HasReferenceToAssemblyAsync(Project project, str s_metadataIdToAssemblyName.TryAdd(metadataId, metadataAssemblyName); if (metadataAssemblyName == assemblyName) return true; - } } } From e69f831a147548d9c02dcb2f3a7d8de1975c6650 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 13:27:05 -0700 Subject: [PATCH 06/14] parity --- .../FindReferences/DependentProjectsFinder.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index cedefd8d3e192..dd03c06ec1fde 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -354,15 +354,11 @@ private static async Task HasReferenceToAssemblyAsync(Project project, str using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - if (!s_metadataIdToAssemblyName.TryGetValue(metadataId, out var name)) - { - uncomputedReferences.Add((peReference, metadataId)); - continue; - } - - if (name == assemblyName) + if (s_metadataIdToAssemblyName.TryGetValue(metadataId, out var name) && name == assemblyName) return true; } + + uncomputedReferences.Add((peReference, metadataId)); } if (uncomputedReferences.Count == 0) @@ -390,7 +386,6 @@ private static async Task HasReferenceToAssemblyAsync(Project project, str s_metadataIdToAssemblyName.TryAdd(metadataId, metadataAssemblyName); if (metadataAssemblyName == assemblyName) return true; - } } } From e20069f3fb307c44b593448710b1e63d2226e464 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 13:28:00 -0700 Subject: [PATCH 07/14] Update src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs --- .../FindSymbols/FindReferences/DependentProjectsFinder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index dd03c06ec1fde..b6fd0cdaf042c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -156,7 +156,7 @@ private static async Task> GetDependentProjectsWorkerAsy // Try to add to cache, returning existing value if another thread already added it. using (await s_solutionToDependentProjectMapGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - if (dictionary.TryGetValue((symbolOrigination.assembly, symbolOrigination.sourceProject, visibility), out dependentProjects)) + if (dictionary.TryGetValue(key, out dependentProjects)) return dependentProjects; return dictionary.GetOrAdd(key, dependentProjects); From aeb242daf716300b15ffc12e99058028edf21a30 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 13:37:50 -0700 Subject: [PATCH 08/14] Fix loops --- .../FindReferences/DependentProjectsFinder.cs | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index dd03c06ec1fde..3ffd141a1e9a2 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -354,31 +354,30 @@ private static async Task HasReferenceToAssemblyAsync(Project project, str using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - if (s_metadataIdToAssemblyName.TryGetValue(metadataId, out var name) && name == assemblyName) - return true; + if (s_metadataIdToAssemblyName.TryGetValue(metadataId, out var name)) + { + // We already know the assembly name for this metadata id. If it matches the one we're looking for, + // we're done. Otherwise, keep looking. + if (name == assemblyName) + return true; + else + continue; + } } + // We didn't know the name for the metadata id. Add it to the list of things we need to compute below. uncomputedReferences.Add((peReference, metadataId)); } if (uncomputedReferences.Count == 0) return false; - Compilation? compilation = null; + var compilation = CreateCompilation(project); foreach (var (peReference, metadataId) in uncomputedReferences) { cancellationToken.ThrowIfCancellationRequested(); - using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - if (s_metadataIdToAssemblyName.TryGetValue(metadataId, out var name) && name == assemblyName) - return true; - } - - // Defer creating the compilation till needed. - CreateCompilation(project, ref compilation); - if (compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol { Name: string metadataAssemblyName }) { using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) @@ -392,18 +391,15 @@ private static async Task HasReferenceToAssemblyAsync(Project project, str return false; - static void CreateCompilation(Project project, [NotNull] ref Compilation? compilation) + static Compilation CreateCompilation(Project project) { - if (compilation != null) - return; - // Use the project's compilation if it has one. - if (project.TryGetCompilation(out compilation)) - return; + if (project.TryGetCompilation(out var compilation)) + return compilation; // Perf: check metadata reference using newly created empty compilation with only metadata references. var factory = project.Services.GetRequiredService(); - compilation = factory + return factory .CreateCompilation(project.AssemblyName, project.CompilationOptions!) .AddReferences(project.MetadataReferences); } From 4c52251b6838d35a284060767e083eb8b16a7711 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 13:39:49 -0700 Subject: [PATCH 09/14] Add no matter what --- .../FindReferences/DependentProjectsFinder.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index 3ffd141a1e9a2..961e4135f3c84 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -29,7 +29,7 @@ internal static partial class DependentProjectsFinder /// Cache from the for a particular to the /// name of the defined by it. /// - private static readonly Dictionary s_metadataIdToAssemblyName = new(); + private static readonly Dictionary s_metadataIdToAssemblyName = new(); private static readonly SemaphoreSlim s_metadataIdToAssemblyNameGate = new(initialCount: 1); private static readonly ConditionalWeakTable< @@ -387,6 +387,15 @@ private static async Task HasReferenceToAssemblyAsync(Project project, str return true; } } + else + { + using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) + { + // Explicitly store that we don't know the name of this pe-reference's assembly. That way we don't + // try to recompute it in the future every time we see it. + s_metadataIdToAssemblyName.TryAdd(metadataId, null); + } + } } return false; From a446a8751162a3faa80b473931cd2d18f6685066 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 13:40:39 -0700 Subject: [PATCH 10/14] Add no matter what --- .../FindReferences/DependentProjectsFinder.cs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index 8c48e25ee82d0..176901eecb174 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -378,24 +378,17 @@ private static async Task HasReferenceToAssemblyAsync(Project project, str { cancellationToken.ThrowIfCancellationRequested(); - if (compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol { Name: string metadataAssemblyName }) - { - using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - s_metadataIdToAssemblyName.TryAdd(metadataId, metadataAssemblyName); - if (metadataAssemblyName == assemblyName) - return true; - } - } - else + var name = compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol { Name: string metadataAssemblyName } + ? metadataAssemblyName + : null; + + using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - // Explicitly store that we don't know the name of this pe-reference's assembly. That way we don't - // try to recompute it in the future every time we see it. - s_metadataIdToAssemblyName.TryAdd(metadataId, null); - } + name = s_metadataIdToAssemblyName.GetOrAdd(metadataId, name); } + + if (name == assemblyName) + return true; } return false; From babfc3a37c1f00b2268630b9be123afd231fe347 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 13:41:19 -0700 Subject: [PATCH 11/14] Docs --- .../FindSymbols/FindReferences/DependentProjectsFinder.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index 176901eecb174..411b60f6c1a16 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -378,6 +378,8 @@ private static async Task HasReferenceToAssemblyAsync(Project project, str { cancellationToken.ThrowIfCancellationRequested(); + // Attempt to get the assembly name for this pe-reference. If we fail, we still want to add that info into + // the dictionary (by mapping us to 'null'). That way we don't keep trying to compute it over and over. var name = compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol { Name: string metadataAssemblyName } ? metadataAssemblyName : null; From 026ebed202b28a103353b1386046f19dadf2ef7f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 13:54:00 -0700 Subject: [PATCH 12/14] Simplify --- .../FindReferences/DependentProjectsFinder.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index 411b60f6c1a16..8feec7c6c56fc 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -198,7 +198,7 @@ private static async Task AddSubmissionDependentProjectsAsync( if (sourceProject?.IsSubmission != true) return; - var projectIdsToReferencingSubmissionIds = new Dictionary>(); + using var _1 = PooledDictionary>.GetInstance(out var projectIdsToReferencingSubmissionIds); // search only submission project foreach (var projectId in solution.ProjectIds) @@ -217,15 +217,7 @@ private static async Task AddSubmissionDependentProjectsAsync( { var referencedProject = solution.GetProject(previous.Assembly, cancellationToken); if (referencedProject != null) - { - if (!projectIdsToReferencingSubmissionIds.TryGetValue(referencedProject.Id, out var referencingSubmissions)) - { - referencingSubmissions = []; - projectIdsToReferencingSubmissionIds.Add(referencedProject.Id, referencingSubmissions); - } - - referencingSubmissions.Add(project.Id); - } + projectIdsToReferencingSubmissionIds.MultiAdd(referencedProject.Id, project.Id); } } } @@ -235,7 +227,7 @@ private static async Task AddSubmissionDependentProjectsAsync( // and 2, even though 2 doesn't have a direct reference to 1. Hence we need to take // our current set of projects and find the transitive closure over backwards // submission previous references. - using var _ = ArrayBuilder.GetInstance(out var projectIdsToProcess); + using var _2 = ArrayBuilder.GetInstance(out var projectIdsToProcess); foreach (var dependentProject in dependentProjects.Select(dp => dp.project.Id)) projectIdsToProcess.Push(dependentProject); From 3b59475de44e3ea31f66578adfc2ebc8cf1ad435 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 15:55:49 -0700 Subject: [PATCH 13/14] Simplify git push --- .../FindSymbols/FindReferences/DependentProjectsFinder.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index 8feec7c6c56fc..03b7590699e6c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -156,9 +156,6 @@ private static async Task> GetDependentProjectsWorkerAsy // Try to add to cache, returning existing value if another thread already added it. using (await s_solutionToDependentProjectMapGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - if (dictionary.TryGetValue(key, out dependentProjects)) - return dependentProjects; - return dictionary.GetOrAdd(key, dependentProjects); } From 73070333674875dad510ff58e13b16cd0efe9f4e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 May 2024 16:08:00 -0700 Subject: [PATCH 14/14] Fix null initial name --- .../FindReferences/DependentProjectsFinder.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index 03b7590699e6c..dc87229e40fca 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -375,6 +375,15 @@ private static async Task HasReferenceToAssemblyAsync(Project project, str using (await s_metadataIdToAssemblyNameGate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { + // Overwrite an existing null name with a non-null one. + if (s_metadataIdToAssemblyName.TryGetValue(metadataId, out var existingName) && + existingName == null && + name != null) + { + s_metadataIdToAssemblyName[metadataId] = name; + } + + // Return whatever is in the map, adding ourselves if something is not already there. name = s_metadataIdToAssemblyName.GetOrAdd(metadataId, name); }