@@ -51,6 +51,9 @@ private struct Entry
5151
5252 private int [ ] ? _buckets ;
5353 private Entry [ ] ? _entries ;
54+ #if BIT64
55+ private ulong _fastModMultiplier ;
56+ #endif
5457 private int _count ;
5558 private int _freeList ;
5659 private int _freeCount ;
@@ -330,16 +333,15 @@ private ref TValue FindValue(TKey key)
330333 ThrowHelper . ThrowArgumentNullException ( ExceptionArgument . key ) ;
331334 }
332335
333- int [ ] ? buckets = _buckets ;
334336 ref Entry entry = ref Unsafe . NullRef < Entry > ( ) ;
335- if ( buckets != null )
337+ if ( _buckets != null )
336338 {
337339 Debug . Assert ( _entries != null , "expected entries to be != null" ) ;
338340 IEqualityComparer < TKey > ? comparer = _comparer ;
339341 if ( comparer == null )
340342 {
341343 uint hashCode = ( uint ) key . GetHashCode ( ) ;
342- int i = buckets [ hashCode % ( uint ) buckets . Length ] ;
344+ int i = GetBucket ( hashCode ) ;
343345 Entry [ ] ? entries = _entries ;
344346 uint collisionCount = 0 ;
345347 if ( default ( TKey ) ! != null ) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
@@ -407,7 +409,7 @@ private ref TValue FindValue(TKey key)
407409 else
408410 {
409411 uint hashCode = ( uint ) comparer . GetHashCode ( key ) ;
410- int i = buckets [ hashCode % ( uint ) buckets . Length ] ;
412+ int i = GetBucket ( hashCode ) ;
411413 Entry [ ] ? entries = _entries ;
412414 uint collisionCount = 0 ;
413415 // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
@@ -453,10 +455,16 @@ private ref TValue FindValue(TKey key)
453455 private int Initialize ( int capacity )
454456 {
455457 int size = HashHelpers . GetPrime ( capacity ) ;
458+ int [ ] buckets = new int [ size ] ;
459+ Entry [ ] entries = new Entry [ size ] ;
456460
461+ // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails
457462 _freeList = - 1 ;
458- _buckets = new int [ size ] ;
459- _entries = new Entry [ size ] ;
463+ #if BIT64
464+ _fastModMultiplier = HashHelpers . GetFastModMultiplier ( ( uint ) size ) ;
465+ #endif
466+ _buckets = buckets ;
467+ _entries = entries ;
460468
461469 return size ;
462470 }
@@ -481,7 +489,7 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
481489 uint hashCode = ( uint ) ( ( comparer == null ) ? key . GetHashCode ( ) : comparer . GetHashCode ( key ) ) ;
482490
483491 uint collisionCount = 0 ;
484- ref int bucket = ref _buckets [ hashCode % ( uint ) _buckets . Length ] ;
492+ ref int bucket = ref GetBucket ( hashCode ) ;
485493 // Value in _buckets is 1-based
486494 int i = bucket - 1 ;
487495
@@ -625,7 +633,7 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
625633 if ( count == entries . Length )
626634 {
627635 Resize ( ) ;
628- bucket = ref _buckets [ hashCode % ( uint ) _buckets . Length ] ;
636+ bucket = ref GetBucket ( hashCode ) ;
629637 }
630638 index = count ;
631639 _count = count + 1 ;
@@ -716,7 +724,6 @@ private void Resize(int newSize, bool forceNewHashCodes)
716724 Debug . Assert ( _entries != null , "_entries should be non-null" ) ;
717725 Debug . Assert ( newSize >= _entries . Length ) ;
718726
719- int [ ] buckets = new int [ newSize ] ;
720727 Entry [ ] entries = new Entry [ newSize ] ;
721728
722729 int count = _count ;
@@ -734,19 +741,23 @@ private void Resize(int newSize, bool forceNewHashCodes)
734741 }
735742 }
736743
744+ // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails
745+ _buckets = new int [ newSize ] ;
746+ #if BIT64
747+ _fastModMultiplier = HashHelpers . GetFastModMultiplier ( ( uint ) newSize ) ;
748+ #endif
737749 for ( int i = 0 ; i < count ; i ++ )
738750 {
739751 if ( entries [ i ] . next >= - 1 )
740752 {
741- uint bucket = entries [ i ] . hashCode % ( uint ) newSize ;
753+ ref int bucket = ref GetBucket ( entries [ i ] . hashCode ) ;
742754 // Value in _buckets is 1-based
743- entries [ i ] . next = buckets [ bucket ] - 1 ;
755+ entries [ i ] . next = bucket - 1 ;
744756 // Value in _buckets is 1-based
745- buckets [ bucket ] = i + 1 ;
757+ bucket = i + 1 ;
746758 }
747759 }
748760
749- _buckets = buckets ;
750761 _entries = entries ;
751762 }
752763
@@ -760,17 +771,16 @@ public bool Remove(TKey key)
760771 ThrowHelper . ThrowArgumentNullException ( ExceptionArgument . key ) ;
761772 }
762773
763- int [ ] ? buckets = _buckets ;
764- Entry [ ] ? entries = _entries ;
765- if ( buckets != null )
774+ if ( _buckets != null )
766775 {
767- Debug . Assert ( entries != null , "entries should be non-null" ) ;
776+ Debug . Assert ( _entries != null , "entries should be non-null" ) ;
768777 uint collisionCount = 0 ;
769778 uint hashCode = ( uint ) ( _comparer ? . GetHashCode ( key ) ?? key . GetHashCode ( ) ) ;
770- uint bucket = hashCode % ( uint ) buckets . Length ;
779+ ref int bucket = ref GetBucket ( hashCode ) ;
780+ Entry [ ] ? entries = _entries ;
771781 int last = - 1 ;
772782 // Value in buckets is 1-based
773- int i = buckets [ bucket ] - 1 ;
783+ int i = bucket - 1 ;
774784 while ( i >= 0 )
775785 {
776786 ref Entry entry = ref entries [ i ] ;
@@ -780,7 +790,7 @@ public bool Remove(TKey key)
780790 if ( last < 0 )
781791 {
782792 // Value in buckets is 1-based
783- buckets [ bucket ] = entry . next + 1 ;
793+ bucket = entry . next + 1 ;
784794 }
785795 else
786796 {
@@ -829,17 +839,16 @@ public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value)
829839 ThrowHelper . ThrowArgumentNullException ( ExceptionArgument . key ) ;
830840 }
831841
832- int [ ] ? buckets = _buckets ;
833- Entry [ ] ? entries = _entries ;
834- if ( buckets != null )
842+ if ( _buckets != null )
835843 {
836- Debug . Assert ( entries != null , "entries should be non-null" ) ;
844+ Debug . Assert ( _entries != null , "entries should be non-null" ) ;
837845 uint collisionCount = 0 ;
838846 uint hashCode = ( uint ) ( _comparer ? . GetHashCode ( key ) ?? key . GetHashCode ( ) ) ;
839- uint bucket = hashCode % ( uint ) buckets . Length ;
847+ ref int bucket = ref GetBucket ( hashCode ) ;
848+ Entry [ ] ? entries = _entries ;
840849 int last = - 1 ;
841850 // Value in buckets is 1-based
842- int i = buckets [ bucket ] - 1 ;
851+ int i = bucket - 1 ;
843852 while ( i >= 0 )
844853 {
845854 ref Entry entry = ref entries [ i ] ;
@@ -849,7 +858,7 @@ public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value)
849858 if ( last < 0 )
850859 {
851860 // Value in buckets is 1-based
852- buckets [ bucket ] = entry . next + 1 ;
861+ bucket = entry . next + 1 ;
853862 }
854863 else
855864 {
@@ -982,6 +991,7 @@ public int EnsureCapacity(int capacity)
982991 _version ++ ;
983992 if ( _buckets == null )
984993 return Initialize ( capacity ) ;
994+
985995 int newSize = HashHelpers . GetPrime ( capacity ) ;
986996 Resize ( newSize , forceNewHashCodes : false ) ;
987997 return newSize ;
@@ -1011,8 +1021,8 @@ public void TrimExcess(int capacity)
10111021 {
10121022 if ( capacity < Count )
10131023 ThrowHelper . ThrowArgumentOutOfRangeException ( ExceptionArgument . capacity ) ;
1014- int newSize = HashHelpers . GetPrime ( capacity ) ;
10151024
1025+ int newSize = HashHelpers . GetPrime ( capacity ) ;
10161026 Entry [ ] ? oldEntries = _entries ;
10171027 int currentCapacity = oldEntries == null ? 0 : oldEntries . Length ;
10181028 if ( newSize >= currentCapacity )
@@ -1022,7 +1032,6 @@ public void TrimExcess(int capacity)
10221032 _version ++ ;
10231033 Initialize ( newSize ) ;
10241034 Entry [ ] ? entries = _entries ;
1025- int [ ] ? buckets = _buckets ;
10261035 int count = 0 ;
10271036 for ( int i = 0 ; i < oldCount ; i ++ )
10281037 {
@@ -1031,11 +1040,11 @@ public void TrimExcess(int capacity)
10311040 {
10321041 ref Entry entry = ref entries ! [ count ] ;
10331042 entry = oldEntries [ i ] ;
1034- uint bucket = hashCode % ( uint ) newSize ;
1043+ ref int bucket = ref GetBucket ( hashCode ) ;
10351044 // Value in _buckets is 1-based
1036- entry . next = buckets ! [ bucket ] - 1 ; // If we get here, we have entries, therefore buckets is not null.
1045+ entry . next = bucket - 1 ;
10371046 // Value in _buckets is 1-based
1038- buckets [ bucket ] = count + 1 ;
1047+ bucket = count + 1 ;
10391048 count ++ ;
10401049 }
10411050 }
@@ -1153,6 +1162,17 @@ void IDictionary.Remove(object key)
11531162 }
11541163 }
11551164
1165+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
1166+ private ref int GetBucket ( uint hashCode )
1167+ {
1168+ int [ ] buckets = _buckets ! ;
1169+ #if BIT64
1170+ return ref buckets [ HashHelpers . FastMod ( hashCode , ( uint ) buckets . Length , _fastModMultiplier ) ] ;
1171+ #else
1172+ return ref buckets [ hashCode % ( uint ) buckets . Length ] ;
1173+ #endif
1174+ }
1175+
11561176 public struct Enumerator : IEnumerator < KeyValuePair < TKey , TValue > > ,
11571177 IDictionaryEnumerator
11581178 {
0 commit comments