Skip to content

Commit 93ff34b

Browse files
Auto merge of #142277 - Mark-Simulacrum:active-query-by-idx, r=<try>
Key active queries by usize rather than storing raw keys Starting with perf run to evaluate impact -- my hope is that this helps somewhat with rustc compile times, since it should allow de-duplicating a bunch of `HashTable<K, ...>` codegen since it's now ~always `HashTable<usize, ...>` -- more can be done by merging the DefaultCache's active + non-active maps too. Locally I don't really see any win from this though, so it's possible it's wasted effort -- want to see what perf says about it. r? ghost
2 parents c6768de + ebd893a commit 93ff34b

File tree

8 files changed

+212
-70
lines changed

8 files changed

+212
-70
lines changed

compiler/rustc_data_structures/src/sharded.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,17 @@ impl<K: Eq + Hash, V> ShardedHashMap<K, V> {
199199
}
200200
}
201201
}
202+
203+
#[inline]
204+
pub fn remove(&self, key: K) -> Option<V> {
205+
let hash = make_hash(&key);
206+
let mut shard = self.lock_shard_by_hash(hash);
207+
208+
match table_entry(&mut shard, hash, &key) {
209+
Entry::Occupied(e) => Some(e.remove().0.1),
210+
Entry::Vacant(_) => None,
211+
}
212+
}
202213
}
203214

204215
impl<K: Eq + Hash + Copy> ShardedHashMap<K, ()> {

compiler/rustc_data_structures/src/vec_cache.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
99
use std::fmt::Debug;
1010
use std::marker::PhantomData;
11+
use std::ptr::NonNull;
1112
use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicUsize, Ordering};
1213

1314
use rustc_index::Idx;
@@ -56,6 +57,8 @@ const ENTRIES_BY_BUCKET: [usize; 21] = {
5657
entries
5758
};
5859

60+
const FIRST_BUCKET_SHIFT: usize = 12;
61+
5962
impl SlotIndex {
6063
// This unpacks a flat u32 index into identifying which bucket it belongs to and the offset
6164
// within that bucket. As noted in the VecCache docs, buckets double in size with each index.
@@ -68,7 +71,6 @@ impl SlotIndex {
6871
// slots (see `slot_index_exhaustive` in tests).
6972
#[inline]
7073
const fn from_index(idx: u32) -> Self {
71-
const FIRST_BUCKET_SHIFT: usize = 12;
7274
if idx < (1 << FIRST_BUCKET_SHIFT) {
7375
return SlotIndex {
7476
bucket_idx: 0,
@@ -86,6 +88,15 @@ impl SlotIndex {
8688
}
8789
}
8890

91+
fn to_key<K: Idx>(&self) -> K {
92+
let idx = if self.bucket_idx == 0 {
93+
self.index_in_bucket
94+
} else {
95+
(1 << (self.bucket_idx + FIRST_BUCKET_SHIFT - 1)) + self.index_in_bucket
96+
};
97+
K::new(idx)
98+
}
99+
89100
// SAFETY: Buckets must be managed solely by functions here (i.e., get/put on SlotIndex) and
90101
// `self` comes from SlotIndex::from_index
91102
#[inline]
@@ -124,6 +135,22 @@ impl SlotIndex {
124135
Some((value, index))
125136
}
126137

138+
// SAFETY: Buckets must be managed solely by functions here (i.e., get/put on SlotIndex) and
139+
// `self` comes from SlotIndex::from_index
140+
#[inline]
141+
unsafe fn addr<V>(&self, buckets: &[AtomicPtr<Slot<V>>; 21]) -> usize {
142+
// SAFETY: `bucket_idx` is ilog2(u32).saturating_sub(11), which is at most 21, i.e.,
143+
// in-bounds of buckets. See `from_index` for computation.
144+
let bucket = unsafe { buckets.get_unchecked(self.bucket_idx) };
145+
let ptr = self.bucket_ptr(bucket);
146+
assert!(self.index_in_bucket < self.entries);
147+
// SAFETY: `bucket` was allocated (so <= isize in total bytes) to hold `entries`, so this
148+
// must be inbounds.
149+
let slot = unsafe { ptr.add(self.index_in_bucket) };
150+
151+
slot.addr()
152+
}
153+
127154
fn bucket_ptr<V>(&self, bucket: &AtomicPtr<Slot<V>>) -> *mut Slot<V> {
128155
let ptr = bucket.load(Ordering::Acquire);
129156
if ptr.is_null() { self.initialize_bucket(bucket) } else { ptr }
@@ -331,6 +358,37 @@ where
331358
}
332359
}
333360
}
361+
362+
pub fn to_slot_address(&self, key: &K) -> usize {
363+
let key = u32::try_from(key.index()).unwrap();
364+
let slot_idx = SlotIndex::from_index(key);
365+
// SAFETY: Same safety as lookup.
366+
unsafe { slot_idx.addr(&self.buckets) }
367+
}
368+
369+
pub fn to_key(&self, slot_addr: usize) -> K {
370+
for (bucket_idx, bucket) in self.buckets.iter().enumerate() {
371+
let Some(bucket_ptr) = NonNull::new(bucket.load(Ordering::Acquire)) else {
372+
continue;
373+
};
374+
let entries = ENTRIES_BY_BUCKET[bucket_idx];
375+
376+
// Are we in this bucket?
377+
if !(bucket_ptr.as_ptr().addr() <= slot_addr
378+
&& slot_addr <= bucket_ptr.as_ptr().wrapping_add(entries).addr())
379+
{
380+
continue;
381+
}
382+
383+
let index_in_bucket =
384+
(slot_addr - bucket_ptr.as_ptr().addr()) / std::mem::size_of::<Slot<V>>();
385+
let slot_idx = SlotIndex { bucket_idx, entries, index_in_bucket };
386+
387+
return slot_idx.to_key::<K>();
388+
}
389+
390+
unreachable!()
391+
}
334392
}
335393

336394
#[cfg(test)]

compiler/rustc_middle/src/query/plumbing.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ macro_rules! define_callbacks {
491491
#[derive(Default)]
492492
pub struct QueryStates<'tcx> {
493493
$(
494-
pub $name: QueryState<$($K)*, QueryStackDeferred<'tcx>>,
494+
pub $name: QueryState<QueryStackDeferred<'tcx>>,
495495
)*
496496
}
497497

compiler/rustc_query_impl/src/lib.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,7 @@ where
8383
}
8484

