@@ -29,8 +29,6 @@ struct Slot<V> {
2929struct SlotIndex {
3030 // the index of the bucket in VecCache (0 to 20)
3131 bucket_idx : usize ,
32- // number of entries in that bucket
33- entries : usize ,
3432 // the index of the slot within the bucket
3533 index_in_bucket : usize ,
3634}
@@ -39,12 +37,12 @@ struct SlotIndex {
3937// compile-time. Visiting all powers of two is enough to hit all the buckets.
4038//
4139// We confirm counts are accurate in the slot_index_exhaustive test.
42- const ENTRIES_BY_BUCKET : [ usize ; 21 ] = {
43- let mut entries = [ 0 ; 21 ] ;
40+ const ENTRIES_BY_BUCKET : [ usize ; BUCKETS ] = {
41+ let mut entries = [ 0 ; BUCKETS ] ;
4442 let mut key = 0 ;
4543 loop {
4644 let si = SlotIndex :: from_index ( key) ;
47- entries[ si. bucket_idx ] = si. entries ;
45+ entries[ si. bucket_idx ] = si. entries ( ) ;
4846 if key == 0 {
4947 key = 1 ;
5048 } else if key == ( 1 << 31 ) {
@@ -56,7 +54,14 @@ const ENTRIES_BY_BUCKET: [usize; 21] = {
5654 entries
5755} ;
5856
57+ const BUCKETS : usize = 21 ;
58+
5959impl SlotIndex {
60+ /// The total possible number of entries in the bucket
61+ const fn entries ( & self ) -> usize {
62+ if self . bucket_idx == 0 { 1 << 12 } else { 1 << ( self . bucket_idx + 11 ) }
63+ }
64+
6065 // This unpacks a flat u32 index into identifying which bucket it belongs to and the offset
6166 // within that bucket. As noted in the VecCache docs, buckets double in size with each index.
6267 // Typically that would mean 31 buckets (2^0 + 2^1 ... + 2^31 = u32::MAX - 1), but to reduce
@@ -70,18 +75,13 @@ impl SlotIndex {
7075 const fn from_index ( idx : u32 ) -> Self {
7176 const FIRST_BUCKET_SHIFT : usize = 12 ;
7277 if idx < ( 1 << FIRST_BUCKET_SHIFT ) {
73- return SlotIndex {
74- bucket_idx : 0 ,
75- entries : 1 << FIRST_BUCKET_SHIFT ,
76- index_in_bucket : idx as usize ,
77- } ;
78+ return SlotIndex { bucket_idx : 0 , index_in_bucket : idx as usize } ;
7879 }
7980 // We already ruled out idx 0, so this `ilog2` never panics (and the check optimizes away)
8081 let bucket = idx. ilog2 ( ) as usize ;
8182 let entries = 1 << bucket;
8283 SlotIndex {
8384 bucket_idx : bucket - FIRST_BUCKET_SHIFT + 1 ,
84- entries,
8585 index_in_bucket : idx as usize - entries,
8686 }
8787 }
@@ -98,7 +98,7 @@ impl SlotIndex {
9898 if ptr. is_null ( ) {
9999 return None ;
100100 }
101- assert ! ( self . index_in_bucket < self . entries) ;
101+ debug_assert ! ( self . index_in_bucket < self . entries( ) ) ;
102102 // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this
103103 // must be inbounds.
104104 let slot = unsafe { ptr. add ( self . index_in_bucket ) } ;
@@ -126,11 +126,12 @@ impl SlotIndex {
126126
127127 fn bucket_ptr < V > ( & self , bucket : & AtomicPtr < Slot < V > > ) -> * mut Slot < V > {
128128 let ptr = bucket. load ( Ordering :: Acquire ) ;
129- if ptr. is_null ( ) { self . initialize_bucket ( bucket) } else { ptr }
129+ if ptr. is_null ( ) { Self :: initialize_bucket ( bucket, self . bucket_idx ) } else { ptr }
130130 }
131131
132132 #[ cold]
133- fn initialize_bucket < V > ( & self , bucket : & AtomicPtr < Slot < V > > ) -> * mut Slot < V > {
133+ #[ inline( never) ]
134+ fn initialize_bucket < V > ( bucket : & AtomicPtr < Slot < V > > , bucket_idx : usize ) -> * mut Slot < V > {
134135 static LOCK : std:: sync:: Mutex < ( ) > = std:: sync:: Mutex :: new ( ( ) ) ;
135136
136137 // If we are initializing the bucket, then acquire a global lock.
@@ -144,8 +145,8 @@ impl SlotIndex {
144145 // OK, now under the allocator lock, if we're still null then it's definitely us that will
145146 // initialize this bucket.
146147 if ptr. is_null ( ) {
147- let bucket_layout =
148- std:: alloc:: Layout :: array :: < Slot < V > > ( self . entries as usize ) . unwrap ( ) ;
148+ let bucket_len = SlotIndex { bucket_idx , index_in_bucket : 0 } . entries ( ) ;
149+ let bucket_layout = std:: alloc:: Layout :: array :: < Slot < V > > ( bucket_len ) . unwrap ( ) ;
149150 // This is more of a sanity check -- this code is very cold, so it's safe to pay a
150151 // little extra cost here.
151152 assert ! ( bucket_layout. size( ) > 0 ) ;
@@ -171,7 +172,7 @@ impl SlotIndex {
171172 let bucket = unsafe { buckets. get_unchecked ( self . bucket_idx ) } ;
172173 let ptr = self . bucket_ptr ( bucket) ;
173174
174- assert ! ( self . index_in_bucket < self . entries) ;
175+ debug_assert ! ( self . index_in_bucket < self . entries( ) ) ;
175176 // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this
176177 // must be inbounds.
177178 let slot = unsafe { ptr. add ( self . index_in_bucket ) } ;
@@ -204,6 +205,31 @@ impl SlotIndex {
204205 Err ( _) => false ,
205206 }
206207 }
208+
209+ /// Inserts into the map, given that the slot is unique, so it won't race with other threads.
210+ #[ inline]
211+ unsafe fn put_unique < V > ( & self , buckets : & [ AtomicPtr < Slot < V > > ; 21 ] , value : V , extra : u32 ) {
212+ // SAFETY: `bucket_idx` is ilog2(u32).saturating_sub(11), which is at most 21, i.e.,
213+ // in-bounds of buckets.
214+ let bucket = unsafe { buckets. get_unchecked ( self . bucket_idx ) } ;
215+ let ptr = self . bucket_ptr ( bucket) ;
216+
217+ debug_assert ! ( self . index_in_bucket < self . entries( ) ) ;
218+ // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this
219+ // must be inbounds.
220+ let slot = unsafe { ptr. add ( self . index_in_bucket ) } ;
221+
222+ // SAFETY: We known our slot is unique as a precondition of this function, so this can't race.
223+ unsafe {
224+ ( & raw mut ( * slot) . value ) . write ( value) ;
225+ }
226+
227+ // SAFETY: initialized bucket has zeroed all memory within the bucket, so we are valid for
228+ // AtomicU32 access.
229+ let index_and_lock = unsafe { & ( * slot) . index_and_lock } ;
230+
231+ index_and_lock. store ( extra. checked_add ( 2 ) . unwrap ( ) , Ordering :: Release ) ;
232+ }
207233}
208234
209235/// In-memory cache for queries whose keys are densely-numbered IDs
@@ -229,11 +255,11 @@ pub struct VecCache<K: Idx, V, I> {
229255 // Bucket 19: 1073741824
230256 // Bucket 20: 2147483648
231257 // The total number of entries if all buckets are initialized is u32::MAX-1.
232- buckets : [ AtomicPtr < Slot < V > > ; 21 ] ,
258+ buckets : [ AtomicPtr < Slot < V > > ; BUCKETS ] ,
233259
234260 // In the compiler's current usage these are only *read* during incremental and self-profiling.
235261 // They are an optimization over iterating the full buckets array.
236- present : [ AtomicPtr < Slot < ( ) > > ; 21 ] ,
262+ present : [ AtomicPtr < Slot < ( ) > > ; BUCKETS ] ,
237263 len : AtomicUsize ,
238264
239265 key : PhantomData < ( K , I ) > ,
@@ -307,9 +333,11 @@ where
307333 let slot_idx = SlotIndex :: from_index ( key) ;
308334 if slot_idx. put ( & self . buckets , value, index. index ( ) as u32 ) {
309335 let present_idx = self . len . fetch_add ( 1 , Ordering :: Relaxed ) ;
310- let slot = SlotIndex :: from_index ( present_idx as u32 ) ;
311- // We should always be uniquely putting due to `len` fetch_add returning unique values.
312- assert ! ( slot. put( & self . present, ( ) , key) ) ;
336+ let slot = SlotIndex :: from_index ( u32:: try_from ( present_idx) . unwrap ( ) ) ;
337+ // SAFETY: We should always be uniquely putting due to `len` fetch_add returning unique values.
338+ // We can't get here if `len` overflows because `put` will not succeed u32::MAX + 1 times
339+ // as it will run out of slots.
340+ unsafe { slot. put_unique ( & self . present , ( ) , key) } ;
313341 }
314342 }
315343
0 commit comments