using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
+using Internal.Runtime.CompilerServices;
+
namespace System.Collections.Generic
{
/// <summary>
{
private struct Entry
{
+ public uint hashCode;
// 0-based index of next entry in chain: -1 means end of chain
// also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3,
// so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc.
public int next;
- public uint hashCode;
public TKey key; // Key of entry
public TValue value; // Value of entry
}
{
get
{
- int i = FindEntry(key);
- if (i >= 0) return _entries![i].value;
+ ref TValue value = ref FindValue(key);
+ if (!Unsafe.IsNullRef(ref value))
+ {
+ return value;
+ }
ThrowHelper.ThrowKeyNotFoundException(key);
return default;
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> keyValuePair)
{
- int i = FindEntry(keyValuePair.Key);
- if (i >= 0 && EqualityComparer<TValue>.Default.Equals(_entries![i].value, keyValuePair.Value))
+ ref TValue value = ref FindValue(keyValuePair.Key);
+ if (!Unsafe.IsNullRef(ref value) && EqualityComparer<TValue>.Default.Equals(value, keyValuePair.Value))
{
return true;
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair)
{
- int i = FindEntry(keyValuePair.Key);
- if (i >= 0 && EqualityComparer<TValue>.Default.Equals(_entries![i].value, keyValuePair.Value))
+ ref TValue value = ref FindValue(keyValuePair.Key);
+ if (!Unsafe.IsNullRef(ref value) && EqualityComparer<TValue>.Default.Equals(value, keyValuePair.Value))
{
Remove(keyValuePair.Key);
return true;
}
public bool ContainsKey(TKey key)
- => FindEntry(key) >= 0;
+ => !Unsafe.IsNullRef(ref FindValue(key));
public bool ContainsValue(TValue value)
{
}
}
- private int FindEntry(TKey key)
+ private ref TValue FindValue(TKey key)
{
if (key == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
- int i = -1;
int[]? buckets = _buckets;
- Entry[]? entries = _entries;
- int collisionCount = 0;
+ ref Entry entry = ref Unsafe.NullRef<Entry>();
if (buckets != null)
{
- Debug.Assert(entries != null, "expected entries to be != null");
+ Debug.Assert(_entries != null, "expected entries to be != null");
IEqualityComparer<TKey>? comparer = _comparer;
if (comparer == null)
{
uint hashCode = (uint)key.GetHashCode();
- // Value in _buckets is 1-based
- i = buckets[hashCode % (uint)buckets.Length] - 1;
+ int i = buckets[hashCode % (uint)buckets.Length];
+ Entry[]? entries = _entries;
+ uint collisionCount = 0;
if (default(TKey)! != null) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
{
// ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
- while (true)
+
+ // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
+ i--;
+ do
{
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
// Test in if to drop range check for following array access
- if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key)))
+ if ((uint)i >= (uint)entries.Length)
{
- break;
+ goto ReturnNotFound;
}
- i = entries[i].next;
- if (collisionCount >= entries.Length)
+ entry = ref entries[i];
+ if (entry.hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entry.key, key))
{
- // The chain of entries forms a loop; which means a concurrent update has happened.
- // Break out of the loop and throw, rather than looping forever.
- ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ goto ReturnFound;
}
+
+ i = entry.next;
+
collisionCount++;
- }
+ } while (collisionCount <= (uint)entries.Length);
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ goto ConcurrentOperation;
}
else
{
// https://github.com/dotnet/coreclr/issues/17273
// So cache in a local rather than get EqualityComparer per loop iteration
EqualityComparer<TKey> defaultComparer = EqualityComparer<TKey>.Default;
- while (true)
+
+ // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
+ i--;
+ do
{
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
// Test in if to drop range check for following array access
- if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key)))
+ if ((uint)i >= (uint)entries.Length)
{
- break;
+ goto ReturnNotFound;
}
- i = entries[i].next;
- if (collisionCount >= entries.Length)
+ entry = ref entries[i];
+ if (entry.hashCode == hashCode && defaultComparer.Equals(entry.key, key))
{
- // The chain of entries forms a loop; which means a concurrent update has happened.
- // Break out of the loop and throw, rather than looping forever.
- ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ goto ReturnFound;
}
+
+ i = entry.next;
+
collisionCount++;
- }
+ } while (collisionCount <= (uint)entries.Length);
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ goto ConcurrentOperation;
}
}
else
{
uint hashCode = (uint)comparer.GetHashCode(key);
- // Value in _buckets is 1-based
- i = buckets[hashCode % (uint)buckets.Length] - 1;
- while (true)
+ int i = buckets[hashCode % (uint)buckets.Length];
+ Entry[]? entries = _entries;
+ uint collisionCount = 0;
+ // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
+ i--;
+ do
{
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
// Test in if to drop range check for following array access
- if ((uint)i >= (uint)entries.Length ||
- (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)))
+ if ((uint)i >= (uint)entries.Length)
{
- break;
+ goto ReturnNotFound;
}
- i = entries[i].next;
- if (collisionCount >= entries.Length)
+ entry = ref entries[i];
+ if (entry.hashCode == hashCode && comparer.Equals(entry.key, key))
{
- // The chain of entries forms a loop; which means a concurrent update has happened.
- // Break out of the loop and throw, rather than looping forever.
- ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ goto ReturnFound;
}
+
+ i = entry.next;
+
collisionCount++;
- }
+ } while (collisionCount <= (uint)entries.Length);
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ goto ConcurrentOperation;
}
}
- return i;
+ goto ReturnNotFound;
+
+ ConcurrentOperation:
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ ReturnFound:
+ ref TValue value = ref entry.value;
+ Return:
+ return ref value;
+ ReturnNotFound:
+ value = ref Unsafe.NullRef<TValue>();
+ goto Return;
}
private int Initialize(int capacity)
IEqualityComparer<TKey>? comparer = _comparer;
uint hashCode = (uint)((comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key));
- int collisionCount = 0;
+ uint collisionCount = 0;
ref int bucket = ref _buckets[hashCode % (uint)_buckets.Length];
// Value in _buckets is 1-based
int i = bucket - 1;
}
i = entries[i].next;
- if (collisionCount >= entries.Length)
+
+ collisionCount++;
+ if (collisionCount > (uint)entries.Length)
{
// The chain of entries forms a loop; which means a concurrent update has happened.
// Break out of the loop and throw, rather than looping forever.
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}
- collisionCount++;
}
}
else
}
i = entries[i].next;
- if (collisionCount >= entries.Length)
+
+ collisionCount++;
+ if (collisionCount > (uint)entries.Length)
{
// The chain of entries forms a loop; which means a concurrent update has happened.
// Break out of the loop and throw, rather than looping forever.
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}
- collisionCount++;
}
}
}
}
i = entries[i].next;
- if (collisionCount >= entries.Length)
+
+ collisionCount++;
+ if (collisionCount > (uint)entries.Length)
{
// The chain of entries forms a loop; which means a concurrent update has happened.
// Break out of the loop and throw, rather than looping forever.
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}
- collisionCount++;
}
}
int[]? buckets = _buckets;
Entry[]? entries = _entries;
- int collisionCount = 0;
if (buckets != null)
{
Debug.Assert(entries != null, "entries should be non-null");
+ uint collisionCount = 0;
uint hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode());
uint bucket = hashCode % (uint)buckets.Length;
int last = -1;
last = i;
i = entry.next;
- if (collisionCount >= entries.Length)
+
+ collisionCount++;
+ if (collisionCount > (uint)entries.Length)
{
// The chain of entries forms a loop; which means a concurrent update has happened.
// Break out of the loop and throw, rather than looping forever.
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}
- collisionCount++;
}
}
return false;
int[]? buckets = _buckets;
Entry[]? entries = _entries;
- int collisionCount = 0;
if (buckets != null)
{
Debug.Assert(entries != null, "entries should be non-null");
+ uint collisionCount = 0;
uint hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode());
uint bucket = hashCode % (uint)buckets.Length;
int last = -1;
last = i;
i = entry.next;
- if (collisionCount >= entries.Length)
+
+ collisionCount++;
+ if (collisionCount > (uint)entries.Length)
{
// The chain of entries forms a loop; which means a concurrent update has happened.
// Break out of the loop and throw, rather than looping forever.
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}
- collisionCount++;
}
}
value = default!;
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
- int i = FindEntry(key);
- if (i >= 0)
+ ref TValue valRef = ref FindValue(key);
+ if (!Unsafe.IsNullRef(ref valRef))
{
- value = _entries![i].value;
+ value = valRef;
return true;
}
value = default!;
{
if (IsCompatibleKey(key))
{
- int i = FindEntry((TKey)key);
- if (i >= 0)
+ ref TValue value = ref FindValue((TKey)key);
+ if (!Unsafe.IsNullRef(ref value))
{
- return _entries![i].value;
+ return value;
}
}
return null;