diff --git a/src/Compilers/Core/Portable/CodeGen/TokenMap.cs b/src/Compilers/Core/Portable/CodeGen/TokenMap.cs index 49df657529796..fecdfeffa91da 100644 --- a/src/Compilers/Core/Portable/CodeGen/TokenMap.cs +++ b/src/Compilers/Core/Portable/CodeGen/TokenMap.cs @@ -8,8 +8,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; +using System.Threading; +using Microsoft.Cci; namespace Microsoft.CodeAnalysis.CodeGen { @@ -24,74 +24,97 @@ namespace Microsoft.CodeAnalysis.CodeGen /// internal sealed class TokenMap { - private readonly ConcurrentDictionary _itemIdentityToToken = new ConcurrentDictionary(ReferenceEqualityComparer.Instance); + private readonly ConcurrentDictionary _itemIdentityToToken = new(); + private readonly Dictionary _itemEquivalentToToken = new(); + private object[] _items = Array.Empty(); + private int _count = 0; - private readonly Dictionary _itemToToken; - private readonly ArrayBuilder _items = new ArrayBuilder(); + internal TokenMap() { } - internal TokenMap(IEqualityComparer comparer) + public uint GetOrAddTokenFor(IReference item, out bool referenceAdded) { - _itemToToken = new Dictionary(comparer); + if (_itemIdentityToToken.TryGetValue(new IReferenceOrISignature(item), out uint token)) + { + referenceAdded = false; + return token; + } + + return AddItem(new IReferenceOrISignatureEquivalent(item), out referenceAdded); } - public uint GetOrAddTokenFor(object item, out bool referenceAdded) + public uint GetOrAddTokenFor(ISignature item, out bool referenceAdded) { - uint tmp; - if (_itemIdentityToToken.TryGetValue(item, out tmp)) + if (_itemIdentityToToken.TryGetValue(new IReferenceOrISignature(item), out uint token)) { referenceAdded = false; - return (uint)tmp; + return token; } - return AddItem(item, out referenceAdded); + return AddItem(new IReferenceOrISignatureEquivalent(item), out referenceAdded); } - private uint AddItem(object item, out bool referenceAdded) + private uint AddItem(IReferenceOrISignatureEquivalent item, out bool referenceAdded) { - Debug.Assert(item is Cci.ISignature || item is Cci.IReference); uint token; - // NOTE: cannot use GetOrAdd here since items and itemToToken must be in sync // so if we do need to add we have to take a lock and modify both collections. - lock (_items) + lock (_itemEquivalentToToken) { - if (!_itemToToken.TryGetValue(item, out token)) + // Check if there is an equivalent type that has a token + if (!_itemEquivalentToToken.TryGetValue(item, out token)) { - token = (uint)_items.Count; - _items.Add(item); - _itemToToken.Add(item, token); + token = (uint)_count; + // No equivalent, add the token for this type + _itemEquivalentToToken.Add(item, token); + + var count = (int)token + 1; + var items = _items; + if (items.Length > count) + { + items[(int)token] = item.AsObject(); + } + else + { + // Not enough room, we need to resize the array + Array.Resize(ref items, Math.Max(8, count * 2)); + items[(int)token] = item.AsObject(); + + // Update the updated array reference before updating _count + Volatile.Write(ref _items, items); + } + + Volatile.Write(ref _count, count); } } + // Use the provided token to update the reference dictionary referenceAdded = _itemIdentityToToken.TryAdd(item, token); return token; } public object GetItem(uint token) { - lock (_items) - { - return _items[(int)token]; - } - } + // If a token has been handed out, then it should be always within _count of the + // current array and a lock is not required. + Debug.Assert(token < (uint)_count && _count <= _items.Length); - public IEnumerable GetAllItems() - { - lock (_items) - { - return _items.ToArray(); - } + return _items[(int)token]; } //TODO: why is this is called twice during emit? // should probably return ROA instead of IE and cache that in Module. (and no need to return count) - public IEnumerable GetAllItemsAndCount(out int count) + public ReadOnlySpan GetAllItems() { - lock (_items) - { - count = _items.Count; - return _items.ToArray(); - } + // Read _count before _items reference, to match inverse of the writes in AddItem. + // So _items is guaranteed to have at least count items; and a lock is not required. + + // Read the count prior to getting the array + int count = Volatile.Read(ref _count); + // Read the array reference + object[] items = Volatile.Read(ref _items); + + // Return a right sized view of the array based on read count and reference. + return new ReadOnlySpan(items, 0, count); } } } diff --git a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs index 6fff9647c5f2e..00bf03d15d0d0 100644 --- a/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs +++ b/src/Compilers/Core/Portable/Emit/CommonPEModuleBuilder.cs @@ -33,9 +33,9 @@ internal abstract class CommonPEModuleBuilder : Cci.IUnit, Cci.IModuleReference internal Cci.IMethodReference DebugEntryPoint; private readonly ConcurrentDictionary _methodBodyMap; - private readonly TokenMap _referencesInILMap = new TokenMap(MetadataEntityReferenceComparer.ConsiderEverything); - private readonly ItemTokenMap _stringsInILMap = new ItemTokenMap(); - private readonly ItemTokenMap _sourceDocumentsInILMap = new ItemTokenMap(); + private readonly TokenMap _referencesInILMap = new(); + private readonly ItemTokenMap _stringsInILMap = new(); + private readonly ItemTokenMap _sourceDocumentsInILMap = new(); private ImmutableArray _lazyAssemblyReferenceAliases; private ImmutableArray _lazyManagedResources; @@ -335,9 +335,9 @@ public string GetStringFromToken(uint token) return _stringsInILMap.GetItem(token); } - public IEnumerable ReferencesInIL(out int count) + public ReadOnlySpan ReferencesInIL() { - return _referencesInILMap.GetAllItemsAndCount(out count); + return _referencesInILMap.GetAllItems(); } /// diff --git a/src/Compilers/Core/Portable/Emit/MetadataEntityReferenceComparer.cs b/src/Compilers/Core/Portable/Emit/MetadataEntityReferenceComparer.cs deleted file mode 100644 index 90ccd7b0cc107..0000000000000 --- a/src/Compilers/Core/Portable/Emit/MetadataEntityReferenceComparer.cs +++ /dev/null @@ -1,50 +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.Collections.Generic; -using Microsoft.CodeAnalysis.Symbols; - -namespace Microsoft.CodeAnalysis.Emit -{ - internal sealed class MetadataEntityReferenceComparer : IEqualityComparer - { - internal static readonly MetadataEntityReferenceComparer ConsiderEverything = new MetadataEntityReferenceComparer(TypeCompareKind.ConsiderEverything); - - private readonly TypeCompareKind _compareKind; - - private MetadataEntityReferenceComparer(TypeCompareKind compareKind) - { - _compareKind = compareKind; - } - - public new bool Equals(object x, object y) - { - if (x is null) - { - return y is null; - } - else if (ReferenceEquals(x, y)) - { - return true; - } - else if (x is ISymbolInternal sx && y is ISymbolInternal sy) - { - return sx.Equals(sy, _compareKind); - } - else if (x is ISymbolCompareKindComparableInternal cx && y is ISymbolCompareKindComparableInternal cy) - { - return cx.Equals(cy, _compareKind); - } - else - { - return x.Equals(y); - } - } - - public int GetHashCode(object obj) - { - return obj?.GetHashCode() ?? 0; - } - } -} diff --git a/src/Compilers/Core/Portable/IReferenceOrISignature.cs b/src/Compilers/Core/Portable/IReferenceOrISignature.cs new file mode 100644 index 0000000000000..a83a17ec968b4 --- /dev/null +++ b/src/Compilers/Core/Portable/IReferenceOrISignature.cs @@ -0,0 +1,97 @@ +// 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. + +#nullable enable + +using System; +using System.Runtime.CompilerServices; +using Microsoft.Cci; +using Microsoft.CodeAnalysis.Symbols; + +namespace Microsoft.CodeAnalysis +{ + // These types are to enable fast-path devirtualization in the Jit. Dictionary, HashTable + // and ConcurrentDictionary will devirtualize (and potentially inline) the IEquatable.Equals + // method for a struct when the Comparer is unspecified in .NET Core, .NET 5; whereas specifying + // a Comparer will make .Equals and GetHashcode slower interface calls. + + /// + /// Used to devirtualize Dictionary/HashSet for EqualityComparer{T}.Default + /// + internal readonly struct IReferenceOrISignatureEquivalent : IEquatable + { + private readonly object _item; + + public IReferenceOrISignatureEquivalent(IReference item) => _item = item; + + public IReferenceOrISignatureEquivalent(ISignature item) => _item = item; + + // Needed to resolve ambiguity for types that implement both IReference and ISignature + public IReferenceOrISignatureEquivalent(IMethodReference item) => _item = item; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(IReferenceOrISignatureEquivalent other) + { + // Fast inlinable ReferenceEquals + var x = _item; + var y = other._item; + if (ReferenceEquals(x, y)) + { + return true; + } + + return EqualsSlow(x, y); + } + + private static bool EqualsSlow(object x, object y) + { + if (x is ISymbolInternal sx && y is ISymbolInternal sy) + { + return sx.Equals(sy, TypeCompareKind.ConsiderEverything); + } + else if (x is ISymbolCompareKindComparableInternal cx && y is ISymbolCompareKindComparableInternal cy) + { + return cx.Equals(cy, TypeCompareKind.ConsiderEverything); + } + else + { + return x.Equals(y); + } + } + + public override bool Equals(object? obj) => false; + + public override int GetHashCode() => _item.GetHashCode(); + + public override string ToString() => _item.ToString() ?? "null"; + + internal object AsObject() => _item; + } + + /// + /// Used to devirtualize ConcurrentDictionary for EqualityComparer{T}.Default and ReferenceEquals + /// + internal readonly struct IReferenceOrISignature : IEquatable + { + private readonly object _item; + + public IReferenceOrISignature(IReference item) => _item = item; + + public IReferenceOrISignature(ISignature item) => _item = item; + + // Used by implicit conversion + private IReferenceOrISignature(object item) => _item = item; + + public static implicit operator IReferenceOrISignature(IReferenceOrISignatureEquivalent item) + => new IReferenceOrISignature(item.AsObject()); + + public bool Equals(IReferenceOrISignature other) => ReferenceEquals(_item, other._item); + + public override bool Equals(object? obj) => false; + + public override int GetHashCode() => _item.GetHashCode(); + + public override string ToString() => _item.ToString() ?? "null"; + } +} diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs index 42c0e6557ef2a..b61dc5a33baef 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs @@ -464,18 +464,10 @@ private bool IsMinimalDelta private void CreateMethodBodyReferenceIndex() { - int count; - var referencesInIL = module.ReferencesInIL(out count); + var referencesInIL = module.ReferencesInIL(); - _pseudoSymbolTokenToTokenMap = new EntityHandle[count]; - _pseudoSymbolTokenToReferenceMap = new object[count]; - - int cur = 0; - foreach (object o in referencesInIL) - { - _pseudoSymbolTokenToReferenceMap[cur] = o; - cur++; - } + _pseudoSymbolTokenToTokenMap = new EntityHandle[referencesInIL.Length]; + _pseudoSymbolTokenToReferenceMap = referencesInIL.ToArray(); } private void CreateIndices() diff --git a/src/Compilers/Core/Portable/PEWriter/ReferenceIndexerBase.cs b/src/Compilers/Core/Portable/PEWriter/ReferenceIndexerBase.cs index 49feb2b198973..7ed9dc12d73a4 100644 --- a/src/Compilers/Core/Portable/PEWriter/ReferenceIndexerBase.cs +++ b/src/Compilers/Core/Portable/PEWriter/ReferenceIndexerBase.cs @@ -6,14 +6,15 @@ using System.Collections.Generic; using System.Diagnostics; using EmitContext = Microsoft.CodeAnalysis.Emit.EmitContext; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Emit; namespace Microsoft.Cci { internal abstract class ReferenceIndexerBase : MetadataVisitor { - private readonly HashSet _alreadySeen = new HashSet(MetadataEntityReferenceComparer.ConsiderEverything); - private readonly HashSet _alreadyHasToken = new HashSet(MetadataEntityReferenceComparer.ConsiderEverything); + private readonly HashSet _alreadySeen = new(); + private readonly HashSet _alreadyHasToken = new(); protected bool typeReferenceNeedsToken; internal ReferenceIndexerBase(EmitContext context) @@ -46,7 +47,7 @@ public override void Visit(IEventDefinition eventDefinition) public override void Visit(IFieldReference fieldReference) { - if (!_alreadySeen.Add(fieldReference)) + if (!_alreadySeen.Add(new IReferenceOrISignatureEquivalent(fieldReference))) { return; } @@ -125,7 +126,7 @@ public override void Visit(IMethodReference methodReference) return; } - if (!_alreadySeen.Add(methodReference)) + if (!_alreadySeen.Add(new IReferenceOrISignatureEquivalent(methodReference))) { return; } @@ -393,7 +394,7 @@ public override void Visit(ITypeReference typeReference) // Returns true if we need to look at the children, false otherwise. private bool VisitTypeReference(ITypeReference typeReference) { - if (!_alreadySeen.Add(typeReference)) + if (!_alreadySeen.Add(new IReferenceOrISignatureEquivalent(typeReference))) { if (!this.typeReferenceNeedsToken) { @@ -401,7 +402,7 @@ private bool VisitTypeReference(ITypeReference typeReference) } this.typeReferenceNeedsToken = false; - if (!_alreadyHasToken.Add(typeReference)) + if (!_alreadyHasToken.Add(new IReferenceOrISignatureEquivalent(typeReference))) { return false; } @@ -419,13 +420,13 @@ private bool VisitTypeReference(ITypeReference typeReference) if (specializedNestedTypeReference != null) { INestedTypeReference unspecializedNestedTypeReference = specializedNestedTypeReference.GetUnspecializedVersion(Context); - if (_alreadyHasToken.Add(unspecializedNestedTypeReference)) + if (_alreadyHasToken.Add(new IReferenceOrISignatureEquivalent(unspecializedNestedTypeReference))) { RecordTypeReference(unspecializedNestedTypeReference); } } - if (this.typeReferenceNeedsToken && _alreadyHasToken.Add(typeReference)) + if (this.typeReferenceNeedsToken && _alreadyHasToken.Add(new IReferenceOrISignatureEquivalent(typeReference))) { RecordTypeReference(typeReference); }