ra_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::QueryTable;
11use crate::{Database, DatabaseKeyIndex, QueryDb};
12use parking_lot::RwLock;
13use rustc_hash::FxHashMap;
14use std::collections::hash_map::Entry;
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::Key: InternValue,
27    Q::Value: InternKey,
28{
29    group_index: u16,
30    tables: RwLock<InternTables<MappedKey<Q>, Q::Key>>,
31}
32
33/// Storage for the looking up interned things.
34pub struct LookupInternedStorage<Q, IQ>
35where
36    Q: Query,
37    Q::Key: InternKey,
38    Q::Value: InternValue,
39{
40    phantom: std::marker::PhantomData<(Q::Key, IQ)>,
41}
42
43struct InternTables<K, V> {
44    /// Map from the key to the corresponding intern-index.
45    map: FxHashMap<K, InternId>,
46
47    /// For each valid intern-index, stores the interned value.
48    values: Vec<Arc<Slot<V>>>,
49}
50
51/// Trait implemented for the "key" that results from a
52/// `#[salsa::intern]` query.  This is basically meant to be a
53/// "newtype"'d `u32`.
54pub trait InternKey {
55    /// Create an instance of the intern-key from a `u32` value.
56    fn from_intern_id(v: InternId) -> Self;
57
58    /// Extract the `u32` with which the intern-key was created.
59    fn as_intern_id(&self) -> InternId;
60}
61
62impl InternKey for InternId {
63    fn from_intern_id(v: InternId) -> InternId {
64        v
65    }
66
67    fn as_intern_id(&self) -> InternId {
68        *self
69    }
70}
71
72/// Trait implemented for the "value" that is being interned.
73pub trait InternValue {
74    /// They key used to intern this value by.
75    type Key: Eq + Hash + Debug + Clone;
76    /// Maps the value to a key that will be used to intern it.
77    fn into_key(&self) -> Self::Key;
78    /// Calls the given function with the key that was used to intern this value.
79    ///
80    /// This is mainly used to prevent frequent cloning of the key when doing a lookup.
81    #[inline]
82    fn with_key<F: FnOnce(&Self::Key) -> T, T>(&self, f: F) -> T {
83        f(&self.into_key())
84    }
85}
86
87impl<A: InternValue + Eq + Hash + Debug + Clone, B: InternValue + Eq + Hash + Debug + Clone>
88    InternValue for (A, B)
89{
90    type Key = Self;
91    #[inline]
92    fn into_key(&self) -> Self::Key {
93        self.clone()
94    }
95    #[inline]
96    fn with_key<F: FnOnce(&Self::Key) -> T, T>(&self, f: F) -> T {
97        f(self)
98    }
99}
100
101pub trait InternValueTrivial
102where
103    Self: Eq + Hash + Debug + Clone,
104{
105}
106
107/// Implement [`InternValue`] trivially, that is without actually mapping at all.
108impl<V: InternValueTrivial> InternValue for V {
109    type Key = Self;
110    #[inline]
111    fn into_key(&self) -> Self::Key {
112        self.clone()
113    }
114    #[inline]
115    fn with_key<F: FnOnce(&Self::Key) -> T, T>(&self, f: F) -> T {
116        f(self)
117    }
118}
119
120impl InternValueTrivial for String {}
121
122#[derive(Debug)]
123struct Slot<V> {
124    /// key index for this slot.
125    key_index: u32,
126
127    /// Value that was interned.
128    value: V,
129
130    /// When was this intern'd?
131    ///
132    /// (This informs the "changed-at" result)
133    interned_at: Revision,
134}
135
136impl<Q> std::panic::RefUnwindSafe for InternedStorage<Q>
137where
138    Q: Query,
139    Q::Key: InternValue,
140    Q::Key: std::panic::RefUnwindSafe,
141    Q::Value: InternKey,
142    Q::Value: std::panic::RefUnwindSafe,
143{
144}
145
146impl<K: Debug + Hash + Eq, V> InternTables<K, V> {
147    /// Returns the slot for the given key.
148    fn slot_for_key(&self, key: &K) -> Option<(Arc<Slot<V>>, InternId)> {
149        let &index = self.map.get(key)?;
150        Some((self.slot_for_index(index), index))
151    }
152
153    /// Returns the slot at the given index.
154    fn slot_for_index(&self, index: InternId) -> Arc<Slot<V>> {
155        let slot = &self.values[index.as_usize()];
156        slot.clone()
157    }
158}
159
160impl<K, V> Default for InternTables<K, V>
161where
162    K: Eq + Hash,
163{
164    fn default() -> Self {
165        Self { map: Default::default(), values: Default::default() }
166    }
167}
168
169type MappedKey<Q> = <<Q as Query>::Key as InternValue>::Key;
170
171impl<Q> InternedStorage<Q>
172where
173    Q: Query,
174    Q::Key: InternValue,
175    Q::Value: InternKey,
176{
177    /// Creates a new slot.
178    fn intern_index(
179        &self,
180        db: &<Q as QueryDb<'_>>::DynDb,
181        mapped_key: MappedKey<Q>,
182        insert: impl FnOnce(Q::Value) -> Q::Key,
183    ) -> (Arc<Slot<Q::Key>>, InternId) {
184        let revision_now = db.salsa_runtime().current_revision();
185
186        let mut tables = self.tables.write();
187        let tables = &mut *tables;
188        let entry = match tables.map.entry(mapped_key) {
189            Entry::Vacant(entry) => entry,
190            Entry::Occupied(entry) => {
191                // Somebody inserted this key while we were waiting
192                // for the write lock. In this case, we don't need to
193                // update the `accessed_at` field because they should
194                // have already done so!
195                let index = *entry.get();
196                let slot = &tables.values[index.as_usize()];
197                return (slot.clone(), index);
198            }
199        };
200
201        let create_slot = |index: InternId| {
202            Arc::new(Slot {
203                key_index: index.as_u32(),
204                value: insert(Q::Value::from_intern_id(index)),
205                interned_at: revision_now,
206            })
207        };
208
209        let index = InternId::from(tables.values.len());
210        let slot = create_slot(index);
211        tables.values.push(slot.clone());
212        entry.insert(index);
213
214        (slot, index)
215    }
216
217    fn intern_check(&self, key: &MappedKey<Q>) -> Option<(Arc<Slot<Q::Key>>, InternId)> {
218        self.tables.read().slot_for_key(key)
219    }
220
221    /// Given an index, lookup and clone its value, updating the
222    /// `accessed_at` time if necessary.
223    fn lookup_value(&self, index: InternId) -> Arc<Slot<Q::Key>> {
224        self.tables.read().slot_for_index(index)
225    }
226
227    fn fetch_or_insert(
228        &self,
229        db: &<Q as QueryDb<'_>>::DynDb,
230        key: MappedKey<Q>,
231        insert: impl FnOnce(Q::Value) -> Q::Key,
232    ) -> Q::Value {
233        db.unwind_if_cancelled();
234        let (slot, index) = match self.intern_check(&key) {
235            Some(i) => i,
236            None => self.intern_index(db, key, insert),
237        };
238        let changed_at = slot.interned_at;
239        db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
240            DatabaseKeyIndex {
241                group_index: self.group_index,
242                query_index: Q::QUERY_INDEX,
243                key_index: slot.key_index,
244            },
245            INTERN_DURABILITY,
246            changed_at,
247        );
248        <Q::Value>::from_intern_id(index)
249    }
250}
251
252impl<Q> QueryStorageOps<Q> for InternedStorage<Q>
253where
254    Q: Query,
255    Q::Key: InternValue,
256    Q::Value: InternKey,
257{
258    const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = CycleRecoveryStrategy::Panic;
259
260    fn new(group_index: u16) -> Self {
261        InternedStorage { group_index, tables: RwLock::new(InternTables::default()) }
262    }
263
264    fn fmt_index(
265        &self,
266        _db: &<Q as QueryDb<'_>>::DynDb,
267        index: u32,
268        fmt: &mut std::fmt::Formatter<'_>,
269    ) -> std::fmt::Result {
270        let intern_id = InternId::from(index);
271        let slot = self.lookup_value(intern_id);
272        write!(fmt, "{}({:?})", Q::QUERY_NAME, slot.value)
273    }
274
275    fn maybe_changed_after(
276        &self,
277        db: &<Q as QueryDb<'_>>::DynDb,
278        input: u32,
279        revision: Revision,
280    ) -> bool {
281        debug_assert!(revision < db.salsa_runtime().current_revision());
282        let intern_id = InternId::from(input);
283        let slot = self.lookup_value(intern_id);
284        slot.maybe_changed_after(revision)
285    }
286
287    fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Q::Value {
288        db.unwind_if_cancelled();
289
290        let (slot, index) = match key.with_key(|key| self.intern_check(key)) {
291            Some(i) => i,
292            None => self.intern_index(db, key.into_key(), |_| key.clone()),
293        };
294        let changed_at = slot.interned_at;
295        db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
296            DatabaseKeyIndex {
297                group_index: self.group_index,
298                query_index: Q::QUERY_INDEX,
299                key_index: slot.key_index,
300            },
301            INTERN_DURABILITY,
302            changed_at,
303        );
304        <Q::Value>::from_intern_id(index)
305    }
306
307    fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
308        INTERN_DURABILITY
309    }
310
311    fn entries<C>(&self, _db: &<Q as QueryDb<'_>>::DynDb) -> C
312    where
313        C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
314    {
315        let tables = self.tables.read();
316        tables
317            .map
318            .values()
319            .map(|index| {
320                TableEntry::new(
321                    tables.values[index.as_usize()].value.clone(),
322                    Some(<Q::Value>::from_intern_id(*index)),
323                )
324            })
325            .collect()
326    }
327}
328
329impl<Q> QueryStorageMassOps for InternedStorage<Q>
330where
331    Q: Query,
332    Q::Key: InternValue,
333    Q::Value: InternKey,
334{
335    fn purge(&self) {
336        *self.tables.write() = Default::default();
337    }
338}
339
340// Workaround for
341// ```
342// IQ: for<'d> QueryDb<
343//     'd,
344//     DynDb = <Q as QueryDb<'d>>::DynDb,
345//     Group = <Q as QueryDb<'d>>::Group,
346//     GroupStorage = <Q as QueryDb<'d>>::GroupStorage,
347// >,
348// ```
349// not working to make rustc know DynDb, Group and GroupStorage being the same in `Q` and `IQ`
350#[doc(hidden)]
351pub trait EqualDynDb<'d, IQ>: QueryDb<'d>
352where
353    IQ: QueryDb<'d>,
354{
355    fn convert_db(d: &Self::DynDb) -> &IQ::DynDb;
356    fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage;
357}
358
359impl<'d, IQ, Q> EqualDynDb<'d, IQ> for Q
360where
361    Q: QueryDb<'d, DynDb = IQ::DynDb, Group = IQ::Group, GroupStorage = IQ::GroupStorage>,
362    Q::DynDb: HasQueryGroup<Q::Group>,
363    IQ: QueryDb<'d>,
364{
365    fn convert_db(d: &Self::DynDb) -> &IQ::DynDb {
366        d
367    }
368    fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage {
369        d
370    }
371}
372
373impl<Q, IQ> QueryStorageOps<Q> for LookupInternedStorage<Q, IQ>
374where
375    Q: Query,
376    Q::Key: InternKey,
377    Q::Value: InternValue,
378    IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>,
379    for<'d> Q: EqualDynDb<'d, IQ>,
380{
381    const CYCLE_STRATEGY: CycleRecoveryStrategy = CycleRecoveryStrategy::Panic;
382
383    fn new(_group_index: u16) -> Self {
384        LookupInternedStorage { phantom: std::marker::PhantomData }
385    }
386
387    fn fmt_index(
388        &self,
389        db: &<Q as QueryDb<'_>>::DynDb,
390        index: u32,
391        fmt: &mut std::fmt::Formatter<'_>,
392    ) -> std::fmt::Result {
393        let group_storage =
394            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
395        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
396        interned_storage.fmt_index(Q::convert_db(db), index, fmt)
397    }
398
399    fn maybe_changed_after(
400        &self,
401        db: &<Q as QueryDb<'_>>::DynDb,
402        input: u32,
403        revision: Revision,
404    ) -> bool {
405        let group_storage =
406            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
407        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
408        interned_storage.maybe_changed_after(Q::convert_db(db), input, revision)
409    }
410
411    fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Q::Value {
412        let index = key.as_intern_id();
413        let group_storage =
414            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
415        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
416        let slot = interned_storage.lookup_value(index);
417        let value = slot.value.clone();
418        let interned_at = slot.interned_at;
419        db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
420            DatabaseKeyIndex {
421                group_index: interned_storage.group_index,
422                query_index: Q::QUERY_INDEX,
423                key_index: slot.key_index,
424            },
425            INTERN_DURABILITY,
426            interned_at,
427        );
428        value
429    }
430
431    fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
432        INTERN_DURABILITY
433    }
434
435    fn entries<C>(&self, db: &<Q as QueryDb<'_>>::DynDb) -> C
436    where
437        C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
438    {
439        let group_storage =
440            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
441        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
442        let tables = interned_storage.tables.read();
443        tables
444            .map
445            .values()
446            .map(|index| {
447                TableEntry::new(
448                    <Q::Key>::from_intern_id(*index),
449                    Some(tables.values[index.as_usize()].value.clone()),
450                )
451            })
452            .collect()
453    }
454}
455
456impl<Q, IQ> QueryStorageMassOps for LookupInternedStorage<Q, IQ>
457where
458    Q: Query,
459    Q::Key: InternKey,
460    Q::Value: InternValue,
461    IQ: Query<Key = Q::Value, Value = Q::Key>,
462{
463    fn purge(&self) {}
464}
465
466impl<K> Slot<K> {
467    fn maybe_changed_after(&self, revision: Revision) -> bool {
468        self.interned_at > revision
469    }
470}
471
472/// Check that `Slot<Q, MP>: Send + Sync` as long as
473/// `DB::DatabaseData: Send + Sync`, which in turn implies that
474/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`.
475#[allow(dead_code)]
476fn check_send_sync<K>()
477where
478    K: Send + Sync,
479{
480    fn is_send_sync<T: Send + Sync>() {}
481    is_send_sync::<Slot<K>>();
482}
483
484/// Check that `Slot<Q, MP>: 'static` as long as
485/// `DB::DatabaseData: 'static`, which in turn implies that
486/// `Q::Key: 'static`, `Q::Value: 'static`.
487#[allow(dead_code)]
488fn check_static<K>()
489where
490    K: 'static,
491{
492    fn is_static<T: 'static>() {}
493    is_static::<Slot<K>>();
494}
495
496impl<Q> QueryTable<'_, Q>
497where
498    Q: Query<Storage = InternedStorage<Q>>,
499    Q::Key: InternValue,
500    Q::Value: InternKey,
501{
502    /// Fetches the intern id for the given key or inserts it if it does not exist.
503    pub fn get_or_insert(
504        &self,
505        key: MappedKey<Q>,
506        insert: impl FnOnce(Q::Value) -> Q::Key,
507    ) -> Q::Value {
508        self.storage.fetch_or_insert(self.db, key, insert)
509    }
510}