| use crate::debug::TableEntry; |
| use crate::durability::Durability; |
| use crate::intern_id::InternId; |
| use crate::plumbing::CycleRecoveryStrategy; |
| use crate::plumbing::HasQueryGroup; |
| use crate::plumbing::QueryStorageMassOps; |
| use crate::plumbing::QueryStorageOps; |
| use crate::revision::Revision; |
| use crate::Query; |
| use crate::QueryTable; |
| use crate::{Database, DatabaseKeyIndex, QueryDb}; |
| use parking_lot::RwLock; |
| use rustc_hash::FxHashMap; |
| use std::collections::hash_map::Entry; |
| use std::fmt::Debug; |
| use std::hash::Hash; |
| use triomphe::Arc; |
| |
| const INTERN_DURABILITY: Durability = Durability::HIGH; |
| |
| /// Handles storage where the value is 'derived' by executing a |
| /// function (in contrast to "inputs"). |
| pub struct InternedStorage<Q> |
| where |
| Q: Query, |
| Q::Key: InternValue, |
| Q::Value: InternKey, |
| { |
| group_index: u16, |
| tables: RwLock<InternTables<MappedKey<Q>, Q::Key>>, |
| } |
| |
| /// Storage for the looking up interned things. |
| pub struct LookupInternedStorage<Q, IQ> |
| where |
| Q: Query, |
| Q::Key: InternKey, |
| Q::Value: InternValue, |
| { |
| phantom: std::marker::PhantomData<(Q::Key, IQ)>, |
| } |
| |
| struct InternTables<K, V> { |
| /// Map from the key to the corresponding intern-index. |
| map: FxHashMap<K, InternId>, |
| |
| /// For each valid intern-index, stores the interned value. |
| values: Vec<Arc<Slot<V>>>, |
| } |
| |
| /// Trait implemented for the "key" that results from a |
| /// `#[salsa::intern]` query. This is basically meant to be a |
| /// "newtype"'d `u32`. |
| pub trait InternKey { |
| /// Create an instance of the intern-key from a `u32` value. |
| fn from_intern_id(v: InternId) -> Self; |
| |
| /// Extract the `u32` with which the intern-key was created. |
| fn as_intern_id(&self) -> InternId; |
| } |
| |
| impl InternKey for InternId { |
| fn from_intern_id(v: InternId) -> InternId { |
| v |
| } |
| |
| fn as_intern_id(&self) -> InternId { |
| *self |
| } |
| } |
| |
| /// Trait implemented for the "value" that is being interned. |
| pub trait InternValue { |
| /// They key used to intern this value by. |
| type Key: Eq + Hash + Debug + Clone; |
| /// Maps the value to a key that will be used to intern it. |
| fn into_key(&self) -> Self::Key; |
| /// Calls the given function with the key that was used to intern this value. |
| /// |
| /// This is mainly used to prevent frequent cloning of the key when doing a lookup. |
| #[inline] |
| fn with_key<F: FnOnce(&Self::Key) -> T, T>(&self, f: F) -> T { |
| f(&self.into_key()) |
| } |
| } |
| |
| impl<A: InternValue + Eq + Hash + Debug + Clone, B: InternValue + Eq + Hash + Debug + Clone> |
| InternValue for (A, B) |
| { |
| type Key = Self; |
| #[inline] |
| fn into_key(&self) -> Self::Key { |
| self.clone() |
| } |
| #[inline] |
| fn with_key<F: FnOnce(&Self::Key) -> T, T>(&self, f: F) -> T { |
| f(self) |
| } |
| } |
| |
| pub trait InternValueTrivial |
| where |
| Self: Eq + Hash + Debug + Clone, |
| { |
| } |
| |
| /// Implement [`InternValue`] trivially, that is without actually mapping at all. |
| impl<V: InternValueTrivial> InternValue for V { |
| type Key = Self; |
| #[inline] |
| fn into_key(&self) -> Self::Key { |
| self.clone() |
| } |
| #[inline] |
| fn with_key<F: FnOnce(&Self::Key) -> T, T>(&self, f: F) -> T { |
| f(self) |
| } |
| } |
| |
| impl InternValueTrivial for String {} |
| |
| #[derive(Debug)] |
| struct Slot<V> { |
| /// key index for this slot. |
| key_index: u32, |
| |
| /// Value that was interned. |
| value: V, |
| |
| /// When was this intern'd? |
| /// |
| /// (This informs the "changed-at" result) |
| interned_at: Revision, |
| } |
| |
| impl<Q> std::panic::RefUnwindSafe for InternedStorage<Q> |
| where |
| Q: Query, |
| Q::Key: InternValue, |
| Q::Key: std::panic::RefUnwindSafe, |
| Q::Value: InternKey, |
| Q::Value: std::panic::RefUnwindSafe, |
| { |
| } |
| |
| impl<K: Debug + Hash + Eq, V> InternTables<K, V> { |
| /// Returns the slot for the given key. |
| fn slot_for_key(&self, key: &K) -> Option<(Arc<Slot<V>>, InternId)> { |
| let &index = self.map.get(key)?; |
| Some((self.slot_for_index(index), index)) |
| } |
| |
| /// Returns the slot at the given index. |
| fn slot_for_index(&self, index: InternId) -> Arc<Slot<V>> { |
| let slot = &self.values[index.as_usize()]; |
| slot.clone() |
| } |
| } |
| |
| impl<K, V> Default for InternTables<K, V> |
| where |
| K: Eq + Hash, |
| { |
| fn default() -> Self { |
| Self { map: Default::default(), values: Default::default() } |
| } |
| } |
| |
| type MappedKey<Q> = <<Q as Query>::Key as InternValue>::Key; |
| |
| impl<Q> InternedStorage<Q> |
| where |
| Q: Query, |
| Q::Key: InternValue, |
| Q::Value: InternKey, |
| { |
| /// Creates a new slot. |
| fn intern_index( |
| &self, |
| db: &<Q as QueryDb<'_>>::DynDb, |
| mapped_key: MappedKey<Q>, |
| insert: impl FnOnce(Q::Value) -> Q::Key, |
| ) -> (Arc<Slot<Q::Key>>, InternId) { |
| let revision_now = db.salsa_runtime().current_revision(); |
| |
| let mut tables = self.tables.write(); |
| let tables = &mut *tables; |
| let entry = match tables.map.entry(mapped_key) { |
| Entry::Vacant(entry) => entry, |
| Entry::Occupied(entry) => { |
| // Somebody inserted this key while we were waiting |
| // for the write lock. In this case, we don't need to |
| // update the `accessed_at` field because they should |
| // have already done so! |
| let index = *entry.get(); |
| let slot = &tables.values[index.as_usize()]; |
| return (slot.clone(), index); |
| } |
| }; |
| |
| let create_slot = |index: InternId| { |
| Arc::new(Slot { |
| key_index: index.as_u32(), |
| value: insert(Q::Value::from_intern_id(index)), |
| interned_at: revision_now, |
| }) |
| }; |
| |
| let index = InternId::from(tables.values.len()); |
| let slot = create_slot(index); |
| tables.values.push(slot.clone()); |
| entry.insert(index); |
| |
| (slot, index) |
| } |
| |
| fn intern_check(&self, key: &MappedKey<Q>) -> Option<(Arc<Slot<Q::Key>>, InternId)> { |
| self.tables.read().slot_for_key(key) |
| } |
| |
| /// Given an index, lookup and clone its value, updating the |
| /// `accessed_at` time if necessary. |
| fn lookup_value(&self, index: InternId) -> Arc<Slot<Q::Key>> { |
| self.tables.read().slot_for_index(index) |
| } |
| |
| fn fetch_or_insert( |
| &self, |
| db: &<Q as QueryDb<'_>>::DynDb, |
| key: MappedKey<Q>, |
| insert: impl FnOnce(Q::Value) -> Q::Key, |
| ) -> Q::Value { |
| db.unwind_if_cancelled(); |
| let (slot, index) = match self.intern_check(&key) { |
| Some(i) => i, |
| None => self.intern_index(db, key, insert), |
| }; |
| let changed_at = slot.interned_at; |
| db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( |
| DatabaseKeyIndex { |
| group_index: self.group_index, |
| query_index: Q::QUERY_INDEX, |
| key_index: slot.key_index, |
| }, |
| INTERN_DURABILITY, |
| changed_at, |
| ); |
| <Q::Value>::from_intern_id(index) |
| } |
| } |
| |
| impl<Q> QueryStorageOps<Q> for InternedStorage<Q> |
| where |
| Q: Query, |
| Q::Key: InternValue, |
| Q::Value: InternKey, |
| { |
| const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = CycleRecoveryStrategy::Panic; |
| |
| fn new(group_index: u16) -> Self { |
| InternedStorage { group_index, tables: RwLock::new(InternTables::default()) } |
| } |
| |
| fn fmt_index( |
| &self, |
| _db: &<Q as QueryDb<'_>>::DynDb, |
| index: u32, |
| fmt: &mut std::fmt::Formatter<'_>, |
| ) -> std::fmt::Result { |
| let intern_id = InternId::from(index); |
| let slot = self.lookup_value(intern_id); |
| write!(fmt, "{}({:?})", Q::QUERY_NAME, slot.value) |
| } |
| |
| fn maybe_changed_after( |
| &self, |
| db: &<Q as QueryDb<'_>>::DynDb, |
| input: u32, |
| revision: Revision, |
| ) -> bool { |
| debug_assert!(revision < db.salsa_runtime().current_revision()); |
| let intern_id = InternId::from(input); |
| let slot = self.lookup_value(intern_id); |
| slot.maybe_changed_after(revision) |
| } |
| |
| fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Q::Value { |
| db.unwind_if_cancelled(); |
| |
| let (slot, index) = match key.with_key(|key| self.intern_check(key)) { |
| Some(i) => i, |
| None => self.intern_index(db, key.into_key(), |_| key.clone()), |
| }; |
| let changed_at = slot.interned_at; |
| db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( |
| DatabaseKeyIndex { |
| group_index: self.group_index, |
| query_index: Q::QUERY_INDEX, |
| key_index: slot.key_index, |
| }, |
| INTERN_DURABILITY, |
| changed_at, |
| ); |
| <Q::Value>::from_intern_id(index) |
| } |
| |
| fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability { |
| INTERN_DURABILITY |
| } |
| |
| fn entries<C>(&self, _db: &<Q as QueryDb<'_>>::DynDb) -> C |
| where |
| C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>, |
| { |
| let tables = self.tables.read(); |
| tables |
| .map |
| .values() |
| .map(|index| { |
| TableEntry::new( |
| tables.values[index.as_usize()].value.clone(), |
| Some(<Q::Value>::from_intern_id(*index)), |
| ) |
| }) |
| .collect() |
| } |
| } |
| |
| impl<Q> QueryStorageMassOps for InternedStorage<Q> |
| where |
| Q: Query, |
| Q::Key: InternValue, |
| Q::Value: InternKey, |
| { |
| fn purge(&self) { |
| *self.tables.write() = Default::default(); |
| } |
| } |
| |
| // Workaround for |
| // ``` |
| // IQ: for<'d> QueryDb< |
| // 'd, |
| // DynDb = <Q as QueryDb<'d>>::DynDb, |
| // Group = <Q as QueryDb<'d>>::Group, |
| // GroupStorage = <Q as QueryDb<'d>>::GroupStorage, |
| // >, |
| // ``` |
| // not working to make rustc know DynDb, Group and GroupStorage being the same in `Q` and `IQ` |
| #[doc(hidden)] |
| pub trait EqualDynDb<'d, IQ>: QueryDb<'d> |
| where |
| IQ: QueryDb<'d>, |
| { |
| fn convert_db(d: &Self::DynDb) -> &IQ::DynDb; |
| fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage; |
| } |
| |
| impl<'d, IQ, Q> EqualDynDb<'d, IQ> for Q |
| where |
| Q: QueryDb<'d, DynDb = IQ::DynDb, Group = IQ::Group, GroupStorage = IQ::GroupStorage>, |
| Q::DynDb: HasQueryGroup<Q::Group>, |
| IQ: QueryDb<'d>, |
| { |
| fn convert_db(d: &Self::DynDb) -> &IQ::DynDb { |
| d |
| } |
| fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage { |
| d |
| } |
| } |
| |
| impl<Q, IQ> QueryStorageOps<Q> for LookupInternedStorage<Q, IQ> |
| where |
| Q: Query, |
| Q::Key: InternKey, |
| Q::Value: InternValue, |
| IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>, |
| for<'d> Q: EqualDynDb<'d, IQ>, |
| { |
| const CYCLE_STRATEGY: CycleRecoveryStrategy = CycleRecoveryStrategy::Panic; |
| |
| fn new(_group_index: u16) -> Self { |
| LookupInternedStorage { phantom: std::marker::PhantomData } |
| } |
| |
| fn fmt_index( |
| &self, |
| db: &<Q as QueryDb<'_>>::DynDb, |
| index: u32, |
| fmt: &mut std::fmt::Formatter<'_>, |
| ) -> std::fmt::Result { |
| let group_storage = |
| <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db); |
| let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); |
| interned_storage.fmt_index(Q::convert_db(db), index, fmt) |
| } |
| |
| fn maybe_changed_after( |
| &self, |
| db: &<Q as QueryDb<'_>>::DynDb, |
| input: u32, |
| revision: Revision, |
| ) -> bool { |
| let group_storage = |
| <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db); |
| let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); |
| interned_storage.maybe_changed_after(Q::convert_db(db), input, revision) |
| } |
| |
| fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Q::Value { |
| let index = key.as_intern_id(); |
| let group_storage = |
| <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db); |
| let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); |
| let slot = interned_storage.lookup_value(index); |
| let value = slot.value.clone(); |
| let interned_at = slot.interned_at; |
| db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted( |
| DatabaseKeyIndex { |
| group_index: interned_storage.group_index, |
| query_index: Q::QUERY_INDEX, |
| key_index: slot.key_index, |
| }, |
| INTERN_DURABILITY, |
| interned_at, |
| ); |
| value |
| } |
| |
| fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability { |
| INTERN_DURABILITY |
| } |
| |
| fn entries<C>(&self, db: &<Q as QueryDb<'_>>::DynDb) -> C |
| where |
| C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>, |
| { |
| let group_storage = |
| <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db); |
| let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)); |
| let tables = interned_storage.tables.read(); |
| tables |
| .map |
| .values() |
| .map(|index| { |
| TableEntry::new( |
| <Q::Key>::from_intern_id(*index), |
| Some(tables.values[index.as_usize()].value.clone()), |
| ) |
| }) |
| .collect() |
| } |
| } |
| |
| impl<Q, IQ> QueryStorageMassOps for LookupInternedStorage<Q, IQ> |
| where |
| Q: Query, |
| Q::Key: InternKey, |
| Q::Value: InternValue, |
| IQ: Query<Key = Q::Value, Value = Q::Key>, |
| { |
| fn purge(&self) {} |
| } |
| |
| impl<K> Slot<K> { |
| fn maybe_changed_after(&self, revision: Revision) -> bool { |
| self.interned_at > revision |
| } |
| } |
| |
| /// Check that `Slot<Q, MP>: Send + Sync` as long as |
| /// `DB::DatabaseData: Send + Sync`, which in turn implies that |
| /// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`. |
| #[allow(dead_code)] |
| fn check_send_sync<K>() |
| where |
| K: Send + Sync, |
| { |
| fn is_send_sync<T: Send + Sync>() {} |
| is_send_sync::<Slot<K>>(); |
| } |
| |
| /// Check that `Slot<Q, MP>: 'static` as long as |
| /// `DB::DatabaseData: 'static`, which in turn implies that |
| /// `Q::Key: 'static`, `Q::Value: 'static`. |
| #[allow(dead_code)] |
| fn check_static<K>() |
| where |
| K: 'static, |
| { |
| fn is_static<T: 'static>() {} |
| is_static::<Slot<K>>(); |
| } |
| |
| impl<'me, Q> QueryTable<'me, Q> |
| where |
| Q: Query<Storage = InternedStorage<Q>>, |
| Q::Key: InternValue, |
| Q::Value: InternKey, |
| { |
| /// Fetches the intern id for the given key or inserts it if it does not exist. |
| pub fn get_or_insert( |
| &self, |
| key: MappedKey<Q>, |
| insert: impl FnOnce(Q::Value) -> Q::Key, |
| ) -> Q::Value { |
| self.storage.fetch_or_insert(self.db, key, insert) |
| } |
| } |