8585
#[inline(always)]
86-
fn query_state<'a>(
87-
self,
88-
qcx: QueryCtxt<'tcx>,
89-
) -> &'a QueryState<Self::Key, QueryStackDeferred<'tcx>>
86+
fn query_state<'a>(self, qcx: QueryCtxt<'tcx>) -> &'a QueryState<QueryStackDeferred<'tcx>>
9087
where
9188
QueryCtxt<'tcx>: 'a,
9289
{
@@ -95,7 +92,7 @@ where
9592
unsafe {
9693
&*(&qcx.tcx.query_system.states as *const QueryStates<'tcx>)
9794
.byte_add(self.dynamic.query_state)
98-
.cast::<QueryState<Self::Key, QueryStackDeferred<'tcx>>>()
95+
.cast::<QueryState<QueryStackDeferred<'tcx>>>()
9996
}
10097
}
10198

compiler/rustc_query_impl/src/plumbing.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,9 +735,10 @@ macro_rules! define_queries {
735735
tcx: TyCtxt<'tcx>,
736736
qmap: &mut QueryMap<QueryStackDeferred<'tcx>>,
737737
) -> Option<()> {
738-
let make_query = |tcx, key| {
738+
let make_query = |tcx: TyCtxt<'tcx>, key| {
739739
let kind = rustc_middle::dep_graph::dep_kinds::$name;
740740
let name = stringify!($name);
741+
let key = tcx.query_system.caches.$name.to_key(key);
741742
$crate::plumbing::create_query_frame(tcx, rustc_middle::query::descs::$name, key, kind, name)
742743
};
743744
let res = tcx.query_system.states.$name.try_collect_active_jobs(

compiler/rustc_query_system/src/query/caches.rs

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::fmt::Debug;
22
use std::hash::Hash;
3-
use std::sync::OnceLock;
3+
use std::sync::{Mutex, OnceLock};
44

55
use rustc_data_structures::sharded::ShardedHashMap;
66
pub use rustc_data_structures::vec_cache::VecCache;
@@ -23,6 +23,17 @@ pub trait QueryCache: Sized {
2323
/// given key, if it is present in the cache.
2424
fn lookup(&self, key: &Self::Key) -> Option<(Self::Value, DepNodeIndex)>;
2525

26+
/// Start the job.
27+
///
28+
/// After the first call to `to_unique_index`, the returned index is guaranteed to be
29+
/// uniquely bound to `key` until earlier of `drop(self)` or `complete(key, ...)`.
30+
fn to_unique_index(&self, key: &Self::Key) -> usize;
31+
32+
/// Reverse the mapping of to_unique_index to a key.
33+
///
34+
/// Will panic if called with an index that's not currently available.
35+
fn to_key(&self, idx: usize) -> Self::Key;
36+
2637
/// Adds a key/value entry to this cache.
2738
///
2839
/// Called by some part of the query system, after having obtained the
@@ -36,11 +47,12 @@ pub trait QueryCache: Sized {
3647
/// more specialized kinds of cache. Backed by a sharded hashmap.
3748
pub struct DefaultCache<K, V> {
3849
cache: ShardedHashMap<K, (V, DepNodeIndex)>,
50+
active: Mutex<Vec<Option<K>>>,
3951
}
4052

4153
impl<K, V> Default for DefaultCache<K, V> {
4254
fn default() -> Self {
43-
DefaultCache { cache: Default::default() }
55+
DefaultCache { cache: Default::default(), active: Default::default() }
4456
}
4557
}
4658

@@ -57,11 +69,49 @@ where
5769
self.cache.get(key)
5870
}
5971

72+
fn to_unique_index(&self, key: &Self::Key) -> usize {
73+
let mut guard = self.active.lock().unwrap();
74+
75+
for (idx, slot) in guard.iter_mut().enumerate() {
76+
if let Some(k) = slot
77+
&& k == key
78+
{
79+
// Return idx if we found the slot containing this key.
80+
return idx;
81+
} else if slot.is_none() {
82+
// If slot is empty, reuse it.
83+
*slot = Some(*key);
84+
return idx;
85+
}
86+
}
87+
88+
// If no slot currently contains our key, add a new slot.
89+
let idx = guard.len();
90+
guard.push(Some(*key));
91+
return idx;
92+
}
93+
94+
fn to_key(&self, idx: usize) -> Self::Key {
95+
let guard = self.active.lock().unwrap();
96+
guard[idx].expect("still present")
97+
}
98+
6099
#[inline]
61100
fn complete(&self, key: K, value: V, index: DepNodeIndex) {
62101
// We may be overwriting another value. This is all right, since the dep-graph
63102
// will check that the fingerprint matches.
64103
self.cache.insert(key, (value, index));
104+
105+
// Make sure to do this second -- this ensures lookups return success prior to active
106+
// getting removed, helping avoiding assignment of multiple indices per logical key.
107+
let mut guard = self.active.lock().unwrap();
108+
for slot in guard.iter_mut() {
109+
if let Some(k) = slot
110+
&& *k == key
111+
{
112+
*slot = None;
113+
}
114+
}
65115
}
66116

67117
fn iter(&self, f: &mut dyn FnMut(&Self::Key, &Self::Value, DepNodeIndex)) {
@@ -102,6 +152,15 @@ where
102152
self.cache.set((value, index)).ok();
103153
}
104154

155+
fn to_unique_index(&self, _: &Self::Key) -> usize {
156+
// SingleCache has a single key, so we can map directly to a constant.
157+
0
158+
}
159+
160+
fn to_key(&self, _: usize) -> Self::Key {
161+
()
162+
}
163+
105164
fn iter(&self, f: &mut dyn FnMut(&Self::Key, &Self::Value, DepNodeIndex)) {
106165
if let Some(value) = self.cache.get() {
107166
f(&(), &value.0, value.1)
@@ -142,6 +201,32 @@ where
142201
}
143202
}
144203

204+
fn to_unique_index(&self, key: &Self::Key) -> usize {
205+
if key.krate == LOCAL_CRATE {
206+
// The local cache assigns keys based on allocated addresses in the backing VecCache.
207+
//
208+
// Those addresses are always at least 4-aligned (due to DepNodeIndex), so the low bit
209+
// can't be set. This means these don't overlap with the other cache given we |1 those
210+
// IDs. We check this with the assertion.
211+
let local_idx = self.local.to_unique_index(&key.index);
212+
assert!(local_idx & 1 == 0);
213+
local_idx
214+
} else {
215+
// Shifting is safe because DefaultCache uses only u32 for its indices, so we won't
216+
// overflow here.
217+
(self.foreign.to_unique_index(key) << 1) | 1
218+
}
219+
}
220+
221+
fn to_key(&self, idx: usize) -> Self::Key {
222+
if idx & 1 == 0 {
223+
// local
224+
DefId::local(self.local.to_key(idx))
225+
} else {
226+
self.foreign.to_key(idx >> 1)
227+
}
228+
}
229+
145230
#[inline]
146231
fn complete(&self, key: DefId, value: V, index: DepNodeIndex) {
147232
if key.krate == LOCAL_CRATE {
@@ -180,4 +265,12 @@ where
180265
fn iter(&self, f: &mut dyn FnMut(&Self::Key, &Self::Value, DepNodeIndex)) {
181266
self.iter(f)
182267
}
268+
269+
fn to_unique_index(&self, key: &Self::Key) -> usize {
270+
self.to_slot_address(key)
271+
}
272+
273+
fn to_key(&self, idx: usize) -> Self::Key {
274+
self.to_key(idx)
275+
}
183276
}

compiler/rustc_query_system/src/query/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub trait QueryConfig<Qcx: QueryContext>: Copy {
2828
fn format_value(self) -> fn(&Self::Value) -> String;
2929

3030
// Don't use this method to access query results, instead use the methods on TyCtxt
31-
fn query_state<'a>(self, tcx: Qcx) -> &'a QueryState<Self::Key, Qcx::QueryInfo>
31+
fn query_state<'a>(self, tcx: Qcx) -> &'a QueryState<Qcx::QueryInfo>
3232
where
3333
Qcx: 'a;
3434

0 commit comments

Comments
 (0)