From ec485d19381b244ee20384e69bc62f91be2072b8 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Fri, 20 Jan 2023 16:11:08 -0800 Subject: [PATCH] Use dictionary lookup while resolving assembly references (#66430) Closes #66410. --- .../Portable/Symbols/ReferenceManager.cs | 4 +- .../AssemblyIdentityExtensions.cs | 4 +- .../Portable/ReferenceManager/AssemblyData.cs | 16 +++- .../AssemblyDataForAssemblyBeingBuilt.cs | 11 +-- .../CommonReferenceManager.Binding.cs | 28 +++++-- .../CommonReferenceManager.Resolution.cs | 82 ++++++++++++------- .../Portable/Symbols/ReferenceManager.vb | 4 +- 7 files changed, 100 insertions(+), 49 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/ReferenceManager.cs b/src/Compilers/CSharp/Portable/Symbols/ReferenceManager.cs index 1c13751b9af71..5dac15e864be7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ReferenceManager.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ReferenceManager.cs @@ -919,9 +919,9 @@ public override ImmutableArray AssemblyReferences } public override AssemblyReferenceBinding[] BindAssemblyReferences( - ImmutableArray assemblies, AssemblyIdentityComparer assemblyIdentityComparer) + MultiDictionary assemblies, AssemblyIdentityComparer assemblyIdentityComparer) { - return ResolveReferencedAssemblies(_referencedAssemblies, assemblies, definitionStartIndex: 0, assemblyIdentityComparer: assemblyIdentityComparer); + return ResolveReferencedAssemblies(_referencedAssemblies, assemblies, resolveAgainstAssemblyBeingBuilt: true, assemblyIdentityComparer: assemblyIdentityComparer); } public sealed override bool IsLinked diff --git a/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentityExtensions.cs b/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentityExtensions.cs index 7fff48e6bccac..e4d5d51bcdbe7 100644 --- a/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentityExtensions.cs +++ b/src/Compilers/Core/Portable/MetadataReference/AssemblyIdentityExtensions.cs @@ -18,11 +18,13 @@ internal static bool IsWindowsComponent(this AssemblyIdentity identity) identity.Name.StartsWith("windows.", StringComparison.OrdinalIgnoreCase); } + internal const string WindowsRuntimeIdentitySimpleName = "windows"; + // Windows[.winmd] internal static bool IsWindowsRuntime(this AssemblyIdentity identity) { return (identity.ContentType == AssemblyContentType.WindowsRuntime) && - string.Equals(identity.Name, "windows", StringComparison.OrdinalIgnoreCase); + string.Equals(identity.Name, WindowsRuntimeIdentitySimpleName, StringComparison.OrdinalIgnoreCase); } } } diff --git a/src/Compilers/Core/Portable/ReferenceManager/AssemblyData.cs b/src/Compilers/Core/Portable/ReferenceManager/AssemblyData.cs index 955224fe03a08..5ff36a98cb0ab 100644 --- a/src/Compilers/Core/Portable/ReferenceManager/AssemblyData.cs +++ b/src/Compilers/Core/Portable/ReferenceManager/AssemblyData.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { @@ -48,13 +49,13 @@ internal abstract class AssemblyData /// In other words, match assembly identities returned by AssemblyReferences property against /// assemblies described by provided AssemblyData objects. /// - /// An array of AssemblyData objects to match against. + /// AssemblyData objects to match against. /// Used to compare assembly identities. /// /// For each assembly referenced by this assembly () /// a description of how it binds to one of the input assemblies. /// - public abstract AssemblyReferenceBinding[] BindAssemblyReferences(ImmutableArray assemblies, AssemblyIdentityComparer assemblyIdentityComparer); + public abstract AssemblyReferenceBinding[] BindAssemblyReferences(MultiDictionary assemblies, AssemblyIdentityComparer assemblyIdentityComparer); public abstract bool ContainsNoPiaLocalTypes { get; } @@ -69,6 +70,17 @@ internal abstract class AssemblyData public abstract Compilation? SourceCompilation { get; } private string GetDebuggerDisplay() => $"{GetType().Name}: [{Identity.GetDisplayName()}]"; +#if DEBUG + public sealed override bool Equals(object? obj) + { + return base.Equals(obj); + } + + public sealed override int GetHashCode() + { + return base.GetHashCode(); + } +#endif } } } diff --git a/src/Compilers/Core/Portable/ReferenceManager/AssemblyDataForAssemblyBeingBuilt.cs b/src/Compilers/Core/Portable/ReferenceManager/AssemblyDataForAssemblyBeingBuilt.cs index e7d4513b0b51a..51a34d75c3f8e 100644 --- a/src/Compilers/Core/Portable/ReferenceManager/AssemblyDataForAssemblyBeingBuilt.cs +++ b/src/Compilers/Core/Portable/ReferenceManager/AssemblyDataForAssemblyBeingBuilt.cs @@ -75,27 +75,24 @@ public override IEnumerable AvailableSymbols } public override AssemblyReferenceBinding[] BindAssemblyReferences( - ImmutableArray assemblies, + MultiDictionary assemblies, AssemblyIdentityComparer assemblyIdentityComparer) { var boundReferences = new AssemblyReferenceBinding[_referencedAssemblies.Length]; for (int i = 0; i < _referencedAssemblyData.Length; i++) { - Debug.Assert(ReferenceEquals(_referencedAssemblyData[i], assemblies[i + 1])); - boundReferences[i] = new AssemblyReferenceBinding(assemblies[i + 1].Identity, i + 1); + Debug.Assert(assemblies[_referencedAssemblyData[i].Identity.Name].Contains((_referencedAssemblyData[i], i + 1))); + boundReferences[i] = new AssemblyReferenceBinding(_referencedAssemblyData[i].Identity, i + 1); } - // references from added modules shouldn't resolve against the assembly being built (definition #0) - const int definitionStartIndex = 1; - // resolve references coming from linked modules: for (int i = _referencedAssemblyData.Length; i < _referencedAssemblies.Length; i++) { boundReferences[i] = ResolveReferencedAssembly( _referencedAssemblies[i], assemblies, - definitionStartIndex, + resolveAgainstAssemblyBeingBuilt: false, // references from added modules shouldn't resolve against the assembly being built (definition #0) assemblyIdentityComparer); } diff --git a/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Binding.cs b/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Binding.cs index d681a421157aa..32c53332f4a3e 100644 --- a/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Binding.cs +++ b/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Binding.cs @@ -85,7 +85,7 @@ internal partial class CommonReferenceManager /// /// - Result of resolving assembly references of the corresponding assembly /// against provided set of assembly definitions. Essentially, this is an array returned by - /// method. + /// method. /// protected BoundInputAssembly[] Bind( ImmutableArray explicitAssemblies, @@ -110,17 +110,25 @@ protected BoundInputAssembly[] Bind( var referenceBindings = ArrayBuilder.GetInstance(); try { + var explicitAssembliesMap = new MultiDictionary(explicitAssemblies.Length, AssemblyIdentityComparer.SimpleNameComparer); + + for (int i = 0; i < explicitAssemblies.Length; i++) + { + explicitAssembliesMap.Add(explicitAssemblies[i].Identity.Name, (explicitAssemblies[i], i)); + } + // Based on assembly identity, for each assembly, // bind its references against the other assemblies we have. for (int i = 0; i < explicitAssemblies.Length; i++) { - referenceBindings.Add(explicitAssemblies[i].BindAssemblyReferences(explicitAssemblies, IdentityComparer)); + referenceBindings.Add(explicitAssemblies[i].BindAssemblyReferences(explicitAssembliesMap, IdentityComparer)); } if (resolverOpt?.ResolveMissingAssemblies == true) { ResolveAndBindMissingAssemblies( explicitAssemblies, + explicitAssembliesMap, explicitModules, explicitReferences, explicitReferenceMap, @@ -191,6 +199,7 @@ protected BoundInputAssembly[] Bind( private void ResolveAndBindMissingAssemblies( ImmutableArray explicitAssemblies, + MultiDictionary explicitAssembliesMap, ImmutableArray explicitModules, ImmutableArray explicitReferences, ImmutableArray explicitReferenceMap, @@ -285,7 +294,7 @@ private void ResolveAndBindMissingAssemblies( var data = CreateAssemblyDataForResolvedMissingAssembly(resolvedAssemblyMetadata, resolvedReference, importOptions); implicitAssemblies.Add(data); - var referenceBinding = data.BindAssemblyReferences(explicitAssemblies, IdentityComparer); + var referenceBinding = data.BindAssemblyReferences(explicitAssembliesMap, IdentityComparer); referenceBindings.Add(referenceBinding); referenceBindingsToProcess.Push((resolvedReference, new ArraySegment(referenceBinding))); } @@ -310,6 +319,15 @@ private void ResolveAndBindMissingAssemblies( // Rebind assembly references that were initially missing. All bindings established above // are against explicitly specified references. + // We only need to resolve against implicitly resolved assemblies, + // since we already resolved against explicitly specified ones. + var implicitAssembliesMap = new MultiDictionary(implicitAssemblies.Count, AssemblyIdentityComparer.SimpleNameComparer); + + for (int i = 0; i < implicitAssemblies.Count; i++) + { + implicitAssembliesMap.Add(implicitAssemblies[i].Identity.Name, (implicitAssemblies[i], explicitAssemblyCount + i)); + } + allAssemblies = explicitAssemblies.AddRange(implicitAssemblies); for (int bindingsIndex = 0; bindingsIndex < referenceBindings.Count; bindingsIndex++) @@ -332,8 +350,8 @@ private void ResolveAndBindMissingAssemblies( Debug.Assert(binding.ReferenceIdentity is object); referenceBinding[i] = ResolveReferencedAssembly( binding.ReferenceIdentity, - allAssemblies, - explicitAssemblyCount, + implicitAssembliesMap, + resolveAgainstAssemblyBeingBuilt: false, IdentityComparer); } } diff --git a/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Resolution.cs b/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Resolution.cs index 76e3aaa81f091..c0f4b3802ff6a 100644 --- a/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Resolution.cs +++ b/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Resolution.cs @@ -890,14 +890,14 @@ protected void GetCompilationReferences( internal static AssemblyReferenceBinding[] ResolveReferencedAssemblies( ImmutableArray references, - ImmutableArray definitions, - int definitionStartIndex, + MultiDictionary definitions, + bool resolveAgainstAssemblyBeingBuilt, AssemblyIdentityComparer assemblyIdentityComparer) { var boundReferences = new AssemblyReferenceBinding[references.Length]; for (int j = 0; j < references.Length; j++) { - boundReferences[j] = ResolveReferencedAssembly(references[j], definitions, definitionStartIndex, assemblyIdentityComparer); + boundReferences[j] = ResolveReferencedAssembly(references[j], definitions, resolveAgainstAssemblyBeingBuilt, assemblyIdentityComparer); } return boundReferences; @@ -906,8 +906,8 @@ internal static AssemblyReferenceBinding[] ResolveReferencedAssemblies( /// /// Used to match AssemblyRef with AssemblyDef. /// - /// Array of definition identities to match against. - /// An index of the first definition to consider, preceding this index are ignored. + /// Definitions to match against. + /// Whether to attempt to resolve the reference against the assembly being built (index 0). /// Reference identity to resolve. /// Assembly identity comparer. /// @@ -915,8 +915,8 @@ internal static AssemblyReferenceBinding[] ResolveReferencedAssemblies( /// internal static AssemblyReferenceBinding ResolveReferencedAssembly( AssemblyIdentity reference, - ImmutableArray definitions, - int definitionStartIndex, + MultiDictionary definitions, + bool resolveAgainstAssemblyBeingBuilt, AssemblyIdentityComparer assemblyIdentityComparer) { // Dev11 C# compiler allows the versions to not match exactly, assuming that a newer library may be used instead of an older version. @@ -925,15 +925,19 @@ internal static AssemblyReferenceBinding ResolveReferencedAssembly( // definition with the lowest version higher than reference version, unless exact version found int minHigherVersionDefinition = -1; + Version? minHigherVersionDefinitionVersion = null; int maxLowerVersionDefinition = -1; + Version? maxLowerVersionDefinitionVersion = null; - // Skip assembly being built for now; it will be considered at the very end: - bool resolveAgainstAssemblyBeingBuilt = definitionStartIndex == 0; - definitionStartIndex = Math.Max(definitionStartIndex, 1); - - for (int i = definitionStartIndex; i < definitions.Length; i++) + foreach ((AssemblyData definitionData, int definitionIndex) in definitions[reference.Name]) { - AssemblyIdentity definition = definitions[i].Identity; + // Skip assembly being built for now; it will be considered at the very end + if (definitionIndex == 0) + { + continue; + } + + AssemblyIdentity definition = definitionData.Identity; switch (assemblyIdentityComparer.Compare(reference, definition)) { @@ -941,15 +945,16 @@ internal static AssemblyReferenceBinding ResolveReferencedAssembly( continue; case AssemblyIdentityComparer.ComparisonResult.Equivalent: - return new AssemblyReferenceBinding(reference, i); + return new AssemblyReferenceBinding(reference, definitionIndex); case AssemblyIdentityComparer.ComparisonResult.EquivalentIgnoringVersion: if (reference.Version < definition.Version) { // Refers to an older assembly than we have - if (minHigherVersionDefinition == -1 || definition.Version < definitions[minHigherVersionDefinition].Identity.Version) + if (minHigherVersionDefinition == -1 || definition.Version < minHigherVersionDefinitionVersion) { - minHigherVersionDefinition = i; + minHigherVersionDefinition = definitionIndex; + minHigherVersionDefinitionVersion = definition.Version; } } else @@ -957,9 +962,10 @@ internal static AssemblyReferenceBinding ResolveReferencedAssembly( Debug.Assert(reference.Version > definition.Version); // Refers to a newer assembly than we have - if (maxLowerVersionDefinition == -1 || definition.Version > definitions[maxLowerVersionDefinition].Identity.Version) + if (maxLowerVersionDefinition == -1 || definition.Version > maxLowerVersionDefinitionVersion) { - maxLowerVersionDefinition = i; + maxLowerVersionDefinition = definitionIndex; + maxLowerVersionDefinitionVersion = definition.Version; } } @@ -989,11 +995,17 @@ internal static AssemblyReferenceBinding ResolveReferencedAssembly( // substitute for a collection of Windows.*.winmd compile-time references. if (reference.IsWindowsComponent()) { - for (int i = definitionStartIndex; i < definitions.Length; i++) + foreach ((AssemblyData definitionData, int definitionIndex) in definitions[AssemblyIdentityExtensions.WindowsRuntimeIdentitySimpleName]) { - if (definitions[i].Identity.IsWindowsRuntime()) + // Skip assembly being built for now; it will be considered at the very end + if (definitionIndex == 0) + { + continue; + } + + if (definitionData.Identity.IsWindowsRuntime()) { - return new AssemblyReferenceBinding(reference, i); + return new AssemblyReferenceBinding(reference, definitionIndex); } } } @@ -1007,19 +1019,24 @@ internal static AssemblyReferenceBinding ResolveReferencedAssembly( // allow the compilation to match the reference. if (reference.ContentType == AssemblyContentType.WindowsRuntime) { - for (int i = definitionStartIndex; i < definitions.Length; i++) + foreach ((AssemblyData definitionData, int definitionIndex) in definitions[reference.Name]) { - var definition = definitions[i].Identity; - var sourceCompilation = definitions[i].SourceCompilation; + // Skip assembly being built for now; it will be considered at the very end + if (definitionIndex == 0) + { + continue; + } + + var definition = definitionData.Identity; + var sourceCompilation = definitionData.SourceCompilation; if (definition.ContentType == AssemblyContentType.Default && sourceCompilation?.Options.OutputKind == OutputKind.WindowsRuntimeMetadata && - AssemblyIdentityComparer.SimpleNameComparer.Equals(reference.Name, definition.Name) && reference.Version.Equals(definition.Version) && reference.IsRetargetable == definition.IsRetargetable && AssemblyIdentityComparer.CultureComparer.Equals(reference.CultureName, definition.CultureName) && AssemblyIdentity.KeysEqual(reference, definition)) { - return new AssemblyReferenceBinding(reference, i); + return new AssemblyReferenceBinding(reference, definitionIndex); } } } @@ -1027,11 +1044,16 @@ internal static AssemblyReferenceBinding ResolveReferencedAssembly( // As in the native compiler (see IMPORTER::MapAssemblyRefToAid), we compare against the // compilation (i.e. source) assembly as a last resort. We follow the native approach of // skipping the public key comparison since we have yet to compute it. - if (resolveAgainstAssemblyBeingBuilt && - AssemblyIdentityComparer.SimpleNameComparer.Equals(reference.Name, definitions[0].Identity.Name)) + if (resolveAgainstAssemblyBeingBuilt) { - Debug.Assert(definitions[0].Identity.PublicKeyToken.IsEmpty); - return new AssemblyReferenceBinding(reference, 0); + foreach ((AssemblyData definitionData, int definitionIndex) in definitions[reference.Name]) + { + if (definitionIndex == 0) + { + Debug.Assert(definitionData.Identity.PublicKeyToken.IsEmpty); + return new AssemblyReferenceBinding(reference, 0); + } + } } return new AssemblyReferenceBinding(reference); diff --git a/src/Compilers/VisualBasic/Portable/Symbols/ReferenceManager.vb b/src/Compilers/VisualBasic/Portable/Symbols/ReferenceManager.vb index c1c77f54634a7..078157f50f81d 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/ReferenceManager.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/ReferenceManager.vb @@ -782,8 +782,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property - Public Overrides Function BindAssemblyReferences(assemblies As ImmutableArray(Of AssemblyData), assemblyIdentityComparer As AssemblyIdentityComparer) As AssemblyReferenceBinding() - Return ResolveReferencedAssemblies(_referencedAssemblies, assemblies, definitionStartIndex:=0, assemblyIdentityComparer:=assemblyIdentityComparer) + Public Overrides Function BindAssemblyReferences(assemblies As MultiDictionary(Of String, (DefinitionData As AssemblyData, DefinitionIndex As Integer)), assemblyIdentityComparer As AssemblyIdentityComparer) As AssemblyReferenceBinding() + Return ResolveReferencedAssemblies(_referencedAssemblies, assemblies, resolveAgainstAssemblyBeingBuilt:=True, assemblyIdentityComparer:=assemblyIdentityComparer) End Function Public NotOverridable Overrides ReadOnly Property IsLinked As Boolean