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.
89use std::fmt::{self, Debug};
10use std::marker::PhantomData;
11use std::ops::{Index, IndexMut};
12use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicUsize, Ordering};
1314use rustc_index::Idx;
1516#[cfg(test)]
17mod tests;
1819struct Slot<V> {
20// We never construct &Slot<V> so it's fine for this to not be in an UnsafeCell.
21value: 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.
27index_and_lock: AtomicU32,
28}
2930/// 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)
35bucket_idx: BucketIndex,
36// the index of the slot within the bucket
37index_in_bucket: usize,
38}
3940// 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] = {
45let mut entries = [0; BUCKETS];
46let mut key = 0;
47loop {
48let si = SlotIndex::from_index(key);
49entries[si.bucket_idx.to_usize()] = si.bucket_idx.capacity();
50if key == 0 {
51key = 1;
52 } else if key == (1 << 31) {
53break;
54 } else {
55key <<= 1;
56 }
57 }
58entries59};
6061const BUCKETS: usize = 21;
6263impl SlotIndex {
64/// Unpacks a flat 32-bit index into a [`BucketIndex`] and a slot offset within that bucket.
65#[inline]
66const fn from_index(idx: u32) -> Self {
67let (bucket_idx, index_in_bucket) = BucketIndex::from_flat_index(idxas usize);
68SlotIndex { bucket_idx, index_in_bucket }
69 }
7071// 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]
74unsafe fn get<V: Copy>(&self, buckets: &[AtomicPtr<Slot<V>>; 21]) -> Option<(V, u32)> {
75let bucket = &buckets[self.bucket_idx];
76let ptr = bucket.load(Ordering::Acquire);
77// Bucket is not yet initialized: then we obviously won't find this entry in that bucket.
78if ptr.is_null() {
79return None;
80 }
81if 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.
84let slot = unsafe { ptr.add(self.index_in_bucket) };
8586// SAFETY: initialized bucket has zeroed all memory within the bucket, so we are valid for
87 // AtomicU32 access.
88let index_and_lock = unsafe { &(*slot).index_and_lock };
89let current = index_and_lock.load(Ordering::Acquire);
90let index = match current {
910 => 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.
951 => return None,
96_ => current - 2,
97 };
9899// 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.
103let value = unsafe { (*slot).value };
104Some((value, index))
105 }
106107fn bucket_ptr<V>(&self, bucket: &AtomicPtr<Slot<V>>) -> *mut Slot<V> {
108let ptr = bucket.load(Ordering::Acquire);
109if ptr.is_null() { Self::initialize_bucket(bucket, self.bucket_idx) } else { ptr }
110 }
111112#[cold]
113 #[inline(never)]
114fn initialize_bucket<V>(bucket: &AtomicPtr<Slot<V>>, bucket_idx: BucketIndex) -> *mut Slot<V> {
115static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
116117// 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.
121let _allocator_guard = LOCK.lock().unwrap_or_else(|e| e.into_inner());
122123let ptr = bucket.load(Ordering::Acquire);
124125// OK, now under the allocator lock, if we're still null then it's definitely us that will
126 // initialize this bucket.
127if ptr.is_null() {
128let 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.
132if !(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.
134let allocated = unsafe { std::alloc::alloc_zeroed(bucket_layout).cast::<Slot<V>>() };
135if allocated.is_null() {
136 std::alloc::handle_alloc_error(bucket_layout);
137 }
138bucket.store(allocated, Ordering::Release);
139allocated140 } else {
141// Otherwise some other thread initialized this bucket after we took the lock. In that
142 // case, just return early.
143ptr144 }
145 }
146147/// Returns true if this successfully put into the map.
148#[inline]
149fn put<V>(&self, buckets: &[AtomicPtr<Slot<V>>; 21], value: V, extra: u32) -> bool {
150let bucket = &buckets[self.bucket_idx];
151let ptr = self.bucket_ptr(bucket);
152153if 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.
156let slot = unsafe { ptr.add(self.index_in_bucket) };
157158// SAFETY: initialized bucket has zeroed all memory within the bucket, so we are valid for
159 // AtomicU32 access.
160let index_and_lock = unsafe { &(*slot).index_and_lock };
161match index_and_lock.compare_exchange(0, 1, Ordering::AcqRel, Ordering::Acquire) {
162Ok(_) => {
163// We have acquired the initialization lock. It is our job to write `value` and
164 // then set the lock to the real index.
165166unsafe {
167 (&raw mut (*slot).value).write(value);
168 }
169170index_and_lock.store(extra.checked_add(2).unwrap(), Ordering::Release);
171172true
173}
174175// 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).
179Err(1) => { ::core::panicking::panic_fmt(format_args!("caller raced calls to put()")); }panic!("caller raced calls to put()"),
180181// This slot was already populated. Also ignore, currently this is the same as
182 // "initializing".
183Err(_) => false,
184 }
185 }
186187/// Inserts into the map, given that the slot is unique, so it won't race with other threads.
188#[inline]
189unsafe fn put_unique<V>(&self, buckets: &[AtomicPtr<Slot<V>>; 21], value: V, extra: u32) {
190let bucket = &buckets[self.bucket_idx];
191let ptr = self.bucket_ptr(bucket);
192193if 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.
196let slot = unsafe { ptr.add(self.index_in_bucket) };
197198// SAFETY: We known our slot is unique as a precondition of this function, so this can't race.
199unsafe {
200 (&raw mut (*slot).value).write(value);
201 }
202203// SAFETY: initialized bucket has zeroed all memory within the bucket, so we are valid for
204 // AtomicU32 access.
205let index_and_lock = unsafe { &(*slot).index_and_lock };
206207index_and_lock.store(extra.checked_add(2).unwrap(), Ordering::Release);
208 }
209}
210211/// 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.
234buckets: [AtomicPtr<Slot<V>>; BUCKETS],
235236// 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.
238present: [AtomicPtr<Slot<()>>; BUCKETS],
239// Whether we are in a mode where we need to track `present`; if not, we leave it empty
240track_present: bool,
241 len: AtomicUsize,
242243 key: PhantomData<(K, I)>,
244}
245246impl<K: Idx, V, I> Defaultfor VecCache<K, V, I> {
247fn default() -> Self {
248VecCache {
249 buckets: Default::default(),
250 key: PhantomData,
251 len: Default::default(),
252 present: Default::default(),
253 track_present: true,
254 }
255 }
256}
257258impl<K: Idx, V, I> VecCache<K, V, I> {
259pub fn without_track_present() -> Self {
260let mut this = VecCache::default();
261this.track_present = false;
262this263 }
264}
265266// SAFETY: No access to `V` is made.
267unsafe impl<K: Idx, #[may_dangle] V, I> Dropfor VecCache<K, V, I> {
268fn 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.
276if !!std::mem::needs_drop::<K>() {
::core::panicking::panic("assertion failed: !std::mem::needs_drop::<K>()")
};assert!(!std::mem::needs_drop::<K>());
277if !!std::mem::needs_drop::<V>() {
::core::panicking::panic("assertion failed: !std::mem::needs_drop::<V>()")
};assert!(!std::mem::needs_drop::<V>());
278279for (idx, bucket) in BucketIndex::enumerate_buckets(&self.buckets) {
280let bucket = bucket.load(Ordering::Acquire);
281if !bucket.is_null() {
282let layout = std::alloc::Layout::array::<Slot<V>>(ENTRIES_BY_BUCKET[idx]).unwrap();
283unsafe {
284 std::alloc::dealloc(bucket.cast(), layout);
285 }
286 }
287 }
288289if self.track_present {
290for (idx, bucket) in BucketIndex::enumerate_buckets(&self.present) {
291let bucket = bucket.load(Ordering::Acquire);
292if !bucket.is_null() {
293let layout =
294 std::alloc::Layout::array::<Slot<()>>(ENTRIES_BY_BUCKET[idx]).unwrap();
295unsafe {
296 std::alloc::dealloc(bucket.cast(), layout);
297 }
298 }
299 }
300 }
301 }
302}
303304impl<K, V, I> VecCache<K, V, I>
305where
306K: Eq + Idx + Copy + Debug,
307 V: Copy,
308 I: Idx + Copy,
309{
310#[inline(always)]
311pub fn lookup(&self, key: &K) -> Option<(V, I)> {
312let key = u32::try_from(key.index()).unwrap();
313let slot_idx = SlotIndex::from_index(key);
314match unsafe { slot_idx.get(&self.buckets) } {
315Some((value, idx)) => Some((value, I::new(idxas usize))),
316None => None,
317 }
318 }
319320#[inline]
321pub fn complete(&self, key: K, value: V, index: I) {
322let key = u32::try_from(key.index()).unwrap();
323let slot_idx = SlotIndex::from_index(key);
324if slot_idx.put(&self.buckets, value, index.index() as u32) && self.track_present {
325let present_idx = self.len.fetch_add(1, Ordering::Relaxed);
326let 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.
330unsafe { slot.put_unique(&self.present, (), key) };
331 }
332 }
333334pub fn for_each(&self, f: &mut dyn FnMut(&K, &V, I)) {
335if !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");
336for idx in 0..self.len.load(Ordering::Acquire) {
337let key = SlotIndex::from_index(idx as u32);
338match 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.
342None => ::core::panicking::unreachable_default_msg()unreachable!(),
343Some(((), key)) => {
344let key = K::new(key as usize);
345// unwrap() is OK: present entries are always written only after we put the real
346 // entry.
347let value = self.lookup(&key).unwrap();
348 f(&key, &value.0, value.1);
349 }
350 }
351 }
352 }
353354pub fn len(&self) -> usize {
355if !self.track_present {
{
::core::panicking::panic_fmt(format_args!("VecCache::len without track_present"));
}
};assert!(self.track_present, "VecCache::len without track_present");
356self.len.load(Ordering::Acquire)
357 }
358}
359360/// 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
369Bucket00,
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}
392393impl Debugfor BucketIndex {
394fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395 Debug::fmt(&self.to_usize(), f)
396 }
397}
398399impl BucketIndex {
400/// Capacity of bucket 0 (and also of bucket 1).
401const 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.
408const NONZERO_BUCKET_SHIFT_ADJUST: usize = 11;
409410#[inline(always)]
411const fn to_usize(self) -> usize {
412selfas usize413 }
414415#[inline(always)]
416const fn from_raw(raw: usize) -> Self {
417match raw {
418// tidy-alphabetical-start
41900 => Self::Bucket00,
42001 => Self::Bucket01,
42102 => Self::Bucket02,
42203 => Self::Bucket03,
42304 => Self::Bucket04,
42405 => Self::Bucket05,
42506 => Self::Bucket06,
42607 => Self::Bucket07,
42708 => Self::Bucket08,
42809 => Self::Bucket09,
42910 => Self::Bucket10,
43011 => Self::Bucket11,
43112 => Self::Bucket12,
43213 => Self::Bucket13,
43314 => Self::Bucket14,
43415 => Self::Bucket15,
43516 => Self::Bucket16,
43617 => Self::Bucket17,
43718 => Self::Bucket18,
43819 => Self::Bucket19,
43920 => 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 }
444445/// Total number of slots in this bucket.
446#[inline(always)]
447const fn capacity(self) -> usize {
448match self {
449Self::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 }
455456/// 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)]
461const fn from_flat_index(flat: usize) -> (Self, usize) {
462if flat > u32::MAXas usize {
463::core::panicking::panic_default_msg();panic!();
464 }
465466// If the index is in bucket 0, the conversion is trivial.
467 // This also avoids calling `ilog2` when `flat == 0`.
468if flat < Self::BUCKET_0_CAPACITY {
469return (Self::Bucket00, flat);
470 }
471472// 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
482let highest_bit_pos = flat.ilog2() as usize;
483let bucket_index =
484BucketIndex::from_raw(highest_bit_pos - Self::NONZERO_BUCKET_SHIFT_ADJUST);
485486// Clear the highest-set bit (which selects the bucket) to get the
487 // slot offset within this bucket.
488let slot_offset = flat - (1 << highest_bit_pos);
489490 (bucket_index, slot_offset)
491 }
492493#[inline(always)]
494fn iter_all() -> impl ExactSizeIterator<Item = Self> {
495 (0usize..BUCKETS).map(BucketIndex::from_raw)
496 }
497498#[inline(always)]
499fn enumerate_buckets<T>(buckets: &[T; BUCKETS]) -> impl ExactSizeIterator<Item = (Self, &T)> {
500BucketIndex::iter_all().zip(buckets)
501 }
502}
503504impl<T> Index<BucketIndex> for [T; BUCKETS] {
505type Output = T;
506507#[inline(always)]
508fn 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}
514515impl<T> IndexMut<BucketIndex> for [T; BUCKETS] {
516#[inline(always)]
517fn index_mut(&mut self, index: BucketIndex) -> &mut Self::Output {
518&mut self[index.to_usize()]
519 }
520}