Skip to content

Commit f2566af

Browse files
committed
Force invalidation on cache eviction
1 parent 00dbf01 commit f2566af

File tree

11 files changed

+117
-1
lines changed

11 files changed

+117
-1
lines changed

components/salsa-macro-rules/src/setup_tracked_fn.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ macro_rules! setup_tracked_fn {
6060

6161
assert_return_type_is_update: {$($assert_return_type_is_update:tt)*},
6262

63+
force_invalidation_on_cache_eviction: $force_invalidation_on_cache_eviction:literal,
64+
6365
// Annoyingly macro-rules hygiene does not extend to items defined in the macro.
6466
// We have the procedural macro generate names for those items that are
6567
// not used elsewhere in the user's code.
@@ -192,6 +194,7 @@ macro_rules! setup_tracked_fn {
192194
line: line!(),
193195
};
194196
const DEBUG_NAME: &'static str = stringify!($fn_name);
197+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = $force_invalidation_on_cache_eviction;
195198

196199
type DbView = dyn $Db;
197200

components/salsa-macros/src/accumulator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ impl AllowedOptions for Accumulator {
4444
const LRU: bool = false;
4545
const CONSTRUCTOR_NAME: bool = false;
4646
const ID: bool = false;
47+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = false;
4748
}
4849

4950
struct StructMacro {

components/salsa-macros/src/input.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ impl crate::options::AllowedOptions for InputStruct {
6262
const CONSTRUCTOR_NAME: bool = true;
6363

6464
const ID: bool = false;
65+
66+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = false;
6567
}
6668

6769
impl SalsaStructAllowedOptions for InputStruct {

components/salsa-macros/src/interned.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ impl crate::options::AllowedOptions for InternedStruct {
6262
const CONSTRUCTOR_NAME: bool = true;
6363

6464
const ID: bool = true;
65+
66+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = false;
6567
}
6668

6769
impl SalsaStructAllowedOptions for InternedStruct {

components/salsa-macros/src/options.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ pub(crate) struct Options<A: AllowedOptions> {
8989
/// If this is `Some`, the value is the `<ident>`.
9090
pub id: Option<syn::Path>,
9191

92+
pub force_invalidation_on_cache_eviction: Option<syn::Ident>,
93+
9294
/// Remember the `A` parameter, which plays no role after parsing.
9395
phantom: PhantomData<A>,
9496
}
@@ -112,6 +114,7 @@ impl<A: AllowedOptions> Default for Options<A> {
112114
lru: Default::default(),
113115
singleton: Default::default(),
114116
id: Default::default(),
117+
force_invalidation_on_cache_eviction: Default::default(),
115118
}
116119
}
117120
}
@@ -133,6 +136,7 @@ pub(crate) trait AllowedOptions {
133136
const LRU: bool;
134137
const CONSTRUCTOR_NAME: bool;
135138
const ID: bool;
139+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool;
136140
}
137141

138142
type Equals = syn::Token![=];
@@ -351,6 +355,20 @@ impl<A: AllowedOptions> syn::parse::Parse for Options<A> {
351355
"`id` option not allowed here",
352356
));
353357
}
358+
} else if ident == "force_invalidation_on_cache_eviction" {
359+
if A::FORCE_INVALIDATION_ON_CACHE_EVICTION {
360+
if let Some(old) = options.force_invalidation_on_cache_eviction.replace(ident) {
361+
return Err(syn::Error::new(
362+
old.span(),
363+
"option `force_invalidation_on_cache_eviction` provided twice",
364+
));
365+
}
366+
} else {
367+
return Err(syn::Error::new(
368+
ident.span(),
369+
"`force_invalidation_on_cache_eviction` option not allowed here",
370+
));
371+
}
354372
} else {
355373
return Err(syn::Error::new(
356374
ident.span(),

components/salsa-macros/src/tracked_fn.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ impl crate::options::AllowedOptions for TrackedFn {
5555
const CONSTRUCTOR_NAME: bool = false;
5656

5757
const ID: bool = false;
58+
59+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = true;
5860
}
5961

6062
struct Macro {
@@ -147,6 +149,8 @@ impl Macro {
147149
let lru = Literal::usize_unsuffixed(self.args.lru.unwrap_or(0));
148150

149151
let return_ref: bool = self.args.return_ref.is_some();
152+
let force_invalidation_on_cache_eviction: bool =
153+
self.args.force_invalidation_on_cache_eviction.is_some();
150154

151155
// The path expression is responsible for emitting the primary span in the diagnostic we
152156
// want, so by uniformly using `output_ty.span()` we ensure that the diagnostic is emitted
@@ -185,6 +189,7 @@ impl Macro {
185189
lru: #lru,
186190
return_ref: #return_ref,
187191
assert_return_type_is_update: { #assert_return_type_is_update },
192+
force_invalidation_on_cache_eviction: #force_invalidation_on_cache_eviction,
188193
unused_names: [
189194
#zalsa,
190195
#Configuration,

components/salsa-macros/src/tracked_struct.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ impl crate::options::AllowedOptions for TrackedStruct {
5757
const CONSTRUCTOR_NAME: bool = true;
5858

5959
const ID: bool = false;
60+
61+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool = false;
6062
}
6163

6264
impl SalsaStructAllowedOptions for TrackedStruct {

src/function.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub type Memo<C> = memo::Memo<<C as Configuration>::Output<'static>>;
3838
pub trait Configuration: Any {
3939
const DEBUG_NAME: &'static str;
4040
const LOCATION: crate::ingredient::Location;
41+
const FORCE_INVALIDATION_ON_CACHE_EVICTION: bool;
4142

4243
/// The database that this function is associated with.
4344
type DbView: ?Sized + crate::Database;
@@ -327,6 +328,10 @@ where
327328
let db = self.view_caster.downcast(db);
328329
self.accumulated_map(db, key_index)
329330
}
331+
332+
fn force_invalidation_on_cache_eviction(&self) -> bool {
333+
C::FORCE_INVALIDATION_ON_CACHE_EVICTION
334+
}
330335
}
331336

332337
impl<C> std::fmt::Debug for IngredientImpl<C>

src/function/maybe_changed_after.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::function::sync::ClaimResult;
55
use crate::function::{Configuration, IngredientImpl};
66
use crate::key::DatabaseKeyIndex;
77
use crate::loom::sync::atomic::Ordering;
8-
use crate::plumbing::ZalsaLocal;
8+
use crate::plumbing::{Ingredient, ZalsaLocal};
99
use crate::zalsa::{MemoIngredientIndex, Zalsa, ZalsaDatabase};
1010
use crate::zalsa_local::{QueryEdge, QueryOrigin};
1111
use crate::{AsDynDatabase as _, Id, Revision};
@@ -352,6 +352,10 @@ where
352352
old_memo = old_memo.tracing_debug()
353353
);
354354

355+
if old_memo.value.is_none() && self.force_invalidation_on_cache_eviction() {
356+
return VerifyResult::Changed;
357+
}
358+
355359
let can_shallow_update = self.shallow_verify_memo(zalsa, database_key_index, old_memo);
356360
if can_shallow_update.yes()
357361
&& self.validate_may_be_provisional(

src/ingredient.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
165165
let _ = (db, key_index);
166166
(None, InputAccumulatedValues::Empty)
167167
}
168+
169+
fn force_invalidation_on_cache_eviction(&self) -> bool {
170+
false
171+
}
168172
}
169173

170174
impl dyn Ingredient {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! Tests that `force_invalidation_on_cache_eviction` causes dependent queries
2+
//! to be recomputed if our memo is missing, even if our result is unchanged.
3+
4+
mod common;
5+
use common::LogDatabase;
6+
use expect_test::expect;
7+
use salsa::Setter;
8+
use test_log::test;
9+
10+
#[salsa::input(debug)]
11+
struct MyInput {
12+
field: u32,
13+
}
14+
15+
#[salsa::tracked]
16+
struct MyTracked<'db> {
17+
field: u32,
18+
}
19+
20+
#[salsa::tracked(force_invalidation_on_cache_eviction, lru = 1)]
21+
fn intermediate_result(db: &dyn LogDatabase, input: MyInput) -> MyTracked<'_> {
22+
db.push_log(format!("intermediate_result({input:?})"));
23+
MyTracked::new(db, input.field(db) / 2)
24+
}
25+
26+
#[salsa::tracked]
27+
fn final_result(db: &dyn LogDatabase, input: MyInput) -> u32 {
28+
db.push_log(format!("final_result({input:?})"));
29+
intermediate_result(db, input).field(db) * 2
30+
}
31+
32+
#[test]
33+
fn execute() {
34+
let mut db = common::LoggerDatabase::default();
35+
36+
let input = MyInput::new(&db, 22);
37+
assert_eq!(final_result(&db, input), 22);
38+
// on first run, both intermediate and final results were computed
39+
db.assert_logs(expect![[r#"
40+
[
41+
"final_result(MyInput { [salsa id]: Id(0), field: 22 })",
42+
"intermediate_result(MyInput { [salsa id]: Id(0), field: 22 })",
43+
]"#]]);
44+
45+
input.set_field(&mut db).to(23);
46+
assert_eq!(final_result(&db, input), 22);
47+
// intermediate result was the same, so final result was not recomputed
48+
db.assert_logs(expect![[r#"
49+
[
50+
"intermediate_result(MyInput { [salsa id]: Id(0), field: 23 })",
51+
]"#]]);
52+
53+
assert_eq!(intermediate_result(&db, MyInput::new(&db, 10)).field(&db), 5);
54+
input.set_field(&mut db).to(23);
55+
// an intermediate result for a different input evicted the original memo
56+
// from the cache (because query's lru = 1)
57+
db.assert_logs(expect![[r#"
58+
[
59+
"intermediate_result(MyInput { [salsa id]: Id(1), field: 10 })",
60+
]"#]]);
61+
62+
assert_eq!(final_result(&db, input), 22);
63+
// now, despite unchanged intermediate result, final result was recomputed
64+
// (because intermediate query is `force_invalidation_on_cache_eviction`)
65+
db.assert_logs(expect![[r#"
66+
[
67+
"final_result(MyInput { [salsa id]: Id(0), field: 23 })",
68+
"intermediate_result(MyInput { [salsa id]: Id(0), field: 23 })",
69+
]"#]]);
70+
}

0 commit comments

Comments
 (0)