mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-02-02 09:46:06 +00:00
Merge pull request #14 from nikomatsakis/dep-only
Add support for "dependency only" tracking
This commit is contained in:
commit
e18f14fca5
9 changed files with 432 additions and 83 deletions
247
src/dependencies.rs
Normal file
247
src/dependencies.rs
Normal file
|
@ -0,0 +1,247 @@
|
|||
use crate::runtime::QueryDescriptorSet;
|
||||
use crate::runtime::Revision;
|
||||
use crate::runtime::StampedValue;
|
||||
use crate::CycleDetected;
|
||||
use crate::Query;
|
||||
use crate::QueryContext;
|
||||
use crate::QueryDescriptor;
|
||||
use crate::QueryStorageOps;
|
||||
use crate::QueryTable;
|
||||
use log::debug;
|
||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Write;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// "Dependency" queries just track their dependencies and not the
|
||||
/// actual value (which they produce on demand). This lessens the
|
||||
/// storage requirements.
|
||||
pub struct DependencyStorage<QC, Q>
|
||||
where
|
||||
Q: Query<QC>,
|
||||
QC: QueryContext,
|
||||
{
|
||||
map: RwLock<FxHashMap<Q::Key, QueryState<QC>>>,
|
||||
}
|
||||
|
||||
/// Defines the "current state" of query's memoized results.
|
||||
enum QueryState<QC>
|
||||
where
|
||||
QC: QueryContext,
|
||||
{
|
||||
/// We are currently computing the result of this query; if we see
|
||||
/// this value in the table, it indeeds a cycle.
|
||||
InProgress,
|
||||
|
||||
/// We have computed the query already, and here is the result.
|
||||
Memoized(Memo<QC>),
|
||||
}
|
||||
|
||||
struct Memo<QC>
|
||||
where
|
||||
QC: QueryContext,
|
||||
{
|
||||
inputs: QueryDescriptorSet<QC>,
|
||||
|
||||
/// Last time that we checked our inputs to see if they have
|
||||
/// changed. If this is equal to the current revision, then the
|
||||
/// value is up to date. If not, we need to check our inputs and
|
||||
/// see if any of them have changed since our last check -- if so,
|
||||
/// we'll need to re-execute.
|
||||
verified_at: Revision,
|
||||
|
||||
/// Last time that our value changed.
|
||||
changed_at: Revision,
|
||||
}
|
||||
|
||||
impl<QC, Q> Default for DependencyStorage<QC, Q>
|
||||
where
|
||||
Q: Query<QC>,
|
||||
QC: QueryContext,
|
||||
{
|
||||
fn default() -> Self {
|
||||
DependencyStorage {
|
||||
map: RwLock::new(FxHashMap::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<QC, Q> DependencyStorage<QC, Q>
|
||||
where
|
||||
Q: Query<QC>,
|
||||
QC: QueryContext,
|
||||
{
|
||||
fn read(
|
||||
&self,
|
||||
query: &QC,
|
||||
key: &Q::Key,
|
||||
descriptor: &QC::QueryDescriptor,
|
||||
) -> Result<StampedValue<Q::Value>, CycleDetected> {
|
||||
let revision_now = query.salsa_runtime().current_revision();
|
||||
|
||||
debug!(
|
||||
"{:?}({:?}): invoked at {:?}",
|
||||
Q::default(),
|
||||
key,
|
||||
revision_now,
|
||||
);
|
||||
|
||||
{
|
||||
let map_read = self.map.upgradable_read();
|
||||
if let Some(value) = map_read.get(key) {
|
||||
match value {
|
||||
QueryState::InProgress => return Err(CycleDetected),
|
||||
QueryState::Memoized(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
let mut map_write = RwLockUpgradableReadGuard::upgrade(map_read);
|
||||
map_write.insert(key.clone(), QueryState::InProgress);
|
||||
}
|
||||
|
||||
// Note that, unlike with a memoized query, we must always
|
||||
// re-execute.
|
||||
let (stamped_value, inputs) = query
|
||||
.salsa_runtime()
|
||||
.execute_query_implementation::<Q>(query, descriptor, key);
|
||||
|
||||
// We assume that query is side-effect free -- that is, does
|
||||
// not mutate the "inputs" to the query system. Sanity check
|
||||
// that assumption here, at least to the best of our ability.
|
||||
assert_eq!(
|
||||
query.salsa_runtime().current_revision(),
|
||||
revision_now,
|
||||
"revision altered during query execution",
|
||||
);
|
||||
|
||||
{
|
||||
let mut map_write = self.map.write();
|
||||
|
||||
let old_value = map_write.insert(
|
||||
key.clone(),
|
||||
QueryState::Memoized(Memo {
|
||||
inputs,
|
||||
verified_at: revision_now,
|
||||
changed_at: stamped_value.changed_at,
|
||||
}),
|
||||
);
|
||||
assert!(
|
||||
match old_value {
|
||||
Some(QueryState::InProgress) => true,
|
||||
_ => false,
|
||||
},
|
||||
"expected in-progress state",
|
||||
);
|
||||
}
|
||||
|
||||
Ok(stamped_value)
|
||||
}
|
||||
|
||||
fn overwrite_placeholder(
|
||||
&self,
|
||||
map_write: &mut FxHashMap<Q::Key, QueryState<QC>>,
|
||||
key: &Q::Key,
|
||||
value: Option<QueryState<QC>>,
|
||||
) {
|
||||
let old_value = if let Some(v) = value {
|
||||
map_write.insert(key.clone(), v)
|
||||
} else {
|
||||
map_write.remove(key)
|
||||
};
|
||||
|
||||
assert!(
|
||||
match old_value {
|
||||
Some(QueryState::InProgress) => true,
|
||||
_ => false,
|
||||
},
|
||||
"expected in-progress state",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<QC, Q> QueryStorageOps<QC, Q> for DependencyStorage<QC, Q>
|
||||
where
|
||||
Q: Query<QC>,
|
||||
QC: QueryContext,
|
||||
{
|
||||
fn try_fetch<'q>(
|
||||
&self,
|
||||
query: &'q QC,
|
||||
key: &Q::Key,
|
||||
descriptor: &QC::QueryDescriptor,
|
||||
) -> Result<Q::Value, CycleDetected> {
|
||||
let StampedValue { value, changed_at } = self.read(query, key, &descriptor)?;
|
||||
|
||||
query
|
||||
.salsa_runtime()
|
||||
.report_query_read(descriptor, changed_at);
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn maybe_changed_since(
|
||||
&self,
|
||||
query: &'q QC,
|
||||
revision: Revision,
|
||||
key: &Q::Key,
|
||||
_descriptor: &QC::QueryDescriptor,
|
||||
) -> bool {
|
||||
let revision_now = query.salsa_runtime().current_revision();
|
||||
|
||||
debug!(
|
||||
"{:?}({:?})::maybe_changed_since(revision={:?}, revision_now={:?})",
|
||||
Q::default(),
|
||||
key,
|
||||
revision,
|
||||
revision_now,
|
||||
);
|
||||
|
||||
let value = {
|
||||
let map_read = self.map.upgradable_read();
|
||||
match map_read.get(key) {
|
||||
None | Some(QueryState::InProgress) => return true,
|
||||
Some(QueryState::Memoized(memo)) => {
|
||||
// If our memo is still up to date, then check if we've
|
||||
// changed since the revision.
|
||||
if memo.verified_at == revision_now {
|
||||
return memo.changed_at > revision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut map_write = RwLockUpgradableReadGuard::upgrade(map_read);
|
||||
map_write.insert(key.clone(), QueryState::InProgress)
|
||||
};
|
||||
|
||||
// Otherwise, walk the inputs we had and check them. Note that
|
||||
// we don't want to hold the lock while we do this.
|
||||
let mut memo = match value {
|
||||
Some(QueryState::Memoized(memo)) => memo,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if memo
|
||||
.inputs
|
||||
.iter()
|
||||
.all(|old_input| !old_input.maybe_changed_since(query, memo.verified_at))
|
||||
{
|
||||
memo.verified_at = revision_now;
|
||||
self.overwrite_placeholder(
|
||||
&mut self.map.write(),
|
||||
key,
|
||||
Some(QueryState::Memoized(memo)),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Just remove the existing entry. It's out of date.
|
||||
self.overwrite_placeholder(&mut self.map.write(), key, None);
|
||||
|
||||
true
|
||||
}
|
||||
}
|
16
src/input.rs
16
src/input.rs
|
@ -1,5 +1,6 @@
|
|||
use crate::runtime::QueryDescriptorSet;
|
||||
use crate::runtime::Revision;
|
||||
use crate::runtime::StampedValue;
|
||||
use crate::CycleDetected;
|
||||
use crate::MutQueryStorageOps;
|
||||
use crate::Query;
|
||||
|
@ -81,12 +82,11 @@ where
|
|||
key: &Q::Key,
|
||||
descriptor: &QC::QueryDescriptor,
|
||||
) -> Result<Q::Value, CycleDetected> {
|
||||
let StampedValue {
|
||||
value,
|
||||
changed_at: _,
|
||||
} = self.read(query, key, &descriptor)?;
|
||||
let StampedValue { value, changed_at } = self.read(query, key, &descriptor)?;
|
||||
|
||||
query.salsa_runtime().report_query_read(descriptor);
|
||||
query
|
||||
.salsa_runtime()
|
||||
.report_query_read(descriptor, changed_at);
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
@ -137,9 +137,3 @@ where
|
|||
map_write.insert(key, StampedValue { value, changed_at });
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct StampedValue<V> {
|
||||
value: V,
|
||||
changed_at: Revision,
|
||||
}
|
||||
|
|
23
src/lib.rs
23
src/lib.rs
|
@ -16,6 +16,7 @@ use std::fmt::Display;
|
|||
use std::fmt::Write;
|
||||
use std::hash::Hash;
|
||||
|
||||
pub mod dependencies;
|
||||
pub mod input;
|
||||
pub mod memoized;
|
||||
pub mod runtime;
|
||||
|
@ -304,6 +305,22 @@ macro_rules! query_definition {
|
|||
}
|
||||
};
|
||||
|
||||
(
|
||||
@filter_attrs {
|
||||
input { #[storage(dependencies)] $($input:tt)* };
|
||||
storage { $storage:tt };
|
||||
other_attrs { $($other_attrs:tt)* };
|
||||
}
|
||||
) => {
|
||||
$crate::query_definition! {
|
||||
@filter_attrs {
|
||||
input { $($input)* };
|
||||
storage { dependencies };
|
||||
other_attrs { $($other_attrs)* };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
@filter_attrs {
|
||||
input { #[$attr:meta] $($input:tt)* };
|
||||
|
@ -371,6 +388,12 @@ macro_rules! query_definition {
|
|||
$crate::volatile::VolatileStorage<$QC, $Self>
|
||||
};
|
||||
|
||||
(
|
||||
@storage_ty[$QC:ident, $Self:ident, dependencies]
|
||||
) => {
|
||||
$crate::dependencies::DependencyStorage<$QC, $Self>
|
||||
};
|
||||
|
||||
// Accept a "field-like" query definition (input)
|
||||
(
|
||||
@filter_attrs {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::runtime::QueryDescriptorSet;
|
||||
use crate::runtime::Revision;
|
||||
use crate::runtime::StampedValue;
|
||||
use crate::CycleDetected;
|
||||
use crate::Query;
|
||||
use crate::QueryContext;
|
||||
|
@ -47,7 +48,7 @@ where
|
|||
Q: Query<QC>,
|
||||
QC: QueryContext,
|
||||
{
|
||||
value: Q::Value,
|
||||
stamped_value: StampedValue<Q::Value>,
|
||||
|
||||
inputs: QueryDescriptorSet<QC>,
|
||||
|
||||
|
@ -57,27 +58,6 @@ where
|
|||
/// see if any of them have changed since our last check -- if so,
|
||||
/// we'll need to re-execute.
|
||||
verified_at: Revision,
|
||||
|
||||
/// Last time that our value changed.
|
||||
changed_at: Revision,
|
||||
}
|
||||
|
||||
impl<QC, Q> Memo<QC, Q>
|
||||
where
|
||||
Q: Query<QC>,
|
||||
QC: QueryContext,
|
||||
{
|
||||
fn stamped_value(&self) -> StampedValue<Q::Value> {
|
||||
StampedValue {
|
||||
value: self.value.clone(),
|
||||
changed_at: self.changed_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StampedValue<V> {
|
||||
value: V,
|
||||
changed_at: Revision,
|
||||
}
|
||||
|
||||
impl<QC, Q> Default for MemoizedStorage<QC, Q>
|
||||
|
@ -130,10 +110,10 @@ where
|
|||
"{:?}({:?}): returning memoized value (changed_at={:?})",
|
||||
Q::default(),
|
||||
key,
|
||||
m.changed_at,
|
||||
m.stamped_value.changed_at,
|
||||
);
|
||||
|
||||
return Ok(m.stamped_value());
|
||||
return Ok(m.stamped_value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -159,24 +139,17 @@ where
|
|||
// our value, then our value must still be good. We'll just patch
|
||||
// the verified-at date and re-use it.
|
||||
old_memo.verified_at = revision_now;
|
||||
let stamped_value = old_memo.stamped_value();
|
||||
let stamped_value = old_memo.stamped_value.clone();
|
||||
|
||||
let mut map_write = self.map.write();
|
||||
let placeholder = map_write.insert(key.clone(), old_value.unwrap());
|
||||
assert!(
|
||||
match placeholder {
|
||||
Some(QueryState::InProgress) => true,
|
||||
_ => false,
|
||||
},
|
||||
"expected in-progress state",
|
||||
);
|
||||
self.overwrite_placeholder(&mut map_write, key, old_value.unwrap());
|
||||
return Ok(stamped_value);
|
||||
}
|
||||
}
|
||||
|
||||
// Query was not previously executed or value is potentially
|
||||
// stale. Let's execute!
|
||||
let (value, inputs) = query
|
||||
let (mut stamped_value, inputs) = query
|
||||
.salsa_runtime()
|
||||
.execute_query_implementation::<Q>(query, descriptor, key);
|
||||
|
||||
|
@ -191,37 +164,45 @@ where
|
|||
|
||||
// If the new value is equal to the old one, then it didn't
|
||||
// really change, even if some of its inputs have. So we can
|
||||
// "backdate" our `changed_at` revision to be the same as the
|
||||
// "backdate" its `changed_at` revision to be the same as the
|
||||
// old value.
|
||||
let mut changed_at = revision_now;
|
||||
if let Some(QueryState::Memoized(old_memo)) = &old_value {
|
||||
if old_memo.value == value {
|
||||
changed_at = old_memo.changed_at;
|
||||
if old_memo.stamped_value.value == stamped_value.value {
|
||||
assert!(old_memo.stamped_value.changed_at <= stamped_value.changed_at);
|
||||
stamped_value.changed_at = old_memo.stamped_value.changed_at;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut map_write = self.map.write();
|
||||
|
||||
let old_value = map_write.insert(
|
||||
key.clone(),
|
||||
self.overwrite_placeholder(
|
||||
&mut map_write,
|
||||
key,
|
||||
QueryState::Memoized(Memo {
|
||||
value: value.clone(),
|
||||
stamped_value: stamped_value.clone(),
|
||||
inputs,
|
||||
verified_at: revision_now,
|
||||
changed_at,
|
||||
}),
|
||||
);
|
||||
assert!(
|
||||
match old_value {
|
||||
Some(QueryState::InProgress) => true,
|
||||
_ => false,
|
||||
},
|
||||
"expected in-progress state",
|
||||
);
|
||||
}
|
||||
|
||||
Ok(StampedValue { value, changed_at })
|
||||
Ok(stamped_value)
|
||||
}
|
||||
|
||||
fn overwrite_placeholder(
|
||||
&self,
|
||||
map_write: &mut FxHashMap<Q::Key, QueryState<QC, Q>>,
|
||||
key: &Q::Key,
|
||||
value: QueryState<QC, Q>,
|
||||
) {
|
||||
let old_value = map_write.insert(key.clone(), value);
|
||||
assert!(
|
||||
match old_value {
|
||||
Some(QueryState::InProgress) => true,
|
||||
_ => false,
|
||||
},
|
||||
"expected in-progress state",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,12 +217,11 @@ where
|
|||
key: &Q::Key,
|
||||
descriptor: &QC::QueryDescriptor,
|
||||
) -> Result<Q::Value, CycleDetected> {
|
||||
let StampedValue {
|
||||
value,
|
||||
changed_at: _,
|
||||
} = self.read(query, key, &descriptor)?;
|
||||
let StampedValue { value, changed_at } = self.read(query, key, &descriptor)?;
|
||||
|
||||
query.salsa_runtime().report_query_read(descriptor);
|
||||
query
|
||||
.salsa_runtime()
|
||||
.report_query_read(descriptor, changed_at);
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
@ -271,7 +251,7 @@ where
|
|||
None | Some(QueryState::InProgress) => return true,
|
||||
Some(QueryState::Memoized(memo)) => {
|
||||
if memo.verified_at >= revision_now {
|
||||
return memo.changed_at > revision;
|
||||
return memo.stamped_value.changed_at > revision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ where
|
|||
query: &QC,
|
||||
descriptor: &QC::QueryDescriptor,
|
||||
key: &Q::Key,
|
||||
) -> (Q::Value, QueryDescriptorSet<QC>)
|
||||
) -> (StampedValue<Q::Value>, QueryDescriptorSet<QC>)
|
||||
where
|
||||
Q: Query<QC>,
|
||||
{
|
||||
|
@ -111,7 +111,11 @@ where
|
|||
let value = Q::execute(query, key.clone());
|
||||
|
||||
// Extract accumulated inputs.
|
||||
let ActiveQuery { subqueries, .. } = {
|
||||
let ActiveQuery {
|
||||
subqueries,
|
||||
changed_at,
|
||||
..
|
||||
} = {
|
||||
let mut local_state = self.local_state.borrow_mut();
|
||||
|
||||
// Sanity check: pushes and pops should be balanced.
|
||||
|
@ -120,7 +124,7 @@ where
|
|||
local_state.query_stack.pop().unwrap()
|
||||
};
|
||||
|
||||
(value, subqueries)
|
||||
(StampedValue { value, changed_at }, subqueries)
|
||||
}
|
||||
|
||||
/// Reports that the currently active query read the result from
|
||||
|
@ -131,9 +135,9 @@ where
|
|||
/// - `descriptor`: the query whose result was read
|
||||
/// - `changed_revision`: the last revision in which the result of that
|
||||
/// query had changed
|
||||
crate fn report_query_read(&self, descriptor: &QC::QueryDescriptor) {
|
||||
crate fn report_query_read(&self, descriptor: &QC::QueryDescriptor, changed_at: Revision) {
|
||||
if let Some(top_query) = self.local_state.borrow_mut().query_stack.last_mut() {
|
||||
top_query.add_read(descriptor);
|
||||
top_query.add_read(descriptor, changed_at);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,6 +176,9 @@ struct ActiveQuery<QC: QueryContext> {
|
|||
/// What query is executing
|
||||
descriptor: QC::QueryDescriptor,
|
||||
|
||||
/// Records the maximum revision where any subquery changed
|
||||
changed_at: Revision,
|
||||
|
||||
/// Each subquery
|
||||
subqueries: QueryDescriptorSet<QC>,
|
||||
}
|
||||
|
@ -180,12 +187,14 @@ impl<QC: QueryContext> ActiveQuery<QC> {
|
|||
fn new(descriptor: QC::QueryDescriptor) -> Self {
|
||||
ActiveQuery {
|
||||
descriptor,
|
||||
changed_at: Revision::ZERO,
|
||||
subqueries: QueryDescriptorSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_read(&mut self, subquery: &QC::QueryDescriptor) {
|
||||
fn add_read(&mut self, subquery: &QC::QueryDescriptor, changed_at: Revision) {
|
||||
self.subqueries.insert(subquery.clone());
|
||||
self.changed_at = self.changed_at.max(changed_at);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,3 +244,9 @@ impl<QC: QueryContext> QueryDescriptorSet<QC> {
|
|||
self.set.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
crate struct StampedValue<V> {
|
||||
crate value: V,
|
||||
crate changed_at: Revision,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::runtime::Revision;
|
||||
use crate::runtime::StampedValue;
|
||||
use crate::CycleDetected;
|
||||
use crate::Query;
|
||||
use crate::QueryContext;
|
||||
|
@ -54,16 +55,24 @@ where
|
|||
return Err(CycleDetected);
|
||||
}
|
||||
|
||||
// FIXME: Should we even call `execute_query_implementation`
|
||||
// here? Or should we just call `Q::execute`, and maybe
|
||||
// separate out the `push`/`pop` operations.
|
||||
let (value, _inputs) = query
|
||||
let (
|
||||
StampedValue {
|
||||
value,
|
||||
changed_at: _,
|
||||
},
|
||||
_inputs,
|
||||
) = query
|
||||
.salsa_runtime()
|
||||
.execute_query_implementation::<Q>(query, descriptor, key);
|
||||
|
||||
let was_in_progress = self.in_progress.lock().remove(key);
|
||||
assert!(was_in_progress);
|
||||
|
||||
query.salsa_runtime().report_query_read(descriptor);
|
||||
let revision_now = query.salsa_runtime().current_revision();
|
||||
|
||||
query
|
||||
.salsa_runtime()
|
||||
.report_query_read(descriptor, revision_now);
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::counter::Counter;
|
||||
use crate::log::Log;
|
||||
use crate::memoized_dep_inputs;
|
||||
use crate::memoized_inputs;
|
||||
use crate::memoized_volatile;
|
||||
|
||||
|
@ -42,10 +43,12 @@ impl TestContextImpl {
|
|||
|
||||
salsa::query_context_storage! {
|
||||
crate struct TestContextImplStorage for TestContextImpl {
|
||||
impl memoized_volatile::MemoizedVolatileContext {
|
||||
fn memoized2() for memoized_volatile::Memoized2;
|
||||
fn memoized1() for memoized_volatile::Memoized1;
|
||||
fn volatile() for memoized_volatile::Volatile;
|
||||
impl memoized_dep_inputs::MemoizedDepInputsContext {
|
||||
fn dep_memoized2() for memoized_dep_inputs::Memoized2;
|
||||
fn dep_memoized1() for memoized_dep_inputs::Memoized1;
|
||||
fn dep_derived1() for memoized_dep_inputs::Derived1;
|
||||
fn dep_input1() for memoized_dep_inputs::Input1;
|
||||
fn dep_input2() for memoized_dep_inputs::Input2;
|
||||
}
|
||||
|
||||
impl memoized_inputs::MemoizedInputsContext {
|
||||
|
@ -53,6 +56,12 @@ salsa::query_context_storage! {
|
|||
fn input1() for memoized_inputs::Input1;
|
||||
fn input2() for memoized_inputs::Input2;
|
||||
}
|
||||
|
||||
impl memoized_volatile::MemoizedVolatileContext {
|
||||
fn memoized2() for memoized_volatile::Memoized2;
|
||||
fn memoized1() for memoized_volatile::Memoized1;
|
||||
fn volatile() for memoized_volatile::Volatile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
mod counter;
|
||||
mod implementation;
|
||||
mod log;
|
||||
mod memoized_dep_inputs;
|
||||
mod memoized_inputs;
|
||||
mod memoized_volatile;
|
||||
|
||||
|
|
71
tests/incremental/memoized_dep_inputs.rs
Normal file
71
tests/incremental/memoized_dep_inputs.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use crate::implementation::{TestContext, TestContextImpl};
|
||||
|
||||
crate trait MemoizedDepInputsContext: TestContext {
|
||||
salsa::query_prototype! {
|
||||
fn dep_memoized2() for Memoized2;
|
||||
fn dep_memoized1() for Memoized1;
|
||||
fn dep_derived1() for Derived1;
|
||||
fn dep_input1() for Input1;
|
||||
fn dep_input2() for Input2;
|
||||
}
|
||||
}
|
||||
|
||||
salsa::query_definition! {
|
||||
crate Memoized2(query: &impl MemoizedDepInputsContext, (): ()) -> usize {
|
||||
query.log().add("Memoized2 invoked");
|
||||
query.dep_memoized1().read()
|
||||
}
|
||||
}
|
||||
|
||||
salsa::query_definition! {
|
||||
crate Memoized1(query: &impl MemoizedDepInputsContext, (): ()) -> usize {
|
||||
query.log().add("Memoized1 invoked");
|
||||
query.dep_derived1().read() * 2
|
||||
}
|
||||
}
|
||||
|
||||
salsa::query_definition! {
|
||||
#[storage(dependencies)]
|
||||
crate Derived1(query: &impl MemoizedDepInputsContext, (): ()) -> usize {
|
||||
query.log().add("Derived1 invoked");
|
||||
query.dep_input1().read() / 2
|
||||
}
|
||||
}
|
||||
|
||||
salsa::query_definition! {
|
||||
crate Input1: Map<(), usize>;
|
||||
}
|
||||
|
||||
salsa::query_definition! {
|
||||
crate Input2: Map<(), usize>;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revalidate() {
|
||||
let query = TestContextImpl::default();
|
||||
|
||||
// Initial run starts from Memoized2:
|
||||
let v = query.dep_memoized2().read();
|
||||
assert_eq!(v, 0);
|
||||
query.assert_log(&["Memoized2 invoked", "Memoized1 invoked", "Derived1 invoked"]);
|
||||
|
||||
// After that, we first try to validate Memoized1 but wind up
|
||||
// running Memoized2. Note that we don't try to validate
|
||||
// Derived1, so it is invoked by Memoized1.
|
||||
query.dep_input1().set((), 44);
|
||||
let v = query.dep_memoized2().read();
|
||||
assert_eq!(v, 44);
|
||||
query.assert_log(&["Memoized1 invoked", "Derived1 invoked", "Memoized2 invoked"]);
|
||||
|
||||
// Here validation of Memoized1 succeeds so Memoized2 never runs.
|
||||
query.dep_input1().set((), 45);
|
||||
let v = query.dep_memoized2().read();
|
||||
assert_eq!(v, 44);
|
||||
query.assert_log(&["Memoized1 invoked", "Derived1 invoked"]);
|
||||
|
||||
// Here, a change to input2 doesn't affect us, so nothing runs.
|
||||
query.dep_input2().set((), 45);
|
||||
let v = query.dep_memoized2().read();
|
||||
assert_eq!(v, 44);
|
||||
query.assert_log(&[]);
|
||||
}
|
Loading…
Reference in a new issue