gluon_salsa/
interned.rs

1use crate::debug::TableEntry;
2use crate::durability::Durability;
3use crate::intern_id::InternId;
4use crate::plumbing::HasQueryGroup;
5use crate::plumbing::QueryStorageMassOps;
6use crate::plumbing::{QueryStorageOps, QueryStorageOpsSync};
7use crate::revision::Revision;
8use crate::Query;
9use crate::{CycleError, Database, DatabaseKeyIndex, DiscardIf, QueryDb, Runtime, SweepStrategy};
10use crossbeam_utils::atomic::AtomicCell;
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 std::sync::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. When
47    /// an interned value is GC'd, the entry is set to
48    /// `InternValue::Free` with the next free item.
49    values: Vec<InternValue<K>>,
50
51    /// Index of the first free intern-index, if any.
52    first_free: Option<InternId>,
53}
54
55/// Trait implemented for the "key" that results from a
56/// `#[salsa::intern]` query.  This is basically meant to be a
57/// "newtype"'d `u32`.
58pub trait InternKey {
59    /// Create an instance of the intern-key from a `u32` value.
60    fn from_intern_id(v: InternId) -> Self;
61
62    /// Extract the `u32` with which the intern-key was created.
63    fn as_intern_id(&self) -> InternId;
64}
65
66impl InternKey for InternId {
67    fn from_intern_id(v: InternId) -> InternId {
68        v
69    }
70
71    fn as_intern_id(&self) -> InternId {
72        *self
73    }
74}
75
76enum InternValue<K> {
77    /// The value has not been gc'd.
78    Present { slot: Arc<Slot<K>> },
79
80    /// Free-list -- the index is the next
81    Free { next: Option<InternId> },
82}
83
84#[derive(Debug)]
85struct Slot<K> {
86    /// Index of this slot in the list of interned values;
87    /// set to None if gc'd.
88    index: InternId,
89
90    /// DatabaseKeyIndex for this slot.
91    database_key_index: DatabaseKeyIndex,
92
93    /// Value that was interned.
94    value: K,
95
96    /// When was this intern'd?
97    ///
98    /// (This informs the "changed-at" result)
99    interned_at: Revision,
100
101    /// When was it accessed? Equal to `None` if this slot has
102    /// been garbage collected.
103    ///
104    /// This has a subtle interaction with the garbage
105    /// collector. First, we will never GC anything accessed in the
106    /// current revision.
107    ///
108    /// To protect a slot from being GC'd, we can therefore update the
109    /// `accessed_at` field to `Some(revision_now)` before releasing
110    /// the read-lock on our interning tables.
111    accessed_at: AtomicCell<Option<Revision>>,
112}
113
114impl<Q> std::panic::RefUnwindSafe for InternedStorage<Q>
115where
116    Q: Query,
117    Q::Key: std::panic::RefUnwindSafe,
118    Q::Value: InternKey,
119    Q::Value: std::panic::RefUnwindSafe,
120{
121}
122
123impl<K: Debug + Hash + Eq> InternTables<K> {
124    /// Returns the slot for the given key.
125    ///
126    /// The slot will have its "accessed at" field updated to its current revision,
127    /// ensuring that it cannot be GC'd until the current queries complete.
128    fn slot_for_key(&self, key: &K, revision_now: Revision) -> Option<Arc<Slot<K>>> {
129        let index = self.map.get(key)?;
130        Some(self.slot_for_index(*index, revision_now))
131    }
132
133    /// Returns the slot at the given index.
134    ///
135    /// The slot will have its "accessed at" field updated to its current revision,
136    /// ensuring that it cannot be GC'd until the current queries complete.
137    fn slot_for_index(&self, index: InternId, revision_now: Revision) -> Arc<Slot<K>> {
138        match &self.values[index.as_usize()] {
139            InternValue::Present { slot } => {
140                // Subtle: we must update the "accessed at" to the
141                // current revision *while the lock is held* to
142                // prevent this slot from being GC'd.
143                let updated = slot.try_update_accessed_at(revision_now);
144                assert!(
145                    updated,
146                    "failed to update slot {:?} while holding read lock",
147                    slot
148                );
149                slot.clone()
150            }
151            InternValue::Free { .. } => {
152                panic!("index {:?} is free but should not be", index);
153            }
154        }
155    }
156}
157
158impl<K> Default for InternTables<K>
159where
160    K: Eq + Hash,
161{
162    fn default() -> Self {
163        Self {
164            map: Default::default(),
165            values: Default::default(),
166            first_free: Default::default(),
167        }
168    }
169}
170
171impl<Q> InternedStorage<Q>
172where
173    Q: Query,
174    Q::Key: Eq + Hash + Clone,
175    Q::Value: InternKey,
176{
177    /// If `key` has already been interned, returns its slot. Otherwise, creates a new slot.
178    ///
179    /// In either case, the `accessed_at` field of the slot is updated
180    /// to the current revision, ensuring that the slot cannot be GC'd
181    /// while the current queries execute.
182    fn intern_index(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Arc<Slot<Q::Key>> {
183        if let Some(i) = self.intern_check(db, key) {
184            return i;
185        }
186
187        let owned_key1 = key.to_owned();
188        let owned_key2 = owned_key1.clone();
189        let revision_now = db.salsa_runtime().current_revision();
190
191        let mut tables = self.tables.write();
192        let tables = &mut *tables;
193        let entry = match tables.map.entry(owned_key1) {
194            Entry::Vacant(entry) => entry,
195            Entry::Occupied(entry) => {
196                // Somebody inserted this key while we were waiting
197                // for the write lock. In this case, we don't need to
198                // update the `accessed_at` field because they should
199                // have already done so!
200                let index = *entry.get();
201                match &tables.values[index.as_usize()] {
202                    InternValue::Present { slot } => {
203                        debug_assert_eq!(owned_key2, slot.value);
204                        debug_assert_eq!(slot.accessed_at.load(), Some(revision_now));
205                        return slot.clone();
206                    }
207
208                    InternValue::Free { .. } => {
209                        panic!("key {:?} should be present but is not", key,);
210                    }
211                }
212            }
213        };
214
215        let create_slot = |index: InternId| {
216            let database_key_index = DatabaseKeyIndex {
217                group_index: self.group_index,
218                query_index: Q::QUERY_INDEX,
219                key_index: index.as_u32(),
220            };
221            Arc::new(Slot {
222                index,
223                database_key_index,
224                value: owned_key2,
225                interned_at: revision_now,
226                accessed_at: AtomicCell::new(Some(revision_now)),
227            })
228        };
229
230        let (slot, index);
231        match tables.first_free {
232            None => {
233                index = InternId::from(tables.values.len());
234                slot = create_slot(index);
235                tables
236                    .values
237                    .push(InternValue::Present { slot: slot.clone() });
238            }
239
240            Some(i) => {
241                index = i;
242                slot = create_slot(index);
243
244                let next_free = match &tables.values[i.as_usize()] {
245                    InternValue::Free { next } => *next,
246                    InternValue::Present { slot } => {
247                        panic!(
248                            "index {:?} was supposed to be free but contains {:?}",
249                            i, slot.value
250                        );
251                    }
252                };
253
254                tables.values[index.as_usize()] = InternValue::Present { slot: slot.clone() };
255                tables.first_free = next_free;
256            }
257        }
258
259        entry.insert(index);
260
261        slot
262    }
263
264    fn intern_check(
265        &self,
266        db: &<Q as QueryDb<'_>>::DynDb,
267        key: &Q::Key,
268    ) -> Option<Arc<Slot<Q::Key>>> {
269        let revision_now = db.salsa_runtime().current_revision();
270        let slot = self.tables.read().slot_for_key(key, revision_now)?;
271        Some(slot)
272    }
273
274    /// Given an index, lookup and clone its value, updating the
275    /// `accessed_at` time if necessary.
276    fn lookup_value(&self, db: &<Q as QueryDb<'_>>::DynDb, index: InternId) -> Arc<Slot<Q::Key>> {
277        let revision_now = db.salsa_runtime().current_revision();
278        self.tables.read().slot_for_index(index, revision_now)
279    }
280}
281
282impl<Q> QueryStorageOps<Q> for InternedStorage<Q>
283where
284    Q: Query,
285    Q::Value: InternKey,
286{
287    fn new(group_index: u16) -> Self {
288        InternedStorage {
289            group_index,
290            tables: RwLock::new(InternTables::default()),
291        }
292    }
293
294    fn fmt_index(
295        &self,
296        db: &<Q as QueryDb<'_>>::DynDb,
297        index: DatabaseKeyIndex,
298        fmt: &mut std::fmt::Formatter<'_>,
299    ) -> std::fmt::Result {
300        assert_eq!(index.group_index, self.group_index);
301        assert_eq!(index.query_index, Q::QUERY_INDEX);
302        let intern_id = InternId::from(index.key_index);
303        let slot = self.lookup_value(db, intern_id);
304        write!(fmt, "{}({:?})", Q::QUERY_NAME, slot.value)
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            .iter()
319            .map(|(key, index)| {
320                TableEntry::new(key.clone(), Some(<Q::Value>::from_intern_id(*index)))
321            })
322            .collect()
323    }
324
325    fn peek(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Option<Q::Value> {
326        self.intern_check(db, key).map(|slot| {
327            let index = slot.index;
328            <Q::Value>::from_intern_id(index)
329        })
330    }
331}
332
333impl<Q> QueryStorageOpsSync<Q> for InternedStorage<Q>
334where
335    Q: Query,
336    Q::Value: InternKey,
337{
338    fn maybe_changed_since(
339        &self,
340        db: &mut <Q as QueryDb<'_>>::Db,
341        input: DatabaseKeyIndex,
342        revision: Revision,
343    ) -> bool {
344        assert_eq!(input.group_index, self.group_index);
345        assert_eq!(input.query_index, Q::QUERY_INDEX);
346        let intern_id = InternId::from(input.key_index);
347        let slot = self.lookup_value(db, intern_id);
348        slot.maybe_changed_since(db, revision)
349    }
350
351    fn try_fetch(
352        &self,
353        db: &mut <Q as QueryDb<'_>>::Db,
354        key: &Q::Key,
355    ) -> Result<Q::Value, CycleError<DatabaseKeyIndex>> {
356        let slot = self.intern_index(db, key);
357        let changed_at = slot.interned_at;
358        let index = slot.index;
359        db.salsa_runtime().report_query_read(
360            slot.database_key_index,
361            INTERN_DURABILITY,
362            changed_at,
363        );
364        Ok(<Q::Value>::from_intern_id(index))
365    }
366}
367
368impl<Q> QueryStorageMassOps for InternedStorage<Q>
369where
370    Q: Query,
371    Q::Value: InternKey,
372{
373    fn sweep(&self, runtime: &Runtime, strategy: SweepStrategy) {
374        let mut tables = self.tables.write();
375        let last_changed = runtime.last_changed_revision(INTERN_DURABILITY);
376        let revision_now = runtime.current_revision();
377        let InternTables {
378            map,
379            values,
380            first_free,
381        } = &mut *tables;
382        map.retain(|key, intern_index| {
383            match strategy.discard_if {
384                DiscardIf::Never => true,
385
386                // NB: Interned keys *never* discard keys unless they
387                // are outdated, regardless of the sweep strategy. This is
388                // because interned queries are not deterministic;
389                // if we were to remove a value from the current revision,
390                // and the query were later executed again, it would not necessarily
391                // produce the same intern key the second time. This would wreak
392                // havoc. See the test `discard_during_same_revision` for an example.
393                //
394                // Keys that have not (yet) been accessed during this
395                // revision don't have this problem. Anything
396                // dependent on them would regard itself as dirty if
397                // they are removed and also be forced to re-execute.
398                DiscardIf::Always | DiscardIf::Outdated => match &values[intern_index.as_usize()] {
399                    InternValue::Present { slot, .. } => {
400                        if slot.try_collect(last_changed, revision_now) {
401                            values[intern_index.as_usize()] =
402                                InternValue::Free { next: *first_free };
403                            *first_free = Some(*intern_index);
404                            false
405                        } else {
406                            true
407                        }
408                    }
409
410                    InternValue::Free { .. } => {
411                        panic!(
412                            "key {:?} maps to index {:?} which is free",
413                            key, intern_index
414                        );
415                    }
416                },
417            }
418        });
419    }
420    fn purge(&self) {
421        *self.tables.write() = Default::default();
422    }
423}
424
425// Workaround for
426// ```
427// IQ: for<'d> QueryDb<
428//     'd,
429//     DynDb = <Q as QueryDb<'d>>::DynDb,
430//     Group = <Q as QueryDb<'d>>::Group,
431//     GroupStorage = <Q as QueryDb<'d>>::GroupStorage,
432// >,
433// ```
434// not working to make rustc know DynDb, Group and GroupStorage being the same in `Q` and `IQ`
435#[doc(hidden)]
436pub trait EqualDynDb<'d, IQ>: QueryDb<'d>
437where
438    IQ: QueryDb<'d>,
439{
440    fn convert_db(d: &mut Self::Db) -> &mut IQ::Db;
441    fn convert_dyn_db(d: &Self::DynDb) -> &IQ::DynDb;
442    fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage;
443}
444
445impl<'d, IQ, Q> EqualDynDb<'d, IQ> for Q
446where
447    Q: QueryDb<
448        'd,
449        Db = IQ::Db,
450        DynDb = IQ::DynDb,
451        Group = IQ::Group,
452        GroupStorage = IQ::GroupStorage,
453    >,
454    Q::DynDb: HasQueryGroup<Q::Group>,
455    IQ: QueryDb<'d>,
456{
457    fn convert_db(d: &mut Self::Db) -> &mut IQ::Db {
458        d
459    }
460    fn convert_dyn_db(d: &Self::DynDb) -> &IQ::DynDb {
461        d
462    }
463    fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage {
464        d
465    }
466}
467
468impl<Q, IQ> QueryStorageOps<Q> for LookupInternedStorage<Q, IQ>
469where
470    Q: Query,
471    Q::Key: InternKey,
472    Q::Value: Eq + Hash,
473    IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>,
474    for<'d> Q: EqualDynDb<'d, IQ>,
475{
476    fn new(_group_index: u16) -> Self {
477        LookupInternedStorage {
478            phantom: std::marker::PhantomData,
479        }
480    }
481
482    fn fmt_index(
483        &self,
484        db: &<Q as QueryDb<'_>>::DynDb,
485        index: DatabaseKeyIndex,
486        fmt: &mut std::fmt::Formatter<'_>,
487    ) -> std::fmt::Result {
488        let group_storage =
489            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
490        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)).clone();
491        interned_storage.fmt_index(Q::convert_dyn_db(db), index, fmt)
492    }
493
494    fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
495        INTERN_DURABILITY
496    }
497
498    fn entries<C>(&self, db: &<Q as QueryDb<'_>>::DynDb) -> C
499    where
500        C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
501    {
502        let group_storage =
503            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
504        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
505        let tables = interned_storage.tables.read();
506        tables
507            .map
508            .iter()
509            .map(|(key, index)| {
510                TableEntry::new(<Q::Key>::from_intern_id(*index), Some(key.clone()))
511            })
512            .collect()
513    }
514
515    fn peek(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Option<Q::Value> {
516        let index = key.as_intern_id();
517        let interned_storage = query_storage::<Q, IQ>(db);
518        let slot = interned_storage.lookup_value(Q::convert_dyn_db(db), index);
519        let value = slot.value.clone();
520        Some(value)
521    }
522}
523
524fn query_storage<Q, IQ>(db: &<Q as QueryDb<'_>>::DynDb) -> Arc<InternedStorage<IQ>>
525where
526    Q: Query,
527    Q::Key: InternKey,
528    Q::Value: Eq + Hash,
529    IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>,
530    for<'d> Q: EqualDynDb<'d, IQ>,
531{
532    let group_storage =
533        <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db).clone();
534    IQ::query_storage(Q::convert_group_storage(group_storage)).clone()
535}
536
537impl<Q, IQ> QueryStorageOpsSync<Q> for LookupInternedStorage<Q, IQ>
538where
539    Q: Query,
540    Q::Key: InternKey,
541    Q::Value: Eq + Hash,
542    IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>,
543    for<'d> Q: EqualDynDb<'d, IQ>,
544{
545    fn maybe_changed_since(
546        &self,
547        db: &mut <Q as QueryDb<'_>>::Db,
548        input: DatabaseKeyIndex,
549        revision: Revision,
550    ) -> bool {
551        let interned_storage = query_storage::<Q, IQ>(db);
552        interned_storage.maybe_changed_since(Q::convert_db(db), input, revision)
553    }
554
555    fn try_fetch(
556        &self,
557        db: &mut <Q as QueryDb<'_>>::Db,
558        key: &Q::Key,
559    ) -> Result<Q::Value, CycleError<DatabaseKeyIndex>> {
560        let index = key.as_intern_id();
561        let interned_storage = query_storage::<Q, IQ>(db);
562        let slot = interned_storage.lookup_value(Q::convert_db(db), index);
563        let value = slot.value.clone();
564        let interned_at = slot.interned_at;
565        db.salsa_runtime().report_query_read(
566            slot.database_key_index,
567            INTERN_DURABILITY,
568            interned_at,
569        );
570        Ok(value)
571    }
572}
573
574impl<Q, IQ> QueryStorageMassOps for LookupInternedStorage<Q, IQ>
575where
576    Q: Query,
577    Q::Key: InternKey,
578    Q::Value: Eq + Hash,
579    IQ: Query<Key = Q::Value, Value = Q::Key>,
580{
581    fn sweep(&self, _: &Runtime, _strategy: SweepStrategy) {}
582    fn purge(&self) {}
583}
584
585impl<K> Slot<K> {
586    fn maybe_changed_since<DB>(&self, db: &mut DB, revision: Revision) -> bool
587    where
588        DB: std::ops::Deref,
589        DB::Target: Database,
590    {
591        let revision_now = db.salsa_runtime().current_revision();
592        if !self.try_update_accessed_at(revision_now) {
593            // if we failed to update accessed-at, then this slot was garbage collected
594            true
595        } else {
596            // otherwise, compare the interning with revision
597            self.interned_at > revision
598        }
599    }
600
601    /// Updates the `accessed_at` time to be `revision_now` (if
602    /// necessary).  Returns true if the update was successful, or
603    /// false if the slot has been GC'd in the interim.
604    fn try_update_accessed_at(&self, revision_now: Revision) -> bool {
605        if let Some(accessed_at) = self.accessed_at.load() {
606            match self
607                .accessed_at
608                .compare_exchange(Some(accessed_at), Some(revision_now))
609            {
610                Ok(_) => true,
611                Err(Some(r)) => {
612                    // Somebody was racing with us to update the field -- but they
613                    // also updated it to revision now, so that's cool.
614                    debug_assert_eq!(r, revision_now);
615                    true
616                }
617                Err(None) => {
618                    // The garbage collector was racing with us and it swept this
619                    // slot before we could mark it as accessed.
620                    false
621                }
622            }
623        } else {
624            false
625        }
626    }
627
628    /// Invoked during sweeping to try and collect this slot. Fails if
629    /// the slot has been accessed since the intern durability last
630    /// changed, because in that case there may be outstanding
631    /// references that are still considered valid. Note that this
632    /// access could be racing with the attempt to collect (in
633    /// particular, when verifying dependencies).
634    fn try_collect(&self, last_changed: Revision, revision_now: Revision) -> bool {
635        let accessed_at = self.accessed_at.load().unwrap();
636        if accessed_at < last_changed {
637            match self.accessed_at.compare_exchange(Some(accessed_at), None) {
638                Ok(_) => true,
639                Err(r) => {
640                    // The only one racing with us can be a
641                    // verification attempt, which will always bump
642                    // `accessed_at` to the current revision.
643                    debug_assert_eq!(r, Some(revision_now));
644                    false
645                }
646            }
647        } else {
648            false
649        }
650    }
651}
652
653/// Check that `Slot<Q, MP>: Send + Sync` as long as
654/// `DB::DatabaseData: Send + Sync`, which in turn implies that
655/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`.
656#[allow(dead_code)]
657fn check_send_sync<K>()
658where
659    K: Send + Sync,
660{
661    fn is_send_sync<T: Send + Sync>() {}
662    is_send_sync::<Slot<K>>();
663}
664
665/// Check that `Slot<Q, MP>: 'static` as long as
666/// `DB::DatabaseData: 'static`, which in turn implies that
667/// `Q::Key: 'static`, `Q::Value: 'static`.
668#[allow(dead_code)]
669fn check_static<K>()
670where
671    K: 'static,
672{
673    fn is_static<T: 'static>() {}
674    is_static::<Slot<K>>();
675}