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;
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 maybe_changed_since(
308        &self,
309        db: &<Q as QueryDb<'_>>::DynDb,
310        input: DatabaseKeyIndex,
311        revision: Revision,
312    ) -> bool {
313        assert_eq!(input.group_index, self.group_index);
314        assert_eq!(input.query_index, Q::QUERY_INDEX);
315        let intern_id = InternId::from(input.key_index);
316        let slot = self.lookup_value(db, intern_id);
317        slot.maybe_changed_since(db, revision)
318    }
319
320    fn try_fetch(
321        &self,
322        db: &<Q as QueryDb<'_>>::DynDb,
323        key: &Q::Key,
324    ) -> Result<Q::Value, CycleError<DatabaseKeyIndex>> {
325        let slot = self.intern_index(db, key);
326        let changed_at = slot.interned_at;
327        let index = slot.index;
328        db.salsa_runtime().report_query_read(
329            slot.database_key_index,
330            INTERN_DURABILITY,
331            changed_at,
332        );
333        Ok(<Q::Value>::from_intern_id(index))
334    }
335
336    fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
337        INTERN_DURABILITY
338    }
339
340    fn entries<C>(&self, _db: &<Q as QueryDb<'_>>::DynDb) -> C
341    where
342        C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
343    {
344        let tables = self.tables.read();
345        tables
346            .map
347            .iter()
348            .map(|(key, index)| {
349                TableEntry::new(key.clone(), Some(<Q::Value>::from_intern_id(*index)))
350            })
351            .collect()
352    }
353}
354
355impl<Q> QueryStorageMassOps for InternedStorage<Q>
356where
357    Q: Query,
358    Q::Value: InternKey,
359{
360    fn sweep(&self, runtime: &Runtime, strategy: SweepStrategy) {
361        let mut tables = self.tables.write();
362        let last_changed = runtime.last_changed_revision(INTERN_DURABILITY);
363        let revision_now = runtime.current_revision();
364        let InternTables {
365            map,
366            values,
367            first_free,
368        } = &mut *tables;
369        map.retain(|key, intern_index| {
370            match strategy.discard_if {
371                DiscardIf::Never => true,
372
373                // NB: Interned keys *never* discard keys unless they
374                // are outdated, regardless of the sweep strategy. This is
375                // because interned queries are not deterministic;
376                // if we were to remove a value from the current revision,
377                // and the query were later executed again, it would not necessarily
378                // produce the same intern key the second time. This would wreak
379                // havoc. See the test `discard_during_same_revision` for an example.
380                //
381                // Keys that have not (yet) been accessed during this
382                // revision don't have this problem. Anything
383                // dependent on them would regard itself as dirty if
384                // they are removed and also be forced to re-execute.
385                DiscardIf::Always | DiscardIf::Outdated => match &values[intern_index.as_usize()] {
386                    InternValue::Present { slot, .. } => {
387                        if slot.try_collect(last_changed, revision_now) {
388                            values[intern_index.as_usize()] =
389                                InternValue::Free { next: *first_free };
390                            *first_free = Some(*intern_index);
391                            false
392                        } else {
393                            true
394                        }
395                    }
396
397                    InternValue::Free { .. } => {
398                        panic!(
399                            "key {:?} maps to index {:?} which is free",
400                            key, intern_index
401                        );
402                    }
403                },
404            }
405        });
406    }
407    fn purge(&self) {
408        *self.tables.write() = Default::default();
409    }
410}
411
412// Workaround for
413// ```
414// IQ: for<'d> QueryDb<
415//     'd,
416//     DynDb = <Q as QueryDb<'d>>::DynDb,
417//     Group = <Q as QueryDb<'d>>::Group,
418//     GroupStorage = <Q as QueryDb<'d>>::GroupStorage,
419// >,
420// ```
421// not working to make rustc know DynDb, Group and GroupStorage being the same in `Q` and `IQ`
422#[doc(hidden)]
423pub trait EqualDynDb<'d, IQ>: QueryDb<'d>
424where
425    IQ: QueryDb<'d>,
426{
427    fn convert_db(d: &Self::DynDb) -> &IQ::DynDb;
428    fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage;
429}
430
431impl<'d, IQ, Q> EqualDynDb<'d, IQ> for Q
432where
433    Q: QueryDb<'d, DynDb = IQ::DynDb, Group = IQ::Group, GroupStorage = IQ::GroupStorage>,
434    Q::DynDb: HasQueryGroup<Q::Group>,
435    IQ: QueryDb<'d>,
436{
437    fn convert_db(d: &Self::DynDb) -> &IQ::DynDb {
438        d
439    }
440    fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage {
441        d
442    }
443}
444
445impl<Q, IQ> QueryStorageOps<Q> for LookupInternedStorage<Q, IQ>
446where
447    Q: Query,
448    Q::Key: InternKey,
449    Q::Value: Eq + Hash,
450    IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>,
451    for<'d> Q: EqualDynDb<'d, IQ>,
452{
453    fn new(_group_index: u16) -> Self {
454        LookupInternedStorage {
455            phantom: std::marker::PhantomData,
456        }
457    }
458
459    fn fmt_index(
460        &self,
461        db: &<Q as QueryDb<'_>>::DynDb,
462        index: DatabaseKeyIndex,
463        fmt: &mut std::fmt::Formatter<'_>,
464    ) -> std::fmt::Result {
465        let group_storage =
466            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
467        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
468        interned_storage.fmt_index(Q::convert_db(db), index, fmt)
469    }
470
471    fn maybe_changed_since(
472        &self,
473        db: &<Q as QueryDb<'_>>::DynDb,
474        input: DatabaseKeyIndex,
475        revision: Revision,
476    ) -> bool {
477        let group_storage =
478            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
479        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
480        interned_storage.maybe_changed_since(Q::convert_db(db), input, revision)
481    }
482
483    fn try_fetch(
484        &self,
485        db: &<Q as QueryDb<'_>>::DynDb,
486        key: &Q::Key,
487    ) -> Result<Q::Value, CycleError<DatabaseKeyIndex>> {
488        let index = key.as_intern_id();
489        let group_storage =
490            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
491        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
492        let slot = interned_storage.lookup_value(Q::convert_db(db), index);
493        let value = slot.value.clone();
494        let interned_at = slot.interned_at;
495        db.salsa_runtime().report_query_read(
496            slot.database_key_index,
497            INTERN_DURABILITY,
498            interned_at,
499        );
500        Ok(value)
501    }
502
503    fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
504        INTERN_DURABILITY
505    }
506
507    fn entries<C>(&self, db: &<Q as QueryDb<'_>>::DynDb) -> C
508    where
509        C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
510    {
511        let group_storage =
512            <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
513        let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
514        let tables = interned_storage.tables.read();
515        tables
516            .map
517            .iter()
518            .map(|(key, index)| {
519                TableEntry::new(<Q::Key>::from_intern_id(*index), Some(key.clone()))
520            })
521            .collect()
522    }
523}
524
525impl<Q, IQ> QueryStorageMassOps for LookupInternedStorage<Q, IQ>
526where
527    Q: Query,
528    Q::Key: InternKey,
529    Q::Value: Eq + Hash,
530    IQ: Query<Key = Q::Value, Value = Q::Key>,
531{
532    fn sweep(&self, _: &Runtime, _strategy: SweepStrategy) {}
533    fn purge(&self) {}
534}
535
536impl<K> Slot<K> {
537    fn maybe_changed_since<DB: ?Sized + Database>(&self, db: &DB, revision: Revision) -> bool {
538        let revision_now = db.salsa_runtime().current_revision();
539        if !self.try_update_accessed_at(revision_now) {
540            // if we failed to update accessed-at, then this slot was garbage collected
541            true
542        } else {
543            // otherwise, compare the interning with revision
544            self.interned_at > revision
545        }
546    }
547
548    /// Updates the `accessed_at` time to be `revision_now` (if
549    /// necessary).  Returns true if the update was successful, or
550    /// false if the slot has been GC'd in the interim.
551    fn try_update_accessed_at(&self, revision_now: Revision) -> bool {
552        if let Some(accessed_at) = self.accessed_at.load() {
553            match self
554                .accessed_at
555                .compare_exchange(Some(accessed_at), Some(revision_now))
556            {
557                Ok(_) => true,
558                Err(Some(r)) => {
559                    // Somebody was racing with us to update the field -- but they
560                    // also updated it to revision now, so that's cool.
561                    debug_assert_eq!(r, revision_now);
562                    true
563                }
564                Err(None) => {
565                    // The garbage collector was racing with us and it swept this
566                    // slot before we could mark it as accessed.
567                    false
568                }
569            }
570        } else {
571            false
572        }
573    }
574
575    /// Invoked during sweeping to try and collect this slot. Fails if
576    /// the slot has been accessed since the intern durability last
577    /// changed, because in that case there may be outstanding
578    /// references that are still considered valid. Note that this
579    /// access could be racing with the attempt to collect (in
580    /// particular, when verifying dependencies).
581    fn try_collect(&self, last_changed: Revision, revision_now: Revision) -> bool {
582        let accessed_at = self.accessed_at.load().unwrap();
583        if accessed_at < last_changed {
584            match self.accessed_at.compare_exchange(Some(accessed_at), None) {
585                Ok(_) => true,
586                Err(r) => {
587                    // The only one racing with us can be a
588                    // verification attempt, which will always bump
589                    // `accessed_at` to the current revision.
590                    debug_assert_eq!(r, Some(revision_now));
591                    false
592                }
593            }
594        } else {
595            false
596        }
597    }
598}
599
600/// Check that `Slot<Q, MP>: Send + Sync` as long as
601/// `DB::DatabaseData: Send + Sync`, which in turn implies that
602/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`.
603#[allow(dead_code)]
604fn check_send_sync<K>()
605where
606    K: Send + Sync,
607{
608    fn is_send_sync<T: Send + Sync>() {}
609    is_send_sync::<Slot<K>>();
610}
611
612/// Check that `Slot<Q, MP>: 'static` as long as
613/// `DB::DatabaseData: 'static`, which in turn implies that
614/// `Q::Key: 'static`, `Q::Value: 'static`.
615#[allow(dead_code)]
616fn check_static<K>()
617where
618    K: 'static,
619{
620    fn is_static<T: 'static>() {}
621    is_static::<Slot<K>>();
622}