Skip to main content

rustc_data_structures/
vec_cache.rs

1//! VecCache maintains a mapping from K -> (V, I) pairing. K and I must be roughly u32-sized, and V
2//! must be Copy.
3//!
4//! VecCache supports efficient concurrent put/get across the key space, with write-once semantics
5//! (i.e., a given key can only be put once). Subsequent puts will panic.
6//!
7//! This is currently used for query caching.
8
9use std::fmt::{self, Debug};
10use std::marker::PhantomData;
11use std::ops::{Index, IndexMut};
12use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicUsize, Ordering};
13
14use rustc_index::Idx;
15
16#[cfg(test)]
17mod tests;
18
19struct Slot<V> {
20    // We never construct &Slot<V> so it's fine for this to not be in an UnsafeCell.
21    value: V,
22    // This is both an index and a once-lock.
23    //
24    // 0: not yet initialized.
25    // 1: lock held, initializing.
26    // 2..u32::MAX - 2: initialized.
27    index_and_lock: AtomicU32,
28}
29
30/// This uniquely identifies a single `Slot<V>` entry in the buckets map, and provides accessors for
31/// either getting the value or putting a value.
32#[derive(#[automatically_derived]
impl ::core::marker::Copy for SlotIndex { }Copy, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for SlotIndex { }
#[automatically_derived]
impl ::core::clone::Clone for SlotIndex {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<BucketIndex>;
        let _: ::core::clone::AssertParamIsClone<usize>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for SlotIndex {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "SlotIndex",
            "bucket_idx", &self.bucket_idx, "index_in_bucket",
            &&self.index_in_bucket)
    }
}Debug)]
33struct SlotIndex {
34    // the index of the bucket in VecCache (0 to 20)
35    bucket_idx: BucketIndex,
36    // the index of the slot within the bucket
37    index_in_bucket: usize,
38}
39
40// This makes sure the counts are consistent with what we allocate, precomputing each bucket a
41// compile-time. Visiting all powers of two is enough to hit all the buckets.
42//
43// We confirm counts are accurate in the slot_index_exhaustive test.
44const ENTRIES_BY_BUCKET: [usize; BUCKETS] = {
45    let mut entries = [0; BUCKETS];
46    let mut key = 0;
47    loop {
48        let si = SlotIndex::from_index(key);
49        entries[si.bucket_idx.to_usize()] = si.bucket_idx.capacity();
50        if key == 0 {
51            key = 1;
52        } else if key == (1 << 31) {
53            break;
54        } else {
55            key <<= 1;
56        }
57    }
58    entries
59};
60
61const BUCKETS: usize = 21;
62
63impl SlotIndex {
64    /// Unpacks a flat 32-bit index into a [`BucketIndex`] and a slot offset within that bucket.
65    #[inline]
66    const fn from_index(idx: u32) -> Self {
67        let (bucket_idx, index_in_bucket) = BucketIndex::from_flat_index(idx as usize);
68        SlotIndex { bucket_idx, index_in_bucket }
69    }
70
71    // SAFETY: Buckets must be managed solely by functions here (i.e., get/put on SlotIndex) and
72    // `self` comes from SlotIndex::from_index
73    #[inline]
74    unsafe fn get<V: Copy>(&self, buckets: &[AtomicPtr<Slot<V>>; 21]) -> Option<(V, u32)> {
75        let bucket = &buckets[self.bucket_idx];
76        let ptr = bucket.load(Ordering::Acquire);
77        // Bucket is not yet initialized: then we obviously won't find this entry in that bucket.
78        if ptr.is_null() {
79            return None;
80        }
81        if true {
    if !(self.index_in_bucket < self.bucket_idx.capacity()) {
        ::core::panicking::panic("assertion failed: self.index_in_bucket < self.bucket_idx.capacity()")
    };
};debug_assert!(self.index_in_bucket < self.bucket_idx.capacity());
82        // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this
83        // must be inbounds.
84        let slot = unsafe { ptr.add(self.index_in_bucket) };
85
86        // SAFETY: initialized bucket has zeroed all memory within the bucket, so we are valid for
87        // AtomicU32 access.
88        let index_and_lock = unsafe { &(*slot).index_and_lock };
89        let current = index_and_lock.load(Ordering::Acquire);
90        let index = match current {
91            0 => return None,
92            // Treat "initializing" as actually just not initialized at all.
93            // The only reason this is a separate state is that `complete` calls could race and
94            // we can't allow that, but from load perspective there's no difference.
95            1 => return None,
96            _ => current - 2,
97        };
98
99        // SAFETY:
100        // * slot is a valid pointer (buckets are always valid for the index we get).
101        // * value is initialized since we saw a >= 2 index above.
102        // * `V: Copy`, so safe to read.
103        let value = unsafe { (*slot).value };
104        Some((value, index))
105    }
106
107    fn bucket_ptr<V>(&self, bucket: &AtomicPtr<Slot<V>>) -> *mut Slot<V> {
108        let ptr = bucket.load(Ordering::Acquire);
109        if ptr.is_null() { Self::initialize_bucket(bucket, self.bucket_idx) } else { ptr }
110    }
111
112    #[cold]
113    #[inline(never)]
114    fn initialize_bucket<V>(bucket: &AtomicPtr<Slot<V>>, bucket_idx: BucketIndex) -> *mut Slot<V> {
115        static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
116
117        // If we are initializing the bucket, then acquire a global lock.
118        //
119        // This path is quite cold, so it's cheap to use a global lock. This ensures that we never
120        // have multiple allocations for the same bucket.
121        let _allocator_guard = LOCK.lock().unwrap_or_else(|e| e.into_inner());
122
123        let ptr = bucket.load(Ordering::Acquire);
124
125        // OK, now under the allocator lock, if we're still null then it's definitely us that will
126        // initialize this bucket.
127        if ptr.is_null() {
128            let bucket_layout =
129                std::alloc::Layout::array::<Slot<V>>(bucket_idx.capacity()).unwrap();
130            // This is more of a sanity check -- this code is very cold, so it's safe to pay a
131            // little extra cost here.
132            if !(bucket_layout.size() > 0) {
    ::core::panicking::panic("assertion failed: bucket_layout.size() > 0")
};assert!(bucket_layout.size() > 0);
133            // SAFETY: Just checked that size is non-zero.
134            let allocated = unsafe { std::alloc::alloc_zeroed(bucket_layout).cast::<Slot<V>>() };
135            if allocated.is_null() {
136                std::alloc::handle_alloc_error(bucket_layout);
137            }
138            bucket.store(allocated, Ordering::Release);
139            allocated
140        } else {
141            // Otherwise some other thread initialized this bucket after we took the lock. In that
142            // case, just return early.
143            ptr
144        }
145    }
146
147    /// Returns true if this successfully put into the map.
148    #[inline]
149    fn put<V>(&self, buckets: &[AtomicPtr<Slot<V>>; 21], value: V, extra: u32) -> bool {
150        let bucket = &buckets[self.bucket_idx];
151        let ptr = self.bucket_ptr(bucket);
152
153        if true {
    if !(self.index_in_bucket < self.bucket_idx.capacity()) {
        ::core::panicking::panic("assertion failed: self.index_in_bucket < self.bucket_idx.capacity()")
    };
};debug_assert!(self.index_in_bucket < self.bucket_idx.capacity());
154        // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this
155        // must be inbounds.
156        let slot = unsafe { ptr.add(self.index_in_bucket) };
157
158        // SAFETY: initialized bucket has zeroed all memory within the bucket, so we are valid for
159        // AtomicU32 access.
160        let index_and_lock = unsafe { &(*slot).index_and_lock };
161        match index_and_lock.compare_exchange(0, 1, Ordering::AcqRel, Ordering::Acquire) {
162            Ok(_) => {
163                // We have acquired the initialization lock. It is our job to write `value` and
164                // then set the lock to the real index.
165
166                unsafe {
167                    (&raw mut (*slot).value).write(value);
168                }
169
170                index_and_lock.store(extra.checked_add(2).unwrap(), Ordering::Release);
171
172                true
173            }
174
175            // Treat "initializing" as the caller's fault. Callers are responsible for ensuring that
176            // there are no races on initialization. In the compiler's current usage for query
177            // caches, that's the "active query map" which ensures each query actually runs once
178            // (even if concurrently started).
179            Err(1) => { ::core::panicking::panic_fmt(format_args!("caller raced calls to put()")); }panic!("caller raced calls to put()"),
180
181            // This slot was already populated. Also ignore, currently this is the same as
182            // "initializing".
183            Err(_) => false,
184        }
185    }
186
187    /// Inserts into the map, given that the slot is unique, so it won't race with other threads.
188    #[inline]
189    unsafe fn put_unique<V>(&self, buckets: &[AtomicPtr<Slot<V>>; 21], value: V, extra: u32) {
190        let bucket = &buckets[self.bucket_idx];
191        let ptr = self.bucket_ptr(bucket);
192
193        if true {
    if !(self.index_in_bucket < self.bucket_idx.capacity()) {
        ::core::panicking::panic("assertion failed: self.index_in_bucket < self.bucket_idx.capacity()")
    };
};debug_assert!(self.index_in_bucket < self.bucket_idx.capacity());
194        // SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this
195        // must be inbounds.
196        let slot = unsafe { ptr.add(self.index_in_bucket) };
197
198        // SAFETY: We known our slot is unique as a precondition of this function, so this can't race.
199        unsafe {
200            (&raw mut (*slot).value).write(value);
201        }
202
203        // SAFETY: initialized bucket has zeroed all memory within the bucket, so we are valid for
204        // AtomicU32 access.
205        let index_and_lock = unsafe { &(*slot).index_and_lock };
206
207        index_and_lock.store(extra.checked_add(2).unwrap(), Ordering::Release);
208    }
209}
210
211/// In-memory cache for queries whose keys are densely-numbered IDs
212/// (e.g `CrateNum`, `LocalDefId`), and can therefore be used as indices
213/// into a dense vector of cached values.
214///
215/// (As of [#124780] the underlying storage is not an actual `Vec`, but rather
216/// a series of increasingly-large buckets, for improved performance when the
217/// parallel frontend is using multiple threads.)
218///
219/// Each entry in the cache stores the query's return value (`V`), and also
220/// an associated index (`I`), which in practice is a `DepNodeIndex` used for
221/// query dependency tracking.
222///
223/// [#124780]: https://github.com/rust-lang/rust/pull/124780
224pub struct VecCache<K: Idx, V, I> {
225    // Entries per bucket:
226    // Bucket  0:       4096 2^12
227    // Bucket  1:       4096 2^12
228    // Bucket  2:       8192
229    // Bucket  3:      16384
230    // ...
231    // Bucket 19: 1073741824
232    // Bucket 20: 2147483648
233    // The total number of entries if all buckets are initialized is 2^32.
234    buckets: [AtomicPtr<Slot<V>>; BUCKETS],
235
236    // In the compiler's current usage these are only *read* during incremental and self-profiling.
237    // They are an optimization over iterating the full buckets array.
238    present: [AtomicPtr<Slot<()>>; BUCKETS],
239    // Whether we are in a mode where we need to track `present`; if not, we leave it empty
240    track_present: bool,
241    len: AtomicUsize,
242
243    key: PhantomData<(K, I)>,
244}
245
246impl<K: Idx, V, I> Default for VecCache<K, V, I> {
247    fn default() -> Self {
248        VecCache {
249            buckets: Default::default(),
250            key: PhantomData,
251            len: Default::default(),
252            present: Default::default(),
253            track_present: true,
254        }
255    }
256}
257
258impl<K: Idx, V, I> VecCache<K, V, I> {
259    pub fn without_track_present() -> Self {
260        let mut this = VecCache::default();
261        this.track_present = false;
262        this
263    }
264}
265
266// SAFETY: No access to `V` is made.
267unsafe impl<K: Idx, #[may_dangle] V, I> Drop for VecCache<K, V, I> {
268    fn drop(&mut self) {
269        // We have unique ownership, so no locks etc. are needed. Since `K` and `V` are both `Copy`,
270        // we are also guaranteed to just need to deallocate any large arrays (not iterate over
271        // contents).
272        //
273        // Confirm no need to deallocate individual entries. Note that `V: Copy` is asserted on
274        // insert/lookup but not necessarily construction, primarily to avoid annoyingly propagating
275        // the bounds into struct definitions everywhere.
276        if !!std::mem::needs_drop::<K>() {
    ::core::panicking::panic("assertion failed: !std::mem::needs_drop::<K>()")
};assert!(!std::mem::needs_drop::<K>());
277        if !!std::mem::needs_drop::<V>() {
    ::core::panicking::panic("assertion failed: !std::mem::needs_drop::<V>()")
};assert!(!std::mem::needs_drop::<V>());
278
279        for (idx, bucket) in BucketIndex::enumerate_buckets(&self.buckets) {
280            let bucket = bucket.load(Ordering::Acquire);
281            if !bucket.is_null() {
282                let layout = std::alloc::Layout::array::<Slot<V>>(ENTRIES_BY_BUCKET[idx]).unwrap();
283                unsafe {
284                    std::alloc::dealloc(bucket.cast(), layout);
285                }
286            }
287        }
288
289        if self.track_present {
290            for (idx, bucket) in BucketIndex::enumerate_buckets(&self.present) {
291                let bucket = bucket.load(Ordering::Acquire);
292                if !bucket.is_null() {
293                    let layout =
294                        std::alloc::Layout::array::<Slot<()>>(ENTRIES_BY_BUCKET[idx]).unwrap();
295                    unsafe {
296                        std::alloc::dealloc(bucket.cast(), layout);
297                    }
298                }
299            }
300        }
301    }
302}
303
304impl<K, V, I> VecCache<K, V, I>
305where
306    K: Eq + Idx + Copy + Debug,
307    V: Copy,
308    I: Idx + Copy,
309{
310    #[inline(always)]
311    pub fn lookup(&self, key: &K) -> Option<(V, I)> {
312        let key = u32::try_from(key.index()).unwrap();
313        let slot_idx = SlotIndex::from_index(key);
314        match unsafe { slot_idx.get(&self.buckets) } {
315            Some((value, idx)) => Some((value, I::new(idx as usize))),
316            None => None,
317        }
318    }
319
320    #[inline]
321    pub fn complete(&self, key: K, value: V, index: I) {
322        let key = u32::try_from(key.index()).unwrap();
323        let slot_idx = SlotIndex::from_index(key);
324        if slot_idx.put(&self.buckets, value, index.index() as u32) && self.track_present {
325            let present_idx = self.len.fetch_add(1, Ordering::Relaxed);
326            let slot = SlotIndex::from_index(u32::try_from(present_idx).unwrap());
327            // SAFETY: We should always be uniquely putting due to `len` fetch_add returning unique values.
328            // We can't get here if `len` overflows because `put` will not succeed u32::MAX + 1 times
329            // as it will run out of slots.
330            unsafe { slot.put_unique(&self.present, (), key) };
331        }
332    }
333
334    pub fn for_each(&self, f: &mut dyn FnMut(&K, &V, I)) {
335        if !self.track_present {
    {
        ::core::panicking::panic_fmt(format_args!("VecCache::for_each without track_present"));
    }
};assert!(self.track_present, "VecCache::for_each without track_present");
336        for idx in 0..self.len.load(Ordering::Acquire) {
337            let key = SlotIndex::from_index(idx as u32);
338            match unsafe { key.get(&self.present) } {
339                // This shouldn't happen in our current usage (iter is really only
340                // used long after queries are done running), but if we hit this in practice it's
341                // probably fine to just break early.
342                None => ::core::panicking::unreachable_default_msg()unreachable!(),
343                Some(((), key)) => {
344                    let key = K::new(key as usize);
345                    // unwrap() is OK: present entries are always written only after we put the real
346                    // entry.
347                    let value = self.lookup(&key).unwrap();
348                    f(&key, &value.0, value.1);
349                }
350            }
351        }
352    }
353
354    pub fn len(&self) -> usize {
355        if !self.track_present {
    {
        ::core::panicking::panic_fmt(format_args!("VecCache::len without track_present"));
    }
};assert!(self.track_present, "VecCache::len without track_present");
356        self.len.load(Ordering::Acquire)
357    }
358}
359
360/// Index into an array of buckets.
361///
362/// Using an enum lets us tell the compiler that values range from 0 to 20,
363/// allowing array bounds checks to be optimized away,
364/// without having to resort to pattern types or other unstable features.
365#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for BucketIndex { }
#[automatically_derived]
impl ::core::clone::Clone for BucketIndex {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for BucketIndex { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for BucketIndex { }
#[automatically_derived]
impl ::core::cmp::PartialEq for BucketIndex {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for BucketIndex { }Eq)]
366#[repr(usize)]
367enum BucketIndex {
368    // tidy-alphabetical-start
369    Bucket00,
370    Bucket01,
371    Bucket02,
372    Bucket03,
373    Bucket04,
374    Bucket05,
375    Bucket06,
376    Bucket07,
377    Bucket08,
378    Bucket09,
379    Bucket10,
380    Bucket11,
381    Bucket12,
382    Bucket13,
383    Bucket14,
384    Bucket15,
385    Bucket16,
386    Bucket17,
387    Bucket18,
388    Bucket19,
389    Bucket20,
390    // tidy-alphabetical-end
391}
392
393impl Debug for BucketIndex {
394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395        Debug::fmt(&self.to_usize(), f)
396    }
397}
398
399impl BucketIndex {
400    /// Capacity of bucket 0 (and also of bucket 1).
401    const BUCKET_0_CAPACITY: usize = 1 << (Self::NONZERO_BUCKET_SHIFT_ADJUST + 1);
402    /// Adjustment factor from the highest-set-bit-position of a flat index,
403    /// to its corresponding bucket number.
404    ///
405    /// For example, the first flat-index in bucket 2 is 8192.
406    /// Its highest-set-bit-position is `(8192).ilog2() == 13`, and subtracting
407    /// the adjustment factor of 11 gives the bucket number of 2.
408    const NONZERO_BUCKET_SHIFT_ADJUST: usize = 11;
409
410    #[inline(always)]
411    const fn to_usize(self) -> usize {
412        self as usize
413    }
414
415    #[inline(always)]
416    const fn from_raw(raw: usize) -> Self {
417        match raw {
418            // tidy-alphabetical-start
419            00 => Self::Bucket00,
420            01 => Self::Bucket01,
421            02 => Self::Bucket02,
422            03 => Self::Bucket03,
423            04 => Self::Bucket04,
424            05 => Self::Bucket05,
425            06 => Self::Bucket06,
426            07 => Self::Bucket07,
427            08 => Self::Bucket08,
428            09 => Self::Bucket09,
429            10 => Self::Bucket10,
430            11 => Self::Bucket11,
431            12 => Self::Bucket12,
432            13 => Self::Bucket13,
433            14 => Self::Bucket14,
434            15 => Self::Bucket15,
435            16 => Self::Bucket16,
436            17 => Self::Bucket17,
437            18 => Self::Bucket18,
438            19 => Self::Bucket19,
439            20 => Self::Bucket20,
440            // tidy-alphabetical-end
441            _ => { ::core::panicking::panic_fmt(format_args!("bucket index out of range")); }panic!("bucket index out of range"),
442        }
443    }
444
445    /// Total number of slots in this bucket.
446    #[inline(always)]
447    const fn capacity(self) -> usize {
448        match self {
449            Self::Bucket00 => Self::BUCKET_0_CAPACITY,
450            // Bucket 1 has a capacity of `1 << (1 + 11) == pow(2, 12) == 4096`.
451            // Bucket 2 has a capacity of `1 << (2 + 11) == pow(2, 13) == 8192`.
452            _ => 1 << (self.to_usize() + Self::NONZERO_BUCKET_SHIFT_ADJUST),
453        }
454    }
455
456    /// Converts a flat index in the range `0..=u32::MAX` into a bucket index,
457    /// and a slot offset within that bucket.
458    ///
459    /// Panics if `flat > u32::MAX`.
460    #[inline(always)]
461    const fn from_flat_index(flat: usize) -> (Self, usize) {
462        if flat > u32::MAX as usize {
463            ::core::panicking::panic_default_msg();panic!();
464        }
465
466        // If the index is in bucket 0, the conversion is trivial.
467        // This also avoids calling `ilog2` when `flat == 0`.
468        if flat < Self::BUCKET_0_CAPACITY {
469            return (Self::Bucket00, flat);
470        }
471
472        // General-case conversion for a non-zero bucket index.
473        //
474        //              | bucket |   slot
475        // flat | ilog2 |  index | offset
476        // ------------------------------
477        // 4096 |    12 |      1 |      0
478        // 4097 |    12 |      1 |      1
479        // ...
480        // 8191 |    12 |      1 |   4095
481        // 8192 |    13 |      2 |      0
482        let highest_bit_pos = flat.ilog2() as usize;
483        let bucket_index =
484            BucketIndex::from_raw(highest_bit_pos - Self::NONZERO_BUCKET_SHIFT_ADJUST);
485
486        // Clear the highest-set bit (which selects the bucket) to get the
487        // slot offset within this bucket.
488        let slot_offset = flat - (1 << highest_bit_pos);
489
490        (bucket_index, slot_offset)
491    }
492
493    #[inline(always)]
494    fn iter_all() -> impl ExactSizeIterator<Item = Self> {
495        (0usize..BUCKETS).map(BucketIndex::from_raw)
496    }
497
498    #[inline(always)]
499    fn enumerate_buckets<T>(buckets: &[T; BUCKETS]) -> impl ExactSizeIterator<Item = (Self, &T)> {
500        BucketIndex::iter_all().zip(buckets)
501    }
502}
503
504impl<T> Index<BucketIndex> for [T; BUCKETS] {
505    type Output = T;
506
507    #[inline(always)]
508    fn index(&self, index: BucketIndex) -> &Self::Output {
509        // The optimizer should be able to see that see that a bucket index is
510        // always in-bounds, and omit the runtime bounds check.
511        &self[index.to_usize()]
512    }
513}
514
515impl<T> IndexMut<BucketIndex> for [T; BUCKETS] {
516    #[inline(always)]
517    fn index_mut(&mut self, index: BucketIndex) -> &mut Self::Output {
518        &mut self[index.to_usize()]
519    }
520}