From c87268add3728c1bc0e4a1b1fb4ac936934e5ea8 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Wed, 13 Mar 2024 17:44:38 -0700 Subject: [PATCH] Remove ImmutableHashMap in favor of ImmutableDictionary (#72520) My understanding is that ImmutableHashMap precedes ImmutableDictionary and only exists because of that reason. These two data structures appear to have similar performance and functionality. There is no reason to have our own implementation if there isn't an advantage to doing so. --- .../Portable/Workspace/Solution/Project.cs | 21 +- .../Portable/Workspace/Solution/Solution.cs | 7 +- .../Core/CompilerExtensions.projitems | 2 - .../CompilerUtilities/ImmutableHashMap.cs | 1120 ----------------- .../ImmutableHashMapExtensions.cs | 58 - 5 files changed, 13 insertions(+), 1195 deletions(-) delete mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMap.cs delete mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMapExtensions.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index 35c2a92e943b8..1ead6e28e9b29 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; -using Roslyn.Collections.Immutable; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -28,10 +27,10 @@ public partial class Project { private readonly Solution _solution; private readonly ProjectState _projectState; - private ImmutableHashMap _idToDocumentMap = []; - private ImmutableHashMap _idToSourceGeneratedDocumentMap = []; - private ImmutableHashMap _idToAdditionalDocumentMap = []; - private ImmutableHashMap _idToAnalyzerConfigDocumentMap = []; + private ImmutableDictionary _idToDocumentMap = ImmutableDictionary.Empty; + private ImmutableDictionary _idToSourceGeneratedDocumentMap = ImmutableDictionary.Empty; + private ImmutableDictionary _idToAdditionalDocumentMap = ImmutableDictionary.Empty; + private ImmutableDictionary _idToAnalyzerConfigDocumentMap = ImmutableDictionary.Empty; internal Project(Solution solution, ProjectState projectState) { @@ -236,19 +235,19 @@ public bool ContainsAnalyzerConfigDocument(DocumentId documentId) /// Get the document in this project with the specified document Id. /// public Document? GetDocument(DocumentId documentId) - => ImmutableHashMapExtensions.GetOrAdd(ref _idToDocumentMap, documentId, s_tryCreateDocumentFunction, this); + => ImmutableInterlocked.GetOrAdd(ref _idToDocumentMap, documentId, s_tryCreateDocumentFunction, this); /// /// Get the additional document in this project with the specified document Id. /// public TextDocument? GetAdditionalDocument(DocumentId documentId) - => ImmutableHashMapExtensions.GetOrAdd(ref _idToAdditionalDocumentMap, documentId, s_tryCreateAdditionalDocumentFunction, this); + => ImmutableInterlocked.GetOrAdd(ref _idToAdditionalDocumentMap, documentId, s_tryCreateAdditionalDocumentFunction, this); /// /// Get the analyzer config document in this project with the specified document Id. /// public AnalyzerConfigDocument? GetAnalyzerConfigDocument(DocumentId documentId) - => ImmutableHashMapExtensions.GetOrAdd(ref _idToAnalyzerConfigDocumentMap, documentId, s_tryCreateAnalyzerConfigDocumentFunction, this); + => ImmutableInterlocked.GetOrAdd(ref _idToAnalyzerConfigDocumentMap, documentId, s_tryCreateAnalyzerConfigDocumentFunction, this); /// /// Gets a document or a source generated document in this solution with the specified document ID. @@ -285,7 +284,7 @@ public async ValueTask> GetSourceGeneratedD // return an iterator to avoid eagerly allocating all the document instances return generatedDocumentStates.States.Values.Select(state => - ImmutableHashMapExtensions.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this)))!; + ImmutableInterlocked.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this))); } internal async ValueTask> GetAllRegularAndSourceGeneratedDocumentsAsync(CancellationToken cancellationToken = default) @@ -318,7 +317,7 @@ internal async ValueTask> GetAllRegularAndSourceGeneratedD } internal SourceGeneratedDocument GetOrCreateSourceGeneratedDocument(SourceGeneratedDocumentState state) - => ImmutableHashMapExtensions.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this))!; + => ImmutableInterlocked.GetOrAdd(ref _idToSourceGeneratedDocumentMap, state.Id, s_createSourceGeneratedDocumentFunction, (state, this)); /// /// Returns the for a source generated document that has already been generated and observed. @@ -349,7 +348,7 @@ internal SourceGeneratedDocument GetOrCreateSourceGeneratedDocument(SourceGenera if (documentState == null) return null; - return ImmutableHashMapExtensions.GetOrAdd(ref _idToSourceGeneratedDocumentMap, documentId, s_createSourceGeneratedDocumentFunction, (documentState, this)); + return ImmutableInterlocked.GetOrAdd(ref _idToSourceGeneratedDocumentMap, documentId, s_createSourceGeneratedDocumentFunction, (documentState, this)); } internal ValueTask> GetSourceGeneratorDiagnosticsAsync(CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index e7d1aeca91fcf..cafe4e3d878c3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; -using Roslyn.Collections.Immutable; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -28,7 +27,7 @@ public partial class Solution private readonly SolutionCompilationState _compilationState; // Values for all these are created on demand. - private ImmutableHashMap _projectIdToProjectMap; + private ImmutableDictionary _projectIdToProjectMap; /// /// Result of calling . @@ -45,7 +44,7 @@ private Solution( SolutionCompilationState compilationState, AsyncLazy? cachedFrozenSolution = null) { - _projectIdToProjectMap = []; + _projectIdToProjectMap = ImmutableDictionary.Empty; _compilationState = compilationState; _cachedFrozenSolution = cachedFrozenSolution ?? @@ -141,7 +140,7 @@ public Workspace Workspace { if (this.ContainsProject(projectId)) { - return ImmutableHashMapExtensions.GetOrAdd(ref _projectIdToProjectMap, projectId, s_createProjectFunction, this); + return ImmutableInterlocked.GetOrAdd(ref _projectIdToProjectMap, projectId, s_createProjectFunction, this); } return null; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index 320b61bf7b2f4..bbe77051ecf65 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -540,8 +540,6 @@ - - diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMap.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMap.cs deleted file mode 100644 index fdc05e011a342..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMap.cs +++ /dev/null @@ -1,1120 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Roslyn.Utilities; -using Contract = System.Diagnostics.Contracts.Contract; - -namespace Roslyn.Collections.Immutable; - -/// -/// An immutable unordered hash map implementation. -/// -/// The type of the key. -/// The type of the value. -[DebuggerDisplay("Count = {Count}")] -[DebuggerTypeProxy(typeof(ImmutableHashMap<,>.DebuggerProxy))] -[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] -internal sealed class ImmutableHashMap : IImmutableDictionary - where TKey : notnull -{ - private static readonly ImmutableHashMap s_emptySingleton = []; - - /// - /// The root node of the tree that stores this map. - /// - private readonly Bucket? _root; - - /// - /// The comparer used to sort keys in this map. - /// - private readonly IEqualityComparer _keyComparer; - - /// - /// The comparer used to detect equivalent values in this map. - /// - private readonly IEqualityComparer _valueComparer; - - /// - /// Initializes a new instance of the class. - /// - /// The root. - /// The comparer. - /// The value comparer. - private ImmutableHashMap(Bucket? root, IEqualityComparer comparer, IEqualityComparer valueComparer) - : this(comparer, valueComparer) - { - RoslynDebug.AssertNotNull(comparer); - RoslynDebug.AssertNotNull(valueComparer); - - _root = root; - } - - /// - /// Initializes a new instance of the class. - /// - /// The comparer. - /// The value comparer. - internal ImmutableHashMap(IEqualityComparer? comparer = null, IEqualityComparer? valueComparer = null) - { - _keyComparer = comparer ?? EqualityComparer.Default; - _valueComparer = valueComparer ?? EqualityComparer.Default; - } - - /// - /// Gets an empty map with default equality comparers. - /// - public static ImmutableHashMap Empty => s_emptySingleton; - - /// - /// See the interface. - /// - public ImmutableHashMap Clear() - => this.IsEmpty ? this : Empty.WithComparers(_keyComparer, _valueComparer); - - #region Public methods - - /// - /// See the interface. - /// - [Pure] - public ImmutableHashMap Add(TKey key, TValue value) - { - Requires.NotNullAllowStructs(key, "key"); - Contract.Ensures(Contract.Result>() != null); - var vb = new ValueBucket(key, value, _keyComparer.GetHashCode(key)); - if (_root == null) - { - return this.Wrap(vb); - } - else - { - return this.Wrap(_root.Add(0, vb, _keyComparer, _valueComparer, false)); - } - } - - /// - /// See the interface. - /// - [Pure] - public ImmutableHashMap AddRange(IEnumerable> pairs) - { - Requires.NotNull(pairs, "pairs"); - Contract.Ensures(Contract.Result>() != null); - - return this.AddRange(pairs, overwriteOnCollision: false, avoidToHashMap: false); - } - - /// - /// See the interface. - /// - [Pure] - public ImmutableHashMap SetItem(TKey key, TValue value) - { - Requires.NotNullAllowStructs(key, "key"); - Contract.Ensures(Contract.Result>() != null); - Contract.Ensures(!Contract.Result>().IsEmpty); - var vb = new ValueBucket(key, value, _keyComparer.GetHashCode(key)); - if (_root == null) - { - return this.Wrap(vb); - } - else - { - return this.Wrap(_root.Add(0, vb, _keyComparer, _valueComparer, true)); - } - } - - /// - /// Applies a given set of key=value pairs to an immutable dictionary, replacing any conflicting keys in the resulting dictionary. - /// - /// The key=value pairs to set on the map. Any keys that conflict with existing keys will overwrite the previous values. - /// An immutable dictionary. - [Pure] - public ImmutableHashMap SetItems(IEnumerable> items) - { - Requires.NotNull(items, "items"); - Contract.Ensures(Contract.Result>() != null); - - return this.AddRange(items, overwriteOnCollision: true, avoidToHashMap: false); - } - - /// - /// See the interface. - /// - [Pure] - public ImmutableHashMap Remove(TKey key) - { - Requires.NotNullAllowStructs(key, "key"); - Contract.Ensures(Contract.Result>() != null); - if (_root != null) - { - return this.Wrap(_root.Remove(_keyComparer.GetHashCode(key), key, _keyComparer)); - } - - return this; - } - - /// - /// See the interface. - /// - [Pure] - public ImmutableHashMap RemoveRange(IEnumerable keys) - { - Requires.NotNull(keys, "keys"); - Contract.Ensures(Contract.Result>() != null); - var map = _root; - if (map != null) - { - foreach (var key in keys) - { - map = map.Remove(_keyComparer.GetHashCode(key), key, _keyComparer); - if (map == null) - { - break; - } - } - } - - return this.Wrap(map); - } - - /// - /// Returns a hash map that uses the specified key and value comparers and has the same contents as this map. - /// - /// The key comparer. A value of null results in using the default equality comparer for the type. - /// The value comparer. A value of null results in using the default equality comparer for the type. - /// The hash map with the new comparers. - /// - /// In the event that a change in the key equality comparer results in a key collision, an exception is thrown. - /// - [Pure] - public ImmutableHashMap WithComparers(IEqualityComparer keyComparer, IEqualityComparer valueComparer) - { - keyComparer ??= EqualityComparer.Default; - - valueComparer ??= EqualityComparer.Default; - - if (_keyComparer == keyComparer) - { - if (_valueComparer == valueComparer) - { - return this; - } - else - { - // When the key comparer is the same but the value comparer is different, we don't need a whole new tree - // because the structure of the tree does not depend on the value comparer. - // We just need a new root node to store the new value comparer. - return new ImmutableHashMap(_root, _keyComparer, valueComparer); - } - } - else - { - var set = new ImmutableHashMap(keyComparer, valueComparer); - set = set.AddRange(this, overwriteOnCollision: false, avoidToHashMap: true); - return set; - } - } - - /// - /// Returns a hash map that uses the specified key comparer and current value comparer and has the same contents as this map. - /// - /// The key comparer. A value of null results in using the default equality comparer for the type. - /// The hash map with the new comparers. - /// - /// In the event that a change in the key equality comparer results in a key collision, an exception is thrown. - /// - [Pure] - public ImmutableHashMap WithComparers(IEqualityComparer keyComparer) - => this.WithComparers(keyComparer, _valueComparer); - - /// - /// Determines whether the ImmutableSortedMap<TKey,TValue> - /// contains an element with the specified value. - /// - /// - /// The value to locate in the ImmutableSortedMap<TKey,TValue>. - /// The value can be null for reference types. - /// - /// - /// true if the ImmutableSortedMap<TKey,TValue> contains - /// an element with the specified value; otherwise, false. - /// - [Pure] - public bool ContainsValue(TValue value) - => this.Values.Contains(value, _valueComparer); - - #endregion - - #region IImmutableDictionary Members - - /// - /// Gets the number of elements in this collection. - /// - public int Count - { - get { return _root != null ? _root.Count : 0; } - } - - /// - /// Gets a value indicating whether this instance is empty. - /// - /// - /// true if this instance is empty; otherwise, false. - /// - public bool IsEmpty - { - get { return this.Count == 0; } - } - - /// - /// Gets the keys in the map. - /// - public IEnumerable Keys - { - get - { - if (_root == null) - { - yield break; - } - - var stack = new Stack>(); - stack.Push(_root.GetAll().GetEnumerator()); - while (stack.Count > 0) - { - var en = stack.Peek(); - if (en.MoveNext()) - { - if (en.Current is ValueBucket vb) - { - yield return vb.Key; - } - else - { - stack.Push(en.Current.GetAll().GetEnumerator()); - } - } - else - { - stack.Pop(); - } - } - } - } - - /// - /// Gets the values in the map. - /// - public IEnumerable Values - { -#pragma warning disable 618 - get { return this.GetValueBuckets().Select(vb => vb.Value); } -#pragma warning restore 618 - } - - /// - /// Gets the with the specified key. - /// - public TValue this[TKey key] - { - get - { - if (this.TryGetValue(key, out var value)) - { - return value; - } - - throw new KeyNotFoundException(); - } - } - - /// - /// Determines whether the specified key contains key. - /// - /// The key. - /// - /// true if the specified key contains key; otherwise, false. - /// - public bool ContainsKey(TKey key) - { - if (_root != null) - { - var vb = _root.Get(_keyComparer.GetHashCode(key), key, _keyComparer); - return vb != null; - } - - return false; - } - - /// - /// Determines whether this map contains the specified key-value pair. - /// - /// The key value pair. - /// - /// true if this map contains the key-value pair; otherwise, false. - /// - public bool Contains(KeyValuePair keyValuePair) - { - if (_root != null) - { - var vb = _root.Get(_keyComparer.GetHashCode(keyValuePair.Key), keyValuePair.Key, _keyComparer); - return vb != null && _valueComparer.Equals(vb.Value, keyValuePair.Value); - } - - return false; - } - - /// - /// See the interface. - /// -#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) -#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). - { - if (_root != null) - { - var vb = _root.Get(_keyComparer.GetHashCode(key), key, _keyComparer); - if (vb != null) - { - value = vb.Value; - return true; - } - } - - value = default; - return false; - } - - /// - /// See the interface. - /// - public bool TryGetKey(TKey equalKey, out TKey actualKey) - { - if (_root != null) - { - var vb = _root.Get(_keyComparer.GetHashCode(equalKey), equalKey, _keyComparer); - if (vb != null) - { - actualKey = vb.Key; - return true; - } - } - - actualKey = equalKey; - return false; - } - - #endregion - - #region IEnumerable> Members - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - public IEnumerator> GetEnumerator() - => this.GetValueBuckets().Select(vb => new KeyValuePair(vb.Key, vb.Value)).GetEnumerator(); - - #endregion - - #region IEnumerable Members - - IEnumerator IEnumerable.GetEnumerator() - => this.GetEnumerator(); - - #endregion - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() - { - var builder = new StringBuilder("ImmutableHashMap["); - var needComma = false; - foreach (var kv in this) - { - builder.Append(kv.Key); - builder.Append(':'); - builder.Append(kv.Value); - if (needComma) - { - builder.Append(','); - } - - needComma = true; - } - - builder.Append(']'); - return builder.ToString(); - } - - /// - /// Exchanges a key for the actual key instance found in this map. - /// - /// The key to search for. - /// Receives the equal key found in the map. - /// A value indicating whether an equal and existing key was found in the map. - internal bool TryExchangeKey(TKey key, [NotNullWhen(true)] out TKey? existingKey) - { - var vb = _root?.Get(_keyComparer.GetHashCode(key), key, _keyComparer); - if (vb != null) - { - existingKey = vb.Key; - return true; - } - else - { - existingKey = default; - return false; - } - } - - /// - /// Attempts to discover an instance beneath some enumerable sequence - /// if one exists. - /// - /// The sequence that may have come from an immutable map. - /// Receives the concrete typed value if one can be found. - /// true if the cast was successful; false otherwise. - private static bool TryCastToImmutableMap(IEnumerable> sequence, [NotNullWhen(true)] out ImmutableHashMap? other) - { - other = sequence as ImmutableHashMap; - if (other != null) - { - return true; - } - - return false; - } - - private ImmutableHashMap Wrap(Bucket? root) - { - if (root == null) - { - return this.Clear(); - } - - if (_root != root) - { - return root.Count == 0 ? this.Clear() : new ImmutableHashMap(root, _keyComparer, _valueComparer); - } - - return this; - } - - /// - /// Bulk adds entries to the map. - /// - /// The entries to add. - /// true to allow the sequence to include duplicate keys and let the last one win; false to throw on collisions. - /// true when being called from ToHashMap to avoid StackOverflow. - [Pure] - private ImmutableHashMap AddRange(IEnumerable> pairs, bool overwriteOnCollision, bool avoidToHashMap) - { - RoslynDebug.AssertNotNull(pairs); - Contract.Ensures(Contract.Result>() != null); - - // Some optimizations may apply if we're an empty list. - if (this.IsEmpty && !avoidToHashMap) - { - // If the items being added actually come from an ImmutableHashMap - // then there is no value in reconstructing it. - if (TryCastToImmutableMap(pairs, out var other)) - { - return other.WithComparers(_keyComparer, _valueComparer); - } - } - - var map = this; - foreach (var pair in pairs) - { - map = overwriteOnCollision - ? map.SetItem(pair.Key, pair.Value) - : map.Add(pair.Key, pair.Value); - } - - return map; - } - - private IEnumerable GetValueBuckets() - { - if (_root == null) - { - yield break; - } - - var stack = new Stack>(); - stack.Push(_root.GetAll().GetEnumerator()); - while (stack.Count > 0) - { - var en = stack.Peek(); - if (en.MoveNext()) - { - if (en.Current is ValueBucket vb) - { - yield return vb; - } - else - { - stack.Push(en.Current.GetAll().GetEnumerator()); - } - } - else - { - stack.Pop(); - } - } - } - - private abstract class Bucket - { - internal abstract int Count { get; } - - internal abstract Bucket Add(int suggestedHashRoll, ValueBucket bucket, IEqualityComparer comparer, IEqualityComparer valueComparer, bool overwriteExistingValue); - internal abstract Bucket? Remove(int hash, TKey key, IEqualityComparer comparer); - internal abstract ValueBucket? Get(int hash, TKey key, IEqualityComparer comparer); - internal abstract IEnumerable GetAll(); - } - - private abstract class ValueOrListBucket : Bucket - { - /// - /// The hash for this bucket. - /// - internal readonly int Hash; - - /// - /// Initializes a new instance of the class. - /// - /// The hash. - protected ValueOrListBucket(int hash) - => this.Hash = hash; - } - - private sealed class ValueBucket : ValueOrListBucket - { - internal readonly TKey Key; - internal readonly TValue Value; - - /// - /// Initializes a new instance of the class. - /// - /// The key. - /// The value. - /// The hashcode. - internal ValueBucket(TKey key, TValue value, int hashcode) - : base(hashcode) - { - this.Key = key; - this.Value = value; - } - - internal override int Count => 1; - - internal override Bucket Add(int suggestedHashRoll, ValueBucket bucket, IEqualityComparer comparer, IEqualityComparer valueComparer, bool overwriteExistingValue) - { - if (this.Hash == bucket.Hash) - { - if (comparer.Equals(this.Key, bucket.Key)) - { - // Overwrite of same key. If the value is the same as well, don't switch out the bucket. - if (valueComparer.Equals(this.Value, bucket.Value)) - { - return this; - } - else - { - if (overwriteExistingValue) - { - return bucket; - } - else - { - throw new ArgumentException(Strings.DuplicateKey); - } - } - } - else - { - // two of the same hash will never be happy in a hash bucket - return new ListBucket([this, bucket]); - } - } - else - { - return new HashBucket(suggestedHashRoll, this, bucket); - } - } - - internal override Bucket? Remove(int hash, TKey key, IEqualityComparer comparer) - { - if (this.Hash == hash && comparer.Equals(this.Key, key)) - { - return null; - } - - return this; - } - - internal override ValueBucket? Get(int hash, TKey key, IEqualityComparer comparer) - { - if (this.Hash == hash && comparer.Equals(this.Key, key)) - { - return this; - } - - return null; - } - - internal override IEnumerable GetAll() - => SpecializedCollections.SingletonEnumerable(this); - } - - private sealed class ListBucket : ValueOrListBucket - { - private readonly ValueBucket[] _buckets; - - /// - /// Initializes a new instance of the class. - /// - /// The buckets. - internal ListBucket(ValueBucket[] buckets) - : base(buckets[0].Hash) - { - RoslynDebug.AssertNotNull(buckets); - Debug.Assert(buckets.Length >= 2); - _buckets = buckets; - } - - internal override int Count => _buckets.Length; - - internal override Bucket Add(int suggestedHashRoll, ValueBucket bucket, IEqualityComparer comparer, IEqualityComparer valueComparer, bool overwriteExistingValue) - { - if (this.Hash == bucket.Hash) - { - var pos = this.Find(bucket.Key, comparer); - if (pos >= 0) - { - // If the value hasn't changed for this key, return the original bucket. - if (valueComparer.Equals(bucket.Value, _buckets[pos].Value)) - { - return this; - } - else - { - if (overwriteExistingValue) - { - return new ListBucket(_buckets.ReplaceAt(pos, bucket)); - } - else - { - throw new ArgumentException(Strings.DuplicateKey); - } - } - } - else - { - return new ListBucket(_buckets.InsertAt(_buckets.Length, bucket)); - } - } - else - { - return new HashBucket(suggestedHashRoll, this, bucket); - } - } - - internal override Bucket? Remove(int hash, TKey key, IEqualityComparer comparer) - { - if (this.Hash == hash) - { - var pos = this.Find(key, comparer); - if (pos >= 0) - { - if (_buckets.Length == 1) - { - return null; - } - else if (_buckets.Length == 2) - { - return pos == 0 ? _buckets[1] : _buckets[0]; - } - else - { - return new ListBucket(_buckets.RemoveAt(pos)); - } - } - } - - return this; - } - - internal override ValueBucket? Get(int hash, TKey key, IEqualityComparer comparer) - { - if (this.Hash == hash) - { - var pos = this.Find(key, comparer); - if (pos >= 0) - { - return _buckets[pos]; - } - } - - return null; - } - - private int Find(TKey key, IEqualityComparer comparer) - { - for (var i = 0; i < _buckets.Length; i++) - { - if (comparer.Equals(key, _buckets[i].Key)) - { - return i; - } - } - - return -1; - } - - internal override IEnumerable GetAll() - => _buckets; - } - - private sealed class HashBucket : Bucket - { - private readonly int _hashRoll; - private readonly uint _used; - private readonly Bucket[] _buckets; - private readonly int _count; - - /// - /// Initializes a new instance of the class. - /// - /// The hash roll. - /// The used. - /// The buckets. - /// The count. - private HashBucket(int hashRoll, uint used, Bucket[] buckets, int count) - { - RoslynDebug.AssertNotNull(buckets); - Debug.Assert(buckets.Length == CountBits(used)); - - _hashRoll = hashRoll & 31; - _used = used; - _buckets = buckets; - _count = count; - } - - /// - /// Initializes a new instance of the class. - /// - /// The suggested hash roll. - /// The bucket1. - /// The bucket2. - internal HashBucket(int suggestedHashRoll, ValueOrListBucket bucket1, ValueOrListBucket bucket2) - { - RoslynDebug.AssertNotNull(bucket1); - RoslynDebug.AssertNotNull(bucket2); - Debug.Assert(bucket1.Hash != bucket2.Hash); - - // find next hashRoll that causes these two to be slotted in different buckets - var h1 = bucket1.Hash; - var h2 = bucket2.Hash; - int s1; - int s2; - for (var i = 0; i < 32; i++) - { - _hashRoll = (suggestedHashRoll + i) & 31; - s1 = this.ComputeLogicalSlot(h1); - s2 = this.ComputeLogicalSlot(h2); - if (s1 != s2) - { - _count = 2; - _used = (1u << s1) | (1u << s2); - _buckets = new Bucket[2]; - _buckets[this.ComputePhysicalSlot(s1)] = bucket1; - _buckets[this.ComputePhysicalSlot(s2)] = bucket2; - return; - } - } - - throw new InvalidOperationException(); - } - - internal override int Count => _count; - - internal override Bucket Add(int suggestedHashRoll, ValueBucket bucket, IEqualityComparer keyComparer, IEqualityComparer valueComparer, bool overwriteExistingValue) - { - var logicalSlot = ComputeLogicalSlot(bucket.Hash); - if (IsInUse(logicalSlot)) - { - // if this slot is in use, then add the new item to the one in this slot - var physicalSlot = ComputePhysicalSlot(logicalSlot); - var existing = _buckets[physicalSlot]; - - // suggest hash roll that will cause any nested hash bucket to use entirely new bits for picking logical slot - // note: we ignore passed in suggestion, and base new suggestion off current hash roll. - var added = existing.Add(_hashRoll + 5, bucket, keyComparer, valueComparer, overwriteExistingValue); - if (added != existing) - { - var newBuckets = _buckets.ReplaceAt(physicalSlot, added); - return new HashBucket(_hashRoll, _used, newBuckets, _count - existing.Count + added.Count); - } - else - { - return this; - } - } - else - { - var physicalSlot = ComputePhysicalSlot(logicalSlot); - var newBuckets = _buckets.InsertAt(physicalSlot, bucket); - var newUsed = InsertBit(logicalSlot, _used); - return new HashBucket(_hashRoll, newUsed, newBuckets, _count + bucket.Count); - } - } - - internal override Bucket? Remove(int hash, TKey key, IEqualityComparer comparer) - { - var logicalSlot = ComputeLogicalSlot(hash); - if (IsInUse(logicalSlot)) - { - var physicalSlot = ComputePhysicalSlot(logicalSlot); - var existing = _buckets[physicalSlot]; - var result = existing.Remove(hash, key, comparer); - if (result == null) - { - if (_buckets.Length == 1) - { - return null; - } - else if (_buckets.Length == 2) - { - return physicalSlot == 0 ? _buckets[1] : _buckets[0]; - } - else - { - return new HashBucket(_hashRoll, RemoveBit(logicalSlot, _used), _buckets.RemoveAt(physicalSlot), _count - existing.Count); - } - } - else if (_buckets[physicalSlot] != result) - { - return new HashBucket(_hashRoll, _used, _buckets.ReplaceAt(physicalSlot, result), _count - existing.Count + result.Count); - } - } - - return this; - } - - internal override ValueBucket? Get(int hash, TKey key, IEqualityComparer comparer) - { - var logicalSlot = ComputeLogicalSlot(hash); - if (IsInUse(logicalSlot)) - { - var physicalSlot = ComputePhysicalSlot(logicalSlot); - return _buckets[physicalSlot].Get(hash, key, comparer); - } - - return null; - } - - internal override IEnumerable GetAll() - => _buckets; - - private bool IsInUse(int logicalSlot) - => ((1 << logicalSlot) & _used) != 0; - - private int ComputeLogicalSlot(int hc) - { - var uc = unchecked((uint)hc); - var rotated = RotateRight(uc, _hashRoll); - return unchecked((int)(rotated & 31)); - } - - [Pure] - private static uint RotateRight(uint v, int n) - { - Debug.Assert(n is >= 0 and < 32); - if (n == 0) - { - return v; - } - - return v >> n | (v << (32 - n)); - } - - private int ComputePhysicalSlot(int logicalSlot) - { - Debug.Assert(logicalSlot is >= 0 and < 32); - Contract.Ensures(Contract.Result() >= 0); - if (_buckets.Length == 32) - { - return logicalSlot; - } - - if (logicalSlot == 0) - { - return 0; - } - - var mask = uint.MaxValue >> (32 - logicalSlot); // only count the bits up to the logical slot # - return CountBits(_used & mask); - } - - [Pure] - private static int CountBits(uint v) - { - unchecked - { - v -= ((v >> 1) & 0x55555555u); - v = (v & 0x33333333u) + ((v >> 2) & 0x33333333u); - return (int)((v + (v >> 4) & 0xF0F0F0Fu) * 0x1010101u) >> 24; - } - } - - [Pure] - private static uint InsertBit(int position, uint bits) - { - Debug.Assert(0 == (bits & (1u << position))); - return bits | (1u << position); - } - - [Pure] - private static uint RemoveBit(int position, uint bits) - { - Debug.Assert(0 != (bits & (1u << position))); - return bits & ~(1u << position); - } - } - - #region IImmutableDictionary Members - - IImmutableDictionary IImmutableDictionary.Clear() - => this.Clear(); - - IImmutableDictionary IImmutableDictionary.Add(TKey key, TValue value) - => this.Add(key, value); - - IImmutableDictionary IImmutableDictionary.SetItem(TKey key, TValue value) - => this.SetItem(key, value); - - IImmutableDictionary IImmutableDictionary.SetItems(IEnumerable> items) - => this.SetItems(items); - - IImmutableDictionary IImmutableDictionary.AddRange(IEnumerable> pairs) - => this.AddRange(pairs); - - IImmutableDictionary IImmutableDictionary.RemoveRange(IEnumerable keys) - => this.RemoveRange(keys); - - IImmutableDictionary IImmutableDictionary.Remove(TKey key) - => this.Remove(key); - - #endregion - - /// - /// A simple view of the immutable collection that the debugger can show to the developer. - /// - private class DebuggerProxy - { - /// - /// The collection to be enumerated. - /// - private readonly ImmutableHashMap _map; - - /// - /// The simple view of the collection. - /// - private KeyValuePair[]? _contents; - - /// - /// Initializes a new instance of the class. - /// - /// The collection to display in the debugger - public DebuggerProxy(ImmutableHashMap map) - { - Requires.NotNull(map, "map"); - _map = map; - } - - /// - /// Gets a simple debugger-viewable collection. - /// - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public KeyValuePair[] Contents - { - get - { - _contents ??= _map.ToArray(); - - return _contents; - } - } - } - - private static class Requires - { - [DebuggerStepThrough] - public static T NotNullAllowStructs(T value, string parameterName) - { - if (value == null) - { - throw new ArgumentNullException(parameterName); - } - - return value; - } - - [DebuggerStepThrough] - public static T NotNull(T value, string parameterName) where T : class - { - if (value == null) - { - throw new ArgumentNullException(parameterName); - } - - return value; - } - - [DebuggerStepThrough] - public static Exception FailRange(string parameterName, string? message = null) - { - if (string.IsNullOrEmpty(message)) - { - throw new ArgumentOutOfRangeException(parameterName); - } - - throw new ArgumentOutOfRangeException(parameterName, message); - } - - [DebuggerStepThrough] - public static void Range(bool condition, string parameterName, string? message = null) - { - if (!condition) - { - Requires.FailRange(parameterName, message); - } - } - } - - private static class Strings - { - public static string DuplicateKey => CompilerExtensionsResources.An_element_with_the_same_key_but_a_different_value_already_exists; - } -} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMapExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMapExtensions.cs deleted file mode 100644 index 6236cb881c727..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/CompilerUtilities/ImmutableHashMapExtensions.cs +++ /dev/null @@ -1,58 +0,0 @@ -// 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.Threading; -using Roslyn.Collections.Immutable; - -namespace Roslyn.Utilities; - -internal static class ImmutableHashMapExtensions -{ - /// - /// Obtains the value for the specified key from a dictionary, or adds a new value to the dictionary where the key did not previously exist. - /// - /// The type of key stored by the dictionary. - /// The type of value stored by the dictionary. - /// The type of argument supplied to the value factory. - /// The variable or field to atomically update if the specified is not in the dictionary. - /// The key for the value to retrieve or add. - /// The function to execute to obtain the value to insert into the dictionary if the key is not found. Returns null if the value can't be obtained. - /// The argument to pass to the value factory. - /// The value obtained from the dictionary or if it was not present. - public static TValue? GetOrAdd(ref ImmutableHashMap location, TKey key, Func valueProvider, TArg factoryArgument) - where TKey : notnull - where TValue : class - { - Contract.ThrowIfNull(valueProvider); - - var map = Volatile.Read(ref location); - Contract.ThrowIfNull(map); - if (map.TryGetValue(key, out var existingValue)) - { - return existingValue; - } - - var newValue = valueProvider(key, factoryArgument); - if (newValue is null) - { - return null; - } - - do - { - var augmentedMap = map.Add(key, newValue); - var replacedMap = Interlocked.CompareExchange(ref location, augmentedMap, map); - if (replacedMap == map) - { - return newValue; - } - - map = replacedMap; - } - while (!map.TryGetValue(key, out existingValue)); - - return existingValue; - } -}