66//!
77//! This is currently used for query caching.
88
9- use std:: fmt:: Debug ;
9+ use std:: fmt:: { self , Debug } ;
1010use std:: marker:: PhantomData ;
11+ use std:: ops:: { Index , IndexMut } ;
1112use std:: sync:: atomic:: { AtomicPtr , AtomicU32 , AtomicUsize , Ordering } ;
1213
1314use rustc_index:: Idx ;
1415
16+ #[ cfg( test) ]
17+ mod tests;
18+
1519struct Slot < V > {
1620 // We never construct &Slot<V> so it's fine for this to not be in an UnsafeCell.
1721 value : V ,
@@ -28,7 +32,7 @@ struct Slot<V> {
2832#[ derive( Copy , Clone , Debug ) ]
2933struct SlotIndex {
3034 // the index of the bucket in VecCache (0 to 20)
31- bucket_idx : usize ,
35+ bucket_idx : BucketIndex ,
3236 // the index of the slot within the bucket
3337 index_in_bucket : usize ,
3438}
@@ -42,7 +46,7 @@ const ENTRIES_BY_BUCKET: [usize; BUCKETS] = {
4246 let mut key = 0 ;
4347 loop {
4448 let si = SlotIndex :: from_index ( key) ;
45- entries[ si. bucket_idx ] = si. entries ( ) ;
49+ entries[ si. bucket_idx . to_usize ( ) ] = si. bucket_idx . capacity ( ) ;
4650 if key == 0 {
4751 key = 1 ;
4852 } else if key == ( 1 << 31 ) {
@@ -57,11 +61,6 @@ const ENTRIES_BY_BUCKET: [usize; BUCKETS] = {
5761const BUCKETS : usize = 21 ;
5862
5963impl 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-
6564 // This unpacks a flat u32 index into identifying which bucket it belongs to and the offset
6665 // within that bucket. As noted in the VecCache docs, buckets double in size with each index.
6766 // Typically that would mean 31 buckets (2^0 + 2^1 ... + 2^31 = u32::MAX - 1), but to reduce
@@ -73,32 +72,21 @@ impl SlotIndex {
7372 // slots (see `slot_index_exhaustive` in tests).
7473 #[ inline]
7574 const fn from_index ( idx : u32 ) -> Self {
76- const FIRST_BUCKET_SHIFT : usize = 12 ;
77- if idx < ( 1 << FIRST_BUCKET_SHIFT ) {
78- return SlotIndex { bucket_idx : 0 , index_in_bucket : idx as usize } ;
79- }
80- // We already ruled out idx 0, so this `ilog2` never panics (and the check optimizes away)
81- let bucket = idx. ilog2 ( ) as usize ;
82- let entries = 1 << bucket;
83- SlotIndex {
84- bucket_idx : bucket - FIRST_BUCKET_SHIFT + 1 ,
85- index_in_bucket : idx as usize - entries,
86- }
75+ let ( bucket_idx, index_in_bucket) = BucketIndex :: from_flat_index ( idx as usize ) ;
76+ SlotIndex { bucket_idx, index_in_bucket }
8777 }
8878
8979 // SAFETY: Buckets must be managed solely by functions here (i.e., get/put on SlotIndex) and
9080 // `self` comes from SlotIndex::from_index
9181 #[ inline]
9282 unsafe fn get < V : Copy > ( & self , buckets : & [ AtomicPtr < Slot < V > > ; 21 ] ) -> Option < ( V , u32 ) > {
93- // SAFETY: `bucket_idx` is ilog2(u32).saturating_sub(11), which is at most 21, i.e.,
94- // in-bounds of buckets. See `from_index` for computation.
95- let bucket = unsafe { buckets. get_unchecked ( self . bucket_idx ) } ;
83+ let bucket = & buckets[ self . bucket_idx ] ;
9684 let ptr = bucket. load ( Ordering :: Acquire ) ;
9785 // Bucket is not yet initialized: then we obviously won't find this entry in that bucket.
9886 if ptr. is_null ( ) {
9987 return None ;
10088 }
101- debug_assert ! ( self . index_in_bucket < self . entries ( ) ) ;
89+ debug_assert ! ( self . index_in_bucket < self . bucket_idx . capacity ( ) ) ;
10290 // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this
10391 // must be inbounds.
10492 let slot = unsafe { ptr. add ( self . index_in_bucket ) } ;
@@ -131,7 +119,7 @@ impl SlotIndex {
131119
132120 #[ cold]
133121 #[ inline( never) ]
134- fn initialize_bucket < V > ( bucket : & AtomicPtr < Slot < V > > , bucket_idx : usize ) -> * mut Slot < V > {
122+ fn initialize_bucket < V > ( bucket : & AtomicPtr < Slot < V > > , bucket_idx : BucketIndex ) -> * mut Slot < V > {
135123 static LOCK : std:: sync:: Mutex < ( ) > = std:: sync:: Mutex :: new ( ( ) ) ;
136124
137125 // If we are initializing the bucket, then acquire a global lock.
@@ -145,8 +133,8 @@ impl SlotIndex {
145133 // OK, now under the allocator lock, if we're still null then it's definitely us that will
146134 // initialize this bucket.
147135 if ptr. is_null ( ) {
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 ( ) ;
136+ let bucket_layout =
137+ std:: alloc:: Layout :: array :: < Slot < V > > ( bucket_idx . capacity ( ) ) . unwrap ( ) ;
150138 // This is more of a sanity check -- this code is very cold, so it's safe to pay a
151139 // little extra cost here.
152140 assert ! ( bucket_layout. size( ) > 0 ) ;
@@ -167,12 +155,10 @@ impl SlotIndex {
167155 /// Returns true if this successfully put into the map.
168156 #[ inline]
169157 fn put < V > ( & self , buckets : & [ AtomicPtr < Slot < V > > ; 21 ] , value : V , extra : u32 ) -> bool {
170- // SAFETY: `bucket_idx` is ilog2(u32).saturating_sub(11), which is at most 21, i.e.,
171- // in-bounds of buckets.
172- let bucket = unsafe { buckets. get_unchecked ( self . bucket_idx ) } ;
158+ let bucket = & buckets[ self . bucket_idx ] ;
173159 let ptr = self . bucket_ptr ( bucket) ;
174160
175- debug_assert ! ( self . index_in_bucket < self . entries ( ) ) ;
161+ debug_assert ! ( self . index_in_bucket < self . bucket_idx . capacity ( ) ) ;
176162 // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this
177163 // must be inbounds.
178164 let slot = unsafe { ptr. add ( self . index_in_bucket ) } ;
@@ -209,12 +195,10 @@ impl SlotIndex {
209195 /// Inserts into the map, given that the slot is unique, so it won't race with other threads.
210196 #[ inline]
211197 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 ) } ;
198+ let bucket = & buckets[ self . bucket_idx ] ;
215199 let ptr = self . bucket_ptr ( bucket) ;
216200
217- debug_assert ! ( self . index_in_bucket < self . entries ( ) ) ;
201+ debug_assert ! ( self . index_in_bucket < self . bucket_idx . capacity ( ) ) ;
218202 // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this
219203 // must be inbounds.
220204 let slot = unsafe { ptr. add ( self . index_in_bucket ) } ;
@@ -289,7 +273,7 @@ unsafe impl<K: Idx, #[may_dangle] V, I> Drop for VecCache<K, V, I> {
289273 assert ! ( !std:: mem:: needs_drop:: <K >( ) ) ;
290274 assert ! ( !std:: mem:: needs_drop:: <V >( ) ) ;
291275
292- for ( idx, bucket) in self . buckets . iter ( ) . enumerate ( ) {
276+ for ( idx, bucket) in BucketIndex :: enumerate_buckets ( & self . buckets ) {
293277 let bucket = bucket. load ( Ordering :: Acquire ) ;
294278 if !bucket. is_null ( ) {
295279 let layout = std:: alloc:: Layout :: array :: < Slot < V > > ( ENTRIES_BY_BUCKET [ idx] ) . unwrap ( ) ;
@@ -299,7 +283,7 @@ unsafe impl<K: Idx, #[may_dangle] V, I> Drop for VecCache<K, V, I> {
299283 }
300284 }
301285
302- for ( idx, bucket) in self . present . iter ( ) . enumerate ( ) {
286+ for ( idx, bucket) in BucketIndex :: enumerate_buckets ( & self . present ) {
303287 let bucket = bucket. load ( Ordering :: Acquire ) ;
304288 if !bucket. is_null ( ) {
305289 let layout = std:: alloc:: Layout :: array :: < Slot < ( ) > > ( ENTRIES_BY_BUCKET [ idx] ) . unwrap ( ) ;
@@ -365,5 +349,163 @@ where
365349 }
366350}
367351
368- #[ cfg( test) ]
369- mod tests;
352+ /// Index into an array of buckets.
353+ ///
354+ /// Using an enum lets us tell the compiler that values range from 0 to 20,
355+ /// without having to resort to pattern types or other unstable features.
356+ #[ derive( Clone , Copy , PartialEq , Eq ) ]
357+ #[ repr( usize ) ]
358+ enum BucketIndex {
359+ // tidy-alphabetical-start
360+ Bucket00 ,
361+ Bucket01 ,
362+ Bucket02 ,
363+ Bucket03 ,
364+ Bucket04 ,
365+ Bucket05 ,
366+ Bucket06 ,
367+ Bucket07 ,
368+ Bucket08 ,
369+ Bucket09 ,
370+ Bucket10 ,
371+ Bucket11 ,
372+ Bucket12 ,
373+ Bucket13 ,
374+ Bucket14 ,
375+ Bucket15 ,
376+ Bucket16 ,
377+ Bucket17 ,
378+ Bucket18 ,
379+ Bucket19 ,
380+ Bucket20 ,
381+ // tidy-alphabetical-end
382+ }
383+
384+ impl Debug for BucketIndex {
385+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
386+ Debug :: fmt ( & self . to_usize ( ) , f)
387+ }
388+ }
389+
390+ impl BucketIndex {
391+ /// Capacity of bucket 0 (and also of bucket 1).
392+ const BUCKET_0_CAPACITY : usize = 1 << ( Self :: NONZERO_BUCKET_SHIFT_ADJUST + 1 ) ;
393+ /// Adjustment factor from the highest-set-bit-position of a flat index,
394+ /// to its corresponding bucket number.
395+ ///
396+ /// For example, the first flat-index in bucket 2 is 8192.
397+ /// Its highest-set-bit-position is `(8192).ilog2() == 13`, and subtracting
398+ /// the adjustment factor of 11 gives the bucket number of 2.
399+ const NONZERO_BUCKET_SHIFT_ADJUST : usize = 11 ;
400+
401+ #[ inline( always) ]
402+ const fn to_usize ( self ) -> usize {
403+ self as usize
404+ }
405+
406+ #[ inline( always) ]
407+ const fn from_raw ( raw : usize ) -> Self {
408+ match raw {
409+ // tidy-alphabetical-start
410+ 00 => Self :: Bucket00 ,
411+ 01 => Self :: Bucket01 ,
412+ 02 => Self :: Bucket02 ,
413+ 03 => Self :: Bucket03 ,
414+ 04 => Self :: Bucket04 ,
415+ 05 => Self :: Bucket05 ,
416+ 06 => Self :: Bucket06 ,
417+ 07 => Self :: Bucket07 ,
418+ 08 => Self :: Bucket08 ,
419+ 09 => Self :: Bucket09 ,
420+ 10 => Self :: Bucket10 ,
421+ 11 => Self :: Bucket11 ,
422+ 12 => Self :: Bucket12 ,
423+ 13 => Self :: Bucket13 ,
424+ 14 => Self :: Bucket14 ,
425+ 15 => Self :: Bucket15 ,
426+ 16 => Self :: Bucket16 ,
427+ 17 => Self :: Bucket17 ,
428+ 18 => Self :: Bucket18 ,
429+ 19 => Self :: Bucket19 ,
430+ 20 => Self :: Bucket20 ,
431+ // tidy-alphabetical-end
432+ _ => panic ! ( "bucket index out of range" ) ,
433+ }
434+ }
435+
436+ /// Total number of slots in this bucket.
437+ #[ inline( always) ]
438+ const fn capacity ( self ) -> usize {
439+ match self {
440+ Self :: Bucket00 => Self :: BUCKET_0_CAPACITY ,
441+ // Bucket 1 has a capacity of `1 << (1 + 11) == pow(2, 12) == 4096`.
442+ // Bucket 2 has a capacity of `1 << (2 + 11) == pow(2, 13) == 8192`.
443+ _ => 1 << ( self . to_usize ( ) + Self :: NONZERO_BUCKET_SHIFT_ADJUST ) ,
444+ }
445+ }
446+
447+ /// Converts a flat index in the range `0..=u32::MAX` into a bucket index,
448+ /// and a slot offset within that bucket.
449+ ///
450+ /// Panics if `flat > u32::MAX`.
451+ #[ inline( always) ]
452+ const fn from_flat_index ( flat : usize ) -> ( Self , usize ) {
453+ if flat > u32:: MAX as usize {
454+ panic ! ( ) ;
455+ }
456+
457+ // If the index is in bucket 0, the conversion is trivial.
458+ // This also avoids calling `ilog2` when `flat == 0`.
459+ if flat < Self :: BUCKET_0_CAPACITY {
460+ return ( Self :: Bucket00 , flat) ;
461+ }
462+
463+ // General-case conversion for a non-zero bucket index.
464+ //
465+ // | bucket | slot
466+ // flat | ilog2 | index | offset
467+ // ------------------------------
468+ // 4096 | 12 | 1 | 0
469+ // 4097 | 12 | 1 | 1
470+ // ...
471+ // 8191 | 12 | 1 | 4095
472+ // 8192 | 13 | 2 | 0
473+ let highest_bit_pos = flat. ilog2 ( ) as usize ;
474+ let bucket_index =
475+ BucketIndex :: from_raw ( highest_bit_pos - Self :: NONZERO_BUCKET_SHIFT_ADJUST ) ;
476+
477+ // Clear the highest-set bit (which selects the bucket) to get the
478+ // slot offset within this bucket.
479+ let slot_offset = flat & !( 1 << highest_bit_pos) ;
480+
481+ ( bucket_index, slot_offset)
482+ }
483+
484+ #[ inline( always) ]
485+ fn iter_all ( ) -> impl ExactSizeIterator < Item = Self > {
486+ ( 0usize ..BUCKETS ) . map ( BucketIndex :: from_raw)
487+ }
488+
489+ #[ inline( always) ]
490+ fn enumerate_buckets < T > ( buckets : & [ T ; BUCKETS ] ) -> impl ExactSizeIterator < Item = ( Self , & T ) > {
491+ BucketIndex :: iter_all ( ) . zip ( buckets)
492+ }
493+ }
494+
495+ impl < T > Index < BucketIndex > for [ T ; BUCKETS ] {
496+ type Output = T ;
497+
498+ #[ inline( always) ]
499+ fn index ( & self , index : BucketIndex ) -> & Self :: Output {
500+ // The optimizer should be able to see that see that a bucket index is
501+ // always in-bounds, and omit the runtime bounds check.
502+ & self [ index. to_usize ( ) ]
503+ }
504+ }
505+
506+ impl < T > IndexMut < BucketIndex > for [ T ; BUCKETS ] {
507+ #[ inline( always) ]
508+ fn index_mut ( & mut self , index : BucketIndex ) -> & mut Self :: Output {
509+ & mut self [ index. to_usize ( ) ]
510+ }
511+ }
0 commit comments