Make interned's last_interned_at equal Revision::MAX if they are interned outside a query (#804)

There is an assert that `last_interned_at >= last_changed_revision`, and it can fail without this, see the added test.
This commit is contained in:
Chayim Refael Friedman 2025-04-22 13:42:17 +03:00 committed by GitHub
parent 05b4faddb5
commit cf9efae0da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 48 additions and 5 deletions

View file

@ -282,11 +282,12 @@ where
let table = zalsa.table();
// Record the durability of the current query on the interned value.
let durability = zalsa_local
let (durability, last_interned_at) = zalsa_local
.active_query()
.map(|(_, stamp)| stamp.durability)
.map(|(_, stamp)| (stamp.durability, current_revision))
// If there is no active query this durability does not actually matter.
.unwrap_or(Durability::MAX);
// `last_interned_at` needs to be `Revision::MAX`, see the intern_access_in_different_revision test.
.unwrap_or((Durability::MAX, Revision::max()));
let id = zalsa_local.allocate(table, self.ingredient_index, |id| Value::<C> {
fields: unsafe { self.to_internal_data(assemble(id, key)) },
@ -295,7 +296,7 @@ where
durability: AtomicU8::new(durability.as_u8()),
// Record the revision we are interning in.
first_interned_at: current_revision,
last_interned_at: AtomicRevision::from(current_revision),
last_interned_at: AtomicRevision::from(last_interned_at),
});
let value = table.get::<Value<C>>(id);
@ -391,7 +392,11 @@ where
// The slot is valid in this revision but we have to sync the value's revision.
let current_revision = zalsa.current_revision();
value.last_interned_at.store(current_revision);
// No `if` to be branchless.
value.last_interned_at.store(std::cmp::max(
current_revision,
value.last_interned_at.load(),
));
db.salsa_event(&|| {
Event::new(EventKind::DidReinternValue {

View file

@ -17,24 +17,34 @@ pub struct Revision {
}
impl Revision {
#[inline]
pub(crate) fn max() -> Self {
Self::from(usize::MAX)
}
#[inline]
pub(crate) fn start() -> Self {
Self::from(START)
}
#[inline]
pub(crate) fn from(g: usize) -> Self {
Self {
generation: NonZeroUsize::new(g).unwrap(),
}
}
#[inline]
pub(crate) fn from_opt(g: usize) -> Option<Self> {
NonZeroUsize::new(g).map(|generation| Self { generation })
}
#[inline]
pub(crate) fn next(self) -> Revision {
Self::from(self.generation.get() + 1)
}
#[inline]
fn as_usize(self) -> usize {
self.generation.get()
}

View file

@ -0,0 +1,28 @@
use salsa::{Durability, Setter};
#[salsa::interned(no_lifetime)]
struct Interned {
field: u32,
}
#[salsa::input]
struct Input {
field: i32,
}
#[test]
fn the_test() {
let mut db = salsa::DatabaseImpl::default();
let input = Input::builder(-123456)
.field_durability(Durability::HIGH)
.new(&db);
// Create an intern in an early revision.
let interned = Interned::new(&db, 0xDEADBEEF);
// Trigger a new revision.
input
.set_field(&mut db)
.with_durability(Durability::HIGH)
.to(123456);
// Read the interned value
let _ = interned.field(&db);
}