blob: 0255e3bcc76d8a816dee92b9827f969245c9751c [file] [log] [blame]
use crate::debug::TableEntry;
use crate::dependency::DatabaseSlot;
use crate::durability::Durability;
use crate::plumbing::InputQueryStorageOps;
use crate::plumbing::QueryStorageMassOps;
use crate::plumbing::QueryStorageOps;
use crate::revision::Revision;
use crate::runtime::StampedValue;
use crate::CycleError;
use crate::Database;
use crate::Event;
use crate::EventKind;
use crate::Query;
use crate::SweepStrategy;
use log::debug;
use parking_lot::RwLock;
use rustc_hash::FxHashMap;
use std::collections::hash_map::Entry;
use std::sync::Arc;
/// Input queries store the result plus a list of the other queries
/// that they invoked. This means we can avoid recomputing them when
/// none of those inputs have changed.
pub struct InputStorage<DB, Q>
where
Q: Query<DB>,
DB: Database,
{
slots: RwLock<FxHashMap<Q::Key, Arc<Slot<DB, Q>>>>,
}
struct Slot<DB, Q>
where
Q: Query<DB>,
DB: Database,
{
key: Q::Key,
stamped_value: RwLock<StampedValue<Q::Value>>,
}
impl<DB, Q> std::panic::RefUnwindSafe for InputStorage<DB, Q>
where
Q: Query<DB>,
DB: Database,
Q::Key: std::panic::RefUnwindSafe,
Q::Value: std::panic::RefUnwindSafe,
{
}
impl<DB, Q> Default for InputStorage<DB, Q>
where
Q: Query<DB>,
DB: Database,
{
fn default() -> Self {
InputStorage {
slots: Default::default(),
}
}
}
impl<DB, Q> InputStorage<DB, Q>
where
Q: Query<DB>,
DB: Database,
{
fn slot(&self, key: &Q::Key) -> Option<Arc<Slot<DB, Q>>> {
self.slots.read().get(key).cloned()
}
}
impl<DB, Q> QueryStorageOps<DB, Q> for InputStorage<DB, Q>
where
Q: Query<DB>,
DB: Database,
{
fn try_fetch(&self, db: &DB, key: &Q::Key) -> Result<Q::Value, CycleError<DB::DatabaseKey>> {
let slot = self
.slot(key)
.unwrap_or_else(|| panic!("no value set for {:?}({:?})", Q::default(), key));
let StampedValue {
value,
durability,
changed_at,
} = slot.stamped_value.read().clone();
db.salsa_runtime()
.report_query_read(slot, durability, changed_at);
Ok(value)
}
fn durability(&self, _db: &DB, key: &Q::Key) -> Durability {
match self.slot(key) {
Some(slot) => slot.stamped_value.read().durability,
None => panic!("no value set for {:?}({:?})", Q::default(), key),
}
}
fn entries<C>(&self, _db: &DB) -> C
where
C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
{
let slots = self.slots.read();
slots
.values()
.map(|slot| {
TableEntry::new(
slot.key.clone(),
Some(slot.stamped_value.read().value.clone()),
)
})
.collect()
}
}
impl<DB, Q> QueryStorageMassOps<DB> for InputStorage<DB, Q>
where
Q: Query<DB>,
DB: Database,
{
fn sweep(&self, _db: &DB, _strategy: SweepStrategy) {}
}
impl<DB, Q> InputQueryStorageOps<DB, Q> for InputStorage<DB, Q>
where
Q: Query<DB>,
DB: Database,
{
fn set(
&self,
db: &mut DB,
key: &Q::Key,
database_key: &DB::DatabaseKey,
value: Q::Value,
durability: Durability,
) {
log::debug!(
"{:?}({:?}) = {:?} ({:?})",
Q::default(),
key,
value,
durability
);
db.salsa_event(|| Event {
runtime_id: db.salsa_runtime().id(),
kind: EventKind::WillChangeInputValue {
database_key: database_key.clone(),
},
});
// The value is changing, so we need a new revision (*). We also
// need to update the 'last changed' revision by invoking
// `guard.mark_durability_as_changed`.
//
// CAREFUL: This will block until the global revision lock can
// be acquired. If there are still queries executing, they may
// need to read from this input. Therefore, we wait to acquire
// the lock on `map` until we also hold the global query write
// lock.
//
// (*) Technically, since you can't presently access an input
// for a non-existent key, and you can't enumerate the set of
// keys, we only need a new revision if the key used to
// exist. But we may add such methods in the future and this
// case doesn't generally seem worth optimizing for.
db.salsa_runtime_mut().with_incremented_revision(|guard| {
let mut slots = self.slots.write();
// Do this *after* we acquire the lock, so that we are not
// racing with somebody else to modify this same cell.
// (Otherwise, someone else might write a *newer* revision
// into the same cell while we block on the lock.)
let stamped_value = StampedValue {
value,
durability,
changed_at: guard.new_revision(),
};
match slots.entry(key.clone()) {
Entry::Occupied(entry) => {
let mut slot_stamped_value = entry.get().stamped_value.write();
guard.mark_durability_as_changed(slot_stamped_value.durability);
*slot_stamped_value = stamped_value;
}
Entry::Vacant(entry) => {
entry.insert(Arc::new(Slot {
key: key.clone(),
stamped_value: RwLock::new(stamped_value),
}));
}
}
});
}
}
// Unsafe proof obligation: `Slot<DB, Q>` is Send + Sync if the query
// key/value is Send + Sync (also, that we introduce no
// references). These are tested by the `check_send_sync` and
// `check_static` helpers below.
unsafe impl<DB, Q> DatabaseSlot<DB> for Slot<DB, Q>
where
Q: Query<DB>,
DB: Database,
{
fn maybe_changed_since(&self, _db: &DB, revision: Revision) -> bool {
debug!(
"maybe_changed_since(slot={:?}, revision={:?})",
self, revision,
);
let changed_at = self.stamped_value.read().changed_at;
debug!("maybe_changed_since: changed_at = {:?}", changed_at);
changed_at > revision
}
}
/// Check that `Slot<DB, 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<DB, Q>()
where
Q: Query<DB>,
DB: Database,
DB::DatabaseData: Send + Sync,
Q::Key: Send + Sync,
Q::Value: Send + Sync,
{
fn is_send_sync<T: Send + Sync>() {}
is_send_sync::<Slot<DB, Q>>();
}
/// Check that `Slot<DB, 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<DB, Q>()
where
Q: Query<DB>,
DB: Database,
DB: 'static,
DB::DatabaseData: 'static,
Q::Key: 'static,
Q::Value: 'static,
{
fn is_static<T: 'static>() {}
is_static::<Slot<DB, Q>>();
}
impl<DB, Q> std::fmt::Debug for Slot<DB, Q>
where
Q: Query<DB>,
DB: Database,
{
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "{:?}({:?})", Q::default(), self.key)
}
}