| File: Binding\AbstractLookupSymbolsInfo.cs | Web Access |
| Project: src\roslyn\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis) |
// 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.Generic; using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { internal abstract class AbstractLookupSymbolsInfo<TSymbol> where TSymbol : class, ISymbolInternal { public struct ArityEnumerator : IEnumerator<int> { private int _current; private readonly int _low32bits; private int[]? _arities; private const int resetValue = -1; private const int reachedEndValue = int.MaxValue; internal ArityEnumerator(int bitVector, HashSet<int>? arities) { _current = resetValue; _low32bits = bitVector; if (arities == null) { _arities = null; } else { _arities = arities.ToArray(); Array.Sort(_arities); } } public int Current => _current; public void Dispose() => _arities = null; object? System.Collections.IEnumerator.Current => _current; public bool MoveNext() { if (_current == reachedEndValue) { // Already reached the end return false; } // Find the next set bit int arity; // Find the next set bit for (arity = ++_current; arity < 32; arity++) { if (((_low32bits >> arity) & 1) != 0) { _current = arity; return true; } } if (_arities != null) { // Binary search for the current value int index = _arities.BinarySearch(arity); if (index < 0) { index = ~index; } if (index < _arities.Length) { _current = _arities[index]; return true; } } _current = reachedEndValue; return false; } public void Reset() => _current = resetValue; } // TODO: Is the cost of boxing every instance of UniqueSymbolOrArities that we // hand out justified by the benefit of not being able to call our internal // APIs incorrectly (without an explicit cast)? public interface IArityEnumerable { ArityEnumerator GetEnumerator(); int Count { get; } } // PERF: This is a very frequent allocation, so the aim is to keep // it as small as possible. private struct UniqueSymbolOrArities : IArityEnumerable { // For most situations, the set of arities is small and // the values are in the low single digits. However, the // theoretical max for arity is 32,767 (Int16.MaxValue). // The arities field is, therefore, a bitvector of the // arity values from zero to 31. // If an arity greater than 31 is encountered, then // uniqueSymbolOrArities becomes a HashSet for bits // 32 and above. // This (object) field may be a TSymbol, null or a HashSet<int> // If it's a TSymbol: // Then arityBitVectorOrUniqueArity is interpreted as a unique // arity (which may be any value) // If it's null: // Then arityBitVectorOrUniqueArity is interpreted as a bitvector // of arities. // Otherwise it's a HashSet<int>: // Then arityBitVectorOrUniqueArity is interpreted as a bitvector // of arities for arities from zero to 31 and the HashSet contains // arities of 32 or more. private object? _uniqueSymbolOrArities; private int _arityBitVectorOrUniqueArity; public UniqueSymbolOrArities(int arity, TSymbol uniqueSymbol) { _uniqueSymbolOrArities = uniqueSymbol; _arityBitVectorOrUniqueArity = arity; //if there's no unique symbol, how can there be an arity? Debug.Assert((uniqueSymbol != null) || (arity == 0)); } public void AddSymbol(TSymbol symbol, int arity) { if (symbol != null && symbol == _uniqueSymbolOrArities) { Debug.Assert(arity == _arityBitVectorOrUniqueArity); return; } if (this.HasUniqueSymbol) { // The symbol is no longer unique. So clear the // UniqueSymbol field and record the unique arity // before adding the new arity value. Debug.Assert(_uniqueSymbolOrArities is TSymbol); _uniqueSymbolOrArities = null; int uniqueArity = _arityBitVectorOrUniqueArity; _arityBitVectorOrUniqueArity = 0; AddArity(uniqueArity); } AddArity(arity); } private bool HasUniqueSymbol => _uniqueSymbolOrArities != null && !(_uniqueSymbolOrArities is HashSet<int>); private void AddArity(int arity) { Debug.Assert(!this.HasUniqueSymbol); // arities between 0 and 31 will fit in the bit vector if (arity < 32) { unchecked { int bit = 1 << arity; _arityBitVectorOrUniqueArity |= bit; } return; } // Otherwise, use a HashSet var hashSet = _uniqueSymbolOrArities as HashSet<int>; if (hashSet == null) { hashSet = new HashSet<int>(); _uniqueSymbolOrArities = hashSet; } hashSet.Add(arity); } public void GetUniqueSymbolOrArities(out IArityEnumerable? arities, out TSymbol? uniqueSymbol) { if (this.HasUniqueSymbol) { arities = null; #nullable disable // Can '_uniqueSymbolOrArities' be null? https://github.com/dotnet/roslyn/issues/39166 uniqueSymbol = (TSymbol)_uniqueSymbolOrArities; #nullable enable } else { arities = (_uniqueSymbolOrArities == null && _arityBitVectorOrUniqueArity == 0) ? null : (IArityEnumerable)this; uniqueSymbol = null; } } public ArityEnumerator GetEnumerator() { Debug.Assert(!this.HasUniqueSymbol); return new ArityEnumerator(_arityBitVectorOrUniqueArity, (HashSet<int>?)_uniqueSymbolOrArities); } public int Count { get { Debug.Assert(!this.HasUniqueSymbol); int count = BitArithmeticUtilities.CountBits(_arityBitVectorOrUniqueArity); var set = (HashSet<int>?)_uniqueSymbolOrArities; if (set != null) { count += set.Count; } return count; } } #if DEBUG internal TSymbol? UniqueSymbol => _uniqueSymbolOrArities as TSymbol; #endif } private readonly IEqualityComparer<string> _comparer; private readonly Dictionary<string, UniqueSymbolOrArities> _nameMap; internal string? FilterName { get; set; } protected AbstractLookupSymbolsInfo(IEqualityComparer<string> comparer) { _comparer = comparer; _nameMap = new Dictionary<string, UniqueSymbolOrArities>(comparer); } public bool CanBeAdded(string name) => FilterName == null || _comparer.Equals(name, FilterName); public void AddSymbol(TSymbol symbol, string name, int arity) { UniqueSymbolOrArities pair; if (!_nameMap.TryGetValue(name, out pair)) { // First time seeing a symbol with this name. Create a mapping for it from the name // to the one arity we've seen, and also store around the symbol as it's currently // unique. pair = new UniqueSymbolOrArities(arity, symbol); _nameMap.Add(name, pair); } else { pair.AddSymbol(symbol, arity); // Since 'pair' is a struct, the dictionary must be updated with the new value _nameMap[name] = pair; } #if DEBUG // After adding this symbol, the name must map to it (if it's unique), or it must map to // nothing (if it's not unique). If it maps to another symbol then we've done something // horribly wrong. Debug.Assert(pair.UniqueSymbol == null || pair.UniqueSymbol == symbol); #endif } public ICollection<String> Names => _nameMap.Keys; public int Count => _nameMap.Count; /// <summary> /// If <paramref name="uniqueSymbol"/> is set, then <paramref name="arities"/> will be null. /// The only arity in that case will be encoded in the symbol. /// </summary> /// <param name="name"></param> /// <param name="arities"></param> /// <param name="uniqueSymbol"></param> /// <returns></returns> public bool TryGetAritiesAndUniqueSymbol( string name, out IArityEnumerable? arities, out TSymbol? uniqueSymbol) { Debug.Assert(CanBeAdded(name)); UniqueSymbolOrArities pair; if (!_nameMap.TryGetValue(name, out pair)) { arities = null; uniqueSymbol = null; return false; } // If a unique symbol is set (not null), then its arity should be determined // by inspecting the symbol. pair.GetUniqueSymbolOrArities(out arities, out uniqueSymbol); return true; } public void Clear() { _nameMap.Clear(); FilterName = null; } } }