salsa/
interned.rs

1use crate::debug::TableEntry;
2use crate::durability::Durability;
3use crate::intern_id::InternId;
4use crate::plumbing::CycleRecoveryStrategy;
5use crate::plumbing::HasQueryGroup;
6use crate::plumbing::QueryStorageMassOps;
7use crate::plumbing::QueryStorageOps;
8use crate::revision::Revision;
9use crate::Query;
10use crate::{Database, DatabaseKeyIndex, QueryDb};
11use parking_lot::RwLock;
12use rustc_hash::FxHashMap;
13use std::collections::hash_map::Entry;
14use std::convert::From;
15use std::fmt::Debug;
16use std::hash::Hash;
17use triomphe::Arc;
18
19const INTERN_DURABILITY: Durability = Durability::HIGH;
20
21/// Handles storage where the value is 'derived' by executing a
22/// function (in contrast to "inputs").
23pub struct InternedStorage<Q>
24where
25    Q: Query,
26    Q::Value: InternKey,
27{
28    group_index: u16,
29    tables: RwLock<InternTables<Q::Key>>,
30}
31
32/// Storage for the looking up interned things.
33pub struct LookupInternedStorage<Q, IQ>
34where
35    Q: Query,
36    Q::Key: InternKey,
37    Q::Value: Eq + Hash,
38{
39    phantom: std::marker::PhantomData<(Q::Key, IQ)>,
40}
41
42struct InternTables<K> {
43    /// Map from the key to the corresponding intern-index.
44    map: FxHashMap<K, InternId>,
45
46    /// For each valid intern-index, stores the interned value.
47    values: Vec<Arc<Slot<K>>>,
48}
49
50/// Trait implemented for the "key" that results from a
51/// `#[salsa::intern]` query.  This is basically meant to be a
52/// "newtype"'d `u32`.
53pub trait InternKey {
54    /// Create an instance of the intern-key from a `u32` value.
55    fn from_intern_id(v: InternId) -> Self;
56
57    /// Extract the `u32` with which the intern-key was created.
58    fn as_intern_id(&self) -> InternId;
59}
60
61impl InternKey for InternId {
62    fn from_intern_id(v: InternId) -> InternId {
63        v
64    }
65
66    fn as_intern_id(&self) -> InternId {
67        *self
68    }
69}
70
71#[derive(Debug)]
72struct Slot<K> {
73    /// DatabaseKeyIndex for this slot.
74    database_key_index: DatabaseKeyIndex,
75
76    /// Value that was interned.
77    value: K,
78
79    /// When was this intern'd?
80    ///
81    /// (This informs the "changed-at" result)
82    interned_at: Revision,
83}
84
85impl<Q> std::panic::RefUnwindSafe for InternedStorage<Q>
86where
87    Q: Query,
88    Q::Key: std::panic::RefUnwindSafe,
89    Q::Value: InternKey,
90    Q::Value: std::panic::RefUnwindSafe,
91{
92}
93
94impl<K: Debug + Hash + Eq> InternTables<K> {
95    /// Returns the slot for the given key.
96    fn slot_for_key(&self, key: &K) -> Option<(Arc<Slot<K>>, InternId)> {
97        let &index = self.map.get(key)?;
98        Some((self.slot_for_index(index), index))
99    }
100
101    /// Returns the slot at the given index.
102    fn slot_for_index(&self, index: InternId) -> Arc<Slot<K>> {
103        let slot = &self.values[index.as_usize()];
104        slot.clone()
105    }
106}
107
108impl<K> Default for InternTables<K>
109where
110    K: Eq + Hash,
111{
112    fn default() -> Self {
113        Self {
114            map: Default::default(),
115            values: Default::default(),
116        }
117    }
118}
119
120impl<Q> InternedStorage<Q>
121where
122    Q: Query,
123    Q::Key: Eq + Hash + Clone,
124    Q::Value: InternKey,
125{
126    /// If `key` has already been interned, returns its slot. Otherwise, creates a new slot.
127    fn intern_index(
128        &self,
129        db: &<Q as QueryDb<'_>>::DynDb,
130        key: &Q::Key,
131    ) -> (Arc<Slot<Q::Key>>, InternId) {
132        if let Some(i) = self.intern_check(key) {
133            return i;
134        }
135
136        let owned_key1 = key.to_owned();
137        let owned_key2 = owned_key1.clone();
138        let revision_now = db.salsa_runtime().current_revision();
139
140        let mut tables = self.tables.write();
141        let tables = &mut *tables;
142        let entry = match tables.map.entry(owned_key1) {
143            Entry::Vacant(entry) => entry,
144            Entry::Occupied(entry) => {
145                // Somebody inserted this key while we were waiting
146                // for the write lock. In this case, we don't need to
147                // update the `accessed_at` field because they should
148                // have already done so!
149                let index = *entry.get();
150                let slot = &tables.values[index.as_usize()];
151                debug_assert_eq!(owned_key2, slot.value);
152                return (slot.clone(), index);
153            }
154        };
155
156        let create_slot = |index: InternId| {
157            let database_key_index = DatabaseKeyIndex {
158                group_index: self.group_index,
159                query_index: Q::QUERY_INDEX,
160                key_index: index.as_u32(),
161            };
162            Arc::new(Slot {
163                database_key_index,
164                value: owned_key2,
165                interned_at: revision_now,
166            })
167        };
168
169        let (slot, index);
170        index = InternId::from(tables.values.len());
171        slot = create_slot(index);
172        tables.values.push(slot.clone());
173        entry.insert(index);
174
175        (slot, index)
176    }
177
178    fn intern_check(&self, key: &Q::Key) -> Option<(Arc<Slot<Q::Key>>, InternId)> {
179        self.tables.read().slot_for_key(key)
180    }
181
182    /// Given an index, lookup and clone its value, updating the
183    /// `accessed_at` time if necessary.
184    fn lookup_value(&self, index: InternId) -> Arc<Slot<Q::Key>> {
185        self.tables.read().slot_for_index(index)
186    }
187}
188
189impl<Q> QueryStorageOps<Q> for InternedStorage<Q>
190where
191    Q: Query,
192    Q::Value: InternKey,
193{
194    const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = CycleRecoveryStrategy::Panic;
195
196    fn new(group_index: u16) -> Self {
197        InternedStorage {
198            group_index,
199            tables: RwLock::new(InternTables::default()),
200        }
201    }
202
203    fn fmt_index(
204        &self,
205        _db: &<Q as QueryDb<'_>>::DynDb,
206        index: DatabaseKeyIndex,
207        fmt: &mut std::fmt::Formatter<'_>,
208    ) -> std::fmt::Result {
209        assert_eq!(index.group_index, self.group_index);
210        assert_eq!(index.query_index, Q::QUERY_INDEX);
211        let intern_id = InternId::from(index.key_index);
212        let slot = self.lookup_value(intern_id);
213        write!(fmt, "{}({:?})", Q::QUERY_NAME, slot.value)
214    }
215
216    fn maybe_changed_after(
217        &self,
218        db: &<Q as QueryDb<'_>>::DynDb,
219        input: DatabaseKeyIndex,
220        revision: Revision,
221    ) -> bool {
222        assert_eq!(input.group_index, self.group_index);
223        assert_eq!(input.query_index, Q::QUERY_INDEX);
224        debug_assert!(revision < db.salsa_runtime().current_revision());
225        let intern_id = InternId::from(input.key_index);
226        let slot = self.lookup_value(intern_id);
227        slot.maybe_changed_after(revision)
228    }
229
230    fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Q::Value {
231        db.unwind_if_cancelled();
232        let (slot, index) = self.intern_index(db, key);
233        let changed_at = slot.interned_at;
234        db.salsa_runtime()
235            .report_query_read_and_unwind_if_cycle_resulted(
236                slot.database_key_index,
237                INTERN_DURABILITY,
238                changed_at,
239            );
240        <Q::Value>::from_intern_id(index)
241    }
242
243    fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
244        INTERN_DURABILITY
245    }
246
247    fn entries<C>(&self, _db: &<Q as QueryDb<'_>>::DynDb) -> C
248    where
249        C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
250    {
251        let tables = self.tables.read();
252        tables
253            .map
254            .iter()
255            .map(|(key, index)| {
256                TableEntry::new(key.clone(), Some(<Q::Value>::from_intern_id(*index)))
257            })
258            .collect()
259    }
260}
261
262impl<Q> QueryStorageMassOps for InternedStorage<Q>
263where
264    Q: Query,
265    Q::Value: InternKey,
266{
267    fn purge(&self) {
268        *self.tables.write() = Default::default();
269    }
270}
271
272// Workaround for
273// ```
274// IQ: for<'d> QueryDb<
275//     'd,
276//     DynDb = <Q as QueryDb<'d>>::DynDb,
277//     Group = <Q as QueryDb<'d>>::Group,
278//     GroupStorage = <Q as QueryDb<'d>>::GroupStorage,
279// >,
280// ```
281// not working to make rustc know DynDb, Group and GroupStorage being the same in `Q` and `IQ`
282#[doc(hidden)]
283pub trait EqualDynDb<'d, IQ>: QueryDb<'d>
284where
285    IQ: QueryDb<'d>,
286{
287    fn convert_db(d: &Self::DynDb) -> &IQ::DynDb;
288    fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage;
289}
290
291impl<'d, IQ, Q> EqualDynDb<'d, IQ> for Q
292where
293    Q: QueryDb<'d, DynDb = IQ::DynDb, Group = IQ::Group, GroupStorage = IQ::GroupStorage>,
294    Q::DynDb: HasQueryGroup<Q::Group>,
295    IQ: QueryDb<'d>,
296{
297    fn convert_db(d: &Self::DynDb) -> &IQ::DynDb {
298        d
299    }
300    fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage {
301        d
302    }
303}
304
305impl<Q, IQ> QueryStorageOps<Q> for LookupInternedStorage<Q, IQ>
306where
307    Q: Query,
308    Q::Key: InternKey,
309    Q::Value: Eq + Hash,
310    IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>,
311    for<'d> Q: EqualDynDb<'d, IQ>,
312{
313    const CYCLE_STRATEGY: CycleRecoveryStrategy = CycleRecoveryStrategy::Panic;
314
315    fn new(_group_index: u16) -> Self {
316        LookupInternedStorage {
317            phantom: std::marker::PhantomData,
318        }
319    }
320
321    fn fmt_index(
322        &self,
323        db: &<Q as QueryDb<'_>>::DynDb,
324        index: DatabaseKeyIndex,
325        fmt: &mut std::fmt::Formatter<'_>,
326    ) -> std::fmt::Result {
327        let group_storage =
328            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
329        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
330        interned_storage.fmt_index(Q::convert_db(db), index, fmt)
331    }
332
333    fn maybe_changed_after(
334        &self,
335        db: &<Q as QueryDb<'_>>::DynDb,
336        input: DatabaseKeyIndex,
337        revision: Revision,
338    ) -> bool {
339        let group_storage =
340            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
341        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
342        interned_storage.maybe_changed_after(Q::convert_db(db), input, revision)
343    }
344
345    fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Q::Value {
346        let index = key.as_intern_id();
347        let group_storage =
348            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
349        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
350        let slot = interned_storage.lookup_value(index);
351        let value = slot.value.clone();
352        let interned_at = slot.interned_at;
353        db.salsa_runtime()
354            .report_query_read_and_unwind_if_cycle_resulted(
355                slot.database_key_index,
356                INTERN_DURABILITY,
357                interned_at,
358            );
359        value
360    }
361
362    fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
363        INTERN_DURABILITY
364    }
365
366    fn entries<C>(&self, db: &<Q as QueryDb<'_>>::DynDb) -> C
367    where
368        C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
369    {
370        let group_storage =
371            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
372        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
373        let tables = interned_storage.tables.read();
374        tables
375            .map
376            .iter()
377            .map(|(key, index)| {
378                TableEntry::new(<Q::Key>::from_intern_id(*index), Some(key.clone()))
379            })
380            .collect()
381    }
382}
383
384impl<Q, IQ> QueryStorageMassOps for LookupInternedStorage<Q, IQ>
385where
386    Q: Query,
387    Q::Key: InternKey,
388    Q::Value: Eq + Hash,
389    IQ: Query<Key = Q::Value, Value = Q::Key>,
390{
391    fn purge(&self) {}
392}
393
394impl<K> Slot<K> {
395    fn maybe_changed_after(&self, revision: Revision) -> bool {
396        self.interned_at > revision
397    }
398}
399
400/// Check that `Slot<Q, MP>: Send + Sync` as long as
401/// `DB::DatabaseData: Send + Sync`, which in turn implies that
402/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`.
403#[allow(dead_code)]
404fn check_send_sync<K>()
405where
406    K: Send + Sync,
407{
408    fn is_send_sync<T: Send + Sync>() {}
409    is_send_sync::<Slot<K>>();
410}
411
412/// Check that `Slot<Q, MP>: 'static` as long as
413/// `DB::DatabaseData: 'static`, which in turn implies that
414/// `Q::Key: 'static`, `Q::Value: 'static`.
415#[allow(dead_code)]
416fn check_static<K>()
417where
418    K: 'static,
419{
420    fn is_static<T: 'static>() {}
421    is_static::<Slot<K>>();
422}