salsa/tests/gc/interned.rs
Niko Matsakis fad97eeb6a remove the DB parameter
This had two unexpected consequences, one unfortunate, one "medium":

* All `salsa::Database` must be `'static`. This falls out from
`Q::DynDb` not having access to any lifetimes, but also the defaulting
rules for `dyn QueryGroup` that make it `dyn QueryGroup + 'static`. We
don't really support generic databases anyway yet so this isn't a big
deal, and we can add workarounds later (ideally via GATs).

* It is now statically impossible to invoke `snapshot` from a query,
and so we don't need to test that it panics. This is because the
signature of `snapshot` returns a `Snapshot<Self>` and that is not
accessible to a `dyn QueryGroup` type. Similarly, invoking
`Runtime::snapshot` directly is not possible becaues it is
crate-private. So I removed the test. This seems ok, but eventually I
would like to expose ways for queries to do parallel
execution (matklad and I had talked about a "speculation" primitive
for enabling that).

* This commit is 99% boilerplate I did with search-and-replace. I also
rolled in a few other changes I might have preferred to factor out,
most notably removing the `GetQueryTable` plumbing trait in favor of
free-methods, but it was awkward to factor them out and get all the
generics right (so much simpler in this version).
2020-07-04 14:17:11 +00:00

194 lines
6.2 KiB
Rust

use crate::db;
use salsa::debug::DebugQueryTable;
use salsa::{Database, Durability, InternId, SweepStrategy};
/// Query group for tests for how interned keys interact with GC.
#[salsa::query_group(Intern)]
pub(crate) trait InternDatabase {
/// A dummy input that can be used to trigger a new revision.
#[salsa::input]
fn dummy(&self) -> ();
/// Underlying interning query.
#[salsa::interned]
fn intern_str(&self, x: &'static str) -> InternId;
/// This just executes the intern query and returns the result.
fn repeat_intern1(&self, x: &'static str) -> InternId;
/// Same as `repeat_intern1`. =)
fn repeat_intern2(&self, x: &'static str) -> InternId;
}
fn repeat_intern1(db: &dyn InternDatabase, x: &'static str) -> InternId {
db.intern_str(x)
}
fn repeat_intern2(db: &dyn InternDatabase, x: &'static str) -> InternId {
db.intern_str(x)
}
/// This test highlights the difference between *interned queries* and
/// other non-input queries -- in particular, their results are not
/// *deterministic*. Therefore, we cannot GC values that were created
/// in the current revision; that might cause us to re-execute the
/// query twice on the same key during the same revision, which could
/// yield different results each time, wreaking havoc. This test
/// exercises precisely that scenario.
#[test]
fn discard_during_same_revision() {
let db = db::DatabaseImpl::default();
// This will assign index 0 for "foo".
let foo1a = db.repeat_intern1("foo");
// If we are not careful, this would remove the interned key for
// "foo".
InternStrQuery.in_db(&db).sweep(
SweepStrategy::default()
.discard_everything()
.sweep_all_revisions(),
);
// This would then reuse index 0 for "bar".
let bar1 = db.intern_str("bar");
// And here we would assign index *1* to "foo".
let foo2 = db.repeat_intern2("foo");
// But we would still have a cached result, *from the same
// revision*, with the value 0. So that's inconsistent.
let foo1b = db.repeat_intern1("foo");
assert_ne!(foo2, bar1);
assert_eq!(foo1a, foo1b);
assert_eq!(foo1b, foo2);
}
/// This test highlights the difference between *interned queries* and
/// other non-input queries -- in particular, their results are not
/// *deterministic*. Therefore, we cannot GC values that were created
/// in the current revision; that might cause us to re-execute the
/// query twice on the same key during the same revision, which could
/// yield different results each time, wreaking havoc. This test
/// exercises precisely that scenario.
#[test]
fn discard_outdated() {
let mut db = db::DatabaseImpl::default();
let foo_from_rev0 = db.repeat_intern1("foo");
let bar_from_rev0 = db.repeat_intern1("bar");
// Trigger a new revision.
db.salsa_runtime_mut().synthetic_write(Durability::HIGH);
// In this revision, we use "bar".
let bar_from_rev1 = db.repeat_intern1("bar");
// This should collect "foo".
db.sweep_all(SweepStrategy::discard_outdated());
// This should be the same as before the GC, as bar
// is not outdated.
let bar2_from_rev1 = db.repeat_intern1("bar");
// This should re-use the index of "foo".
let baz_from_rev1 = db.repeat_intern1("baz");
// This should assign the next index to "foo".
let foo_from_rev1 = db.repeat_intern1("foo");
assert_eq!(bar_from_rev0, bar_from_rev1);
assert_eq!(bar_from_rev0, bar2_from_rev1);
assert_eq!(foo_from_rev0, baz_from_rev1);
assert_ne!(foo_from_rev0, foo_from_rev1);
assert_ne!(foo_from_rev1, bar_from_rev1);
assert_ne!(foo_from_rev1, baz_from_rev1);
assert_eq!(db.lookup_intern_str(foo_from_rev1), "foo");
assert_eq!(db.lookup_intern_str(bar_from_rev1), "bar");
assert_eq!(db.lookup_intern_str(baz_from_rev1), "baz");
}
/// Variation on `discard_during_same_revision` --- here we show that
/// a synthetic write of level LOW isn't enough to collect interned
/// keys (which are considered durability HIGH).
#[test]
fn discard_durability_after_synthetic_write_low() {
let mut db = db::DatabaseImpl::default();
// This will assign index 0 for "foo".
let foo1a = db.repeat_intern1("foo");
assert_eq!(
Durability::HIGH,
RepeatIntern1Query.in_db(&db).durability("foo")
);
// Trigger a new revision.
db.salsa_runtime_mut().synthetic_write(Durability::LOW);
// If we are not careful, this would remove the interned key for
// "foo".
InternStrQuery.in_db(&db).sweep(
SweepStrategy::default()
.discard_everything()
.sweep_all_revisions(),
);
// This would then reuse index 0 for "bar".
let bar1 = db.intern_str("bar");
// And here we would assign index *1* to "foo".
let foo2 = db.repeat_intern2("foo");
// But we would still have a cached result with the value 0 and
// with high durability, so we can reuse it. That gives an
// inconsistent result.
let foo1b = db.repeat_intern1("foo");
assert_ne!(foo2, bar1);
assert_eq!(foo1a, foo1b);
assert_eq!(foo1b, foo2);
}
/// Variation on previous test in which we do a synthetic write to
/// `Durability::HIGH`.
#[test]
fn discard_durability_after_synthetic_write_high() {
let mut db = db::DatabaseImpl::default();
// This will assign index 0 for "foo".
let foo1a = db.repeat_intern1("foo");
assert_eq!(
Durability::HIGH,
RepeatIntern1Query.in_db(&db).durability("foo")
);
// Trigger a new revision -- marking even high things as having changed.
db.salsa_runtime_mut().synthetic_write(Durability::HIGH);
// We are now able to collect "collect".
InternStrQuery.in_db(&db).sweep(
SweepStrategy::default()
.discard_everything()
.sweep_all_revisions(),
);
// So we can reuse index 0 for "bar".
let bar1 = db.intern_str("bar");
// And here we assign index *1* to "foo".
let foo2 = db.repeat_intern2("foo");
let foo1b = db.repeat_intern1("foo");
// Thus foo1a (from before the synthetic write) and foo1b (from
// after) are different.
assert_ne!(foo1a, foo1b);
// But the things that come after the synthetic write are
// consistent.
assert_ne!(foo2, bar1);
assert_eq!(foo1b, foo2);
}