Skip to content

Commit 00dbf01

Browse files
authored
Pass cycle heads as out parameter for maybe_changed_after (#852)
1 parent 2c86936 commit 00dbf01

15 files changed

+98
-113
lines changed

src/accumulator.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::panic::UnwindSafe;
77

88
use accumulated::{Accumulated, AnyAccumulated};
99

10+
use crate::cycle::CycleHeads;
1011
use crate::function::VerifyResult;
1112
use crate::ingredient::{Ingredient, Jar};
1213
use crate::loom::sync::Arc;
@@ -105,7 +106,7 @@ impl<A: Accumulator> Ingredient for IngredientImpl<A> {
105106
_db: &dyn Database,
106107
_input: Id,
107108
_revision: Revision,
108-
_in_cycle: bool,
109+
_cycle_heads: &mut CycleHeads,
109110
) -> VerifyResult {
110111
panic!("nothing should ever depend on an accumulator directly")
111112
}

src/cycle.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,22 @@ impl CycleHeads {
151151
}
152152
}
153153

154+
#[inline]
155+
pub(crate) fn push_initial(&mut self, database_key_index: DatabaseKeyIndex) {
156+
if let Some(existing) = self
157+
.0
158+
.iter()
159+
.find(|candidate| candidate.database_key_index == database_key_index)
160+
{
161+
assert_eq!(existing.iteration_count, 0);
162+
} else {
163+
self.0.push(CycleHead {
164+
database_key_index,
165+
iteration_count: 0,
166+
});
167+
}
168+
}
169+
154170
#[inline]
155171
pub(crate) fn extend(&mut self, other: &Self) {
156172
self.0.reserve(other.0.len());

src/function.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::ptr::NonNull;
55
pub(crate) use maybe_changed_after::VerifyResult;
66

77
use crate::accumulator::accumulated_map::{AccumulatedMap, InputAccumulatedValues};
8-
use crate::cycle::{CycleHeadKind, CycleRecoveryAction, CycleRecoveryStrategy};
8+
use crate::cycle::{CycleHeadKind, CycleHeads, CycleRecoveryAction, CycleRecoveryStrategy};
99
use crate::function::delete::DeletedEntries;
1010
use crate::function::sync::{ClaimResult, SyncTable};
1111
use crate::ingredient::Ingredient;
@@ -233,11 +233,11 @@ where
233233
db: &dyn Database,
234234
input: Id,
235235
revision: Revision,
236-
in_cycle: bool,
236+
cycle_heads: &mut CycleHeads,
237237
) -> VerifyResult {
238238
// SAFETY: The `db` belongs to the ingredient as per caller invariant
239239
let db = unsafe { self.view_caster.downcast_unchecked(db) };
240-
self.maybe_changed_after(db, input, revision, in_cycle)
240+
self.maybe_changed_after(db, input, revision, cycle_heads)
241241
}
242242

243243
/// True if the input `input` contains a memo that cites itself as a cycle head.

src/function/fetch.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,14 @@ where
176176
let opt_old_memo = self.get_memo_from_table_for(zalsa, id, memo_ingredient_index);
177177
if let Some(old_memo) = opt_old_memo {
178178
if old_memo.value.is_some() {
179-
if let VerifyResult::Unchanged(_, cycle_heads) =
180-
self.deep_verify_memo(db, zalsa, old_memo, self.database_key_index(id))
181-
{
179+
let mut cycle_heads = CycleHeads::default();
180+
if let VerifyResult::Unchanged(_) = self.deep_verify_memo(
181+
db,
182+
zalsa,
183+
old_memo,
184+
self.database_key_index(id),
185+
&mut cycle_heads,
186+
) {
182187
if cycle_heads.is_empty() {
183188
// SAFETY: memo is present in memo_map and we have verified that it is
184189
// still valid for the current revision.

src/function/maybe_changed_after.rs

Lines changed: 48 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -13,53 +13,33 @@ use crate::{AsDynDatabase as _, Id, Revision};
1313
/// Result of memo validation.
1414
pub enum VerifyResult {
1515
/// Memo has changed and needs to be recomputed.
16-
///
17-
/// The cycle heads encountered when validating the memo.
18-
Changed(CycleHeads),
16+
Changed,
1917

2018
/// Memo remains valid.
2119
///
22-
/// The first inner value tracks whether the memo or any of its dependencies have an
20+
/// The inner value tracks whether the memo or any of its dependencies have an
2321
/// accumulated value.
2422
///
25-
/// The second is the cycle heads encountered in validation; don't mark
26-
/// memos verified until we've iterated the full cycle to ensure no inputs changed.
27-
Unchanged(InputAccumulatedValues, CycleHeads),
23+
/// Don't mark memos verified until we've iterated the full cycle to ensure no inputs changed
24+
/// when encountering this variant.
25+
Unchanged(InputAccumulatedValues),
2826
}
2927

3028
impl VerifyResult {
3129
pub(crate) fn changed_if(changed: bool) -> Self {
3230
if changed {
33-
Self::changed()
31+
Self::Changed
3432
} else {
3533
Self::unchanged()
3634
}
3735
}
3836

39-
pub(crate) fn changed() -> Self {
40-
Self::Changed(CycleHeads::default())
41-
}
42-
4337
pub(crate) fn unchanged() -> Self {
44-
Self::Unchanged(InputAccumulatedValues::Empty, CycleHeads::default())
45-
}
46-
47-
pub(crate) fn cycle_heads(&self) -> &CycleHeads {
48-
match self {
49-
Self::Changed(cycle_heads) => cycle_heads,
50-
Self::Unchanged(_, cycle_heads) => cycle_heads,
51-
}
52-
}
53-
54-
pub(crate) fn into_cycle_heads(self) -> CycleHeads {
55-
match self {
56-
Self::Changed(cycle_heads) => cycle_heads,
57-
Self::Unchanged(_, cycle_heads) => cycle_heads,
58-
}
38+
Self::Unchanged(InputAccumulatedValues::Empty)
5939
}
6040

6141
pub(crate) const fn is_unchanged(&self) -> bool {
62-
matches!(self, Self::Unchanged(_, _))
42+
matches!(self, Self::Unchanged(_))
6343
}
6444
}
6545

@@ -72,7 +52,7 @@ where
7252
db: &'db C::DbView,
7353
id: Id,
7454
revision: Revision,
75-
in_cycle: bool,
55+
cycle_heads: &mut CycleHeads,
7656
) -> VerifyResult {
7757
let (zalsa, zalsa_local) = db.zalsas();
7858
let memo_ingredient_index = self.memo_ingredient_index(zalsa, id);
@@ -87,20 +67,17 @@ where
8767
let memo_guard = self.get_memo_from_table_for(zalsa, id, memo_ingredient_index);
8868
let Some(memo) = memo_guard else {
8969
// No memo? Assume has changed.
90-
return VerifyResult::changed();
70+
return VerifyResult::Changed;
9171
};
9272

9373
let can_shallow_update = self.shallow_verify_memo(zalsa, database_key_index, memo);
9474
if can_shallow_update.yes() && !memo.may_be_provisional() {
9575
self.update_shallow(zalsa, database_key_index, memo, can_shallow_update);
9676

9777
return if memo.revisions.changed_at > revision {
98-
VerifyResult::changed()
78+
VerifyResult::Changed
9979
} else {
100-
VerifyResult::Unchanged(
101-
memo.revisions.accumulated_inputs.load(),
102-
CycleHeads::default(),
103-
)
80+
VerifyResult::Unchanged(memo.revisions.accumulated_inputs.load())
10481
};
10582
}
10683

@@ -110,7 +87,7 @@ where
11087
id,
11188
revision,
11289
memo_ingredient_index,
113-
in_cycle,
90+
cycle_heads,
11491
) {
11592
return mcs;
11693
} else {
@@ -127,7 +104,7 @@ where
127104
key_index: Id,
128105
revision: Revision,
129106
memo_ingredient_index: MemoIngredientIndex,
130-
in_cycle: bool,
107+
cycle_heads: &mut CycleHeads,
131108
) -> Option<VerifyResult> {
132109
let database_key_index = self.database_key_index(key_index);
133110

@@ -148,18 +125,16 @@ where
148125
tracing::debug!(
149126
"hit cycle at {database_key_index:?} in `maybe_changed_after`, returning fixpoint initial value",
150127
);
151-
return Some(VerifyResult::Unchanged(
152-
InputAccumulatedValues::Empty,
153-
CycleHeads::initial(database_key_index),
154-
));
128+
cycle_heads.push_initial(database_key_index);
129+
return Some(VerifyResult::unchanged());
155130
}
156131
},
157132
ClaimResult::Claimed(guard) => guard,
158133
};
159134
// Load the current memo, if any.
160135
let Some(old_memo) = self.get_memo_from_table_for(zalsa, key_index, memo_ingredient_index)
161136
else {
162-
return Some(VerifyResult::changed());
137+
return Some(VerifyResult::Changed);
163138
};
164139

165140
tracing::debug!(
@@ -169,15 +144,13 @@ where
169144
);
170145

171146
// Check if the inputs are still valid. We can just compare `changed_at`.
172-
let deep_verify = self.deep_verify_memo(db, zalsa, old_memo, database_key_index);
147+
let deep_verify =
148+
self.deep_verify_memo(db, zalsa, old_memo, database_key_index, cycle_heads);
173149
if deep_verify.is_unchanged() {
174150
return Some(if old_memo.revisions.changed_at > revision {
175-
VerifyResult::Changed(deep_verify.into_cycle_heads())
151+
VerifyResult::Changed
176152
} else {
177-
VerifyResult::Unchanged(
178-
old_memo.revisions.accumulated_inputs.load(),
179-
deep_verify.into_cycle_heads(),
180-
)
153+
VerifyResult::Unchanged(old_memo.revisions.accumulated_inputs.load())
181154
});
182155
}
183156

@@ -191,26 +164,23 @@ where
191164
// the cycle head returned *fixpoint initial* without validating its dependencies.
192165
// `in_cycle` tracks if the enclosing query is in a cycle. `deep_verify.cycle_heads` tracks
193166
// if **this query** encountered a cycle (which means there's some provisional value somewhere floating around).
194-
if old_memo.value.is_some() && !in_cycle && deep_verify.cycle_heads().is_empty() {
167+
if old_memo.value.is_some() && cycle_heads.is_empty() {
195168
let active_query = db.zalsa_local().push_query(database_key_index, 0);
196169
let memo = self.execute(db, active_query, Some(old_memo));
197170
let changed_at = memo.revisions.changed_at;
198171

199172
return Some(if changed_at > revision {
200-
VerifyResult::changed()
173+
VerifyResult::Changed
201174
} else {
202-
VerifyResult::Unchanged(
203-
match &memo.revisions.accumulated {
204-
Some(_) => InputAccumulatedValues::Any,
205-
None => memo.revisions.accumulated_inputs.load(),
206-
},
207-
CycleHeads::default(),
208-
)
175+
VerifyResult::Unchanged(match &memo.revisions.accumulated {
176+
Some(_) => InputAccumulatedValues::Any,
177+
None => memo.revisions.accumulated_inputs.load(),
178+
})
209179
});
210180
}
211181

212182
// Otherwise, nothing for it: have to consider the value to have changed.
213-
Some(VerifyResult::Changed(deep_verify.into_cycle_heads()))
183+
Some(VerifyResult::Changed)
214184
}
215185

216186
/// `Some` if the memo's value and `changed_at` time is still valid in this revision.
@@ -249,7 +219,7 @@ where
249219
);
250220
if last_changed <= verified_at {
251221
// No input of the suitable durability has changed since last verified.
252-
ShallowUpdate::HigherDurability(revision_now)
222+
ShallowUpdate::HigherDurability
253223
} else {
254224
ShallowUpdate::No
255225
}
@@ -263,8 +233,8 @@ where
263233
memo: &Memo<C::Output<'_>>,
264234
update: ShallowUpdate,
265235
) {
266-
if let ShallowUpdate::HigherDurability(revision_now) = update {
267-
memo.mark_as_verified(zalsa, revision_now, database_key_index);
236+
if let ShallowUpdate::HigherDurability = update {
237+
memo.mark_as_verified(zalsa, database_key_index);
268238
memo.mark_outputs_as_verified(zalsa, database_key_index);
269239
}
270240
}
@@ -375,6 +345,7 @@ where
375345
zalsa: &Zalsa,
376346
old_memo: &Memo<C::Output<'_>>,
377347
database_key_index: DatabaseKeyIndex,
348+
cycle_heads: &mut CycleHeads,
378349
) -> VerifyResult {
379350
tracing::debug!(
380351
"{database_key_index:?}: deep_verify_memo(old_memo = {old_memo:#?})",
@@ -408,30 +379,29 @@ where
408379
// Conditionally specified queries
409380
// where the value is specified
410381
// in rev 1 but not in rev 2.
411-
VerifyResult::changed()
382+
VerifyResult::Changed
412383
}
413384
// Return `Unchanged` similar to the initial value that we insert
414385
// when we hit the cycle. Any dependencies accessed when creating the fixpoint initial
415386
// are tracked by the outer query. Nothing should have changed assuming that the
416387
// fixpoint initial function is deterministic.
417-
QueryOrigin::FixpointInitial => VerifyResult::Unchanged(
418-
InputAccumulatedValues::Empty,
419-
CycleHeads::initial(database_key_index),
420-
),
388+
QueryOrigin::FixpointInitial => {
389+
cycle_heads.push_initial(database_key_index);
390+
VerifyResult::unchanged()
391+
}
421392
QueryOrigin::DerivedUntracked(_) => {
422393
// Untracked inputs? Have to assume that it changed.
423-
VerifyResult::changed()
394+
VerifyResult::Changed
424395
}
425396
QueryOrigin::Derived(edges) => {
426397
let is_provisional = old_memo.may_be_provisional();
427398

428399
// If the value is from the same revision but is still provisional, consider it changed
429400
// because we're now in a new iteration.
430-
if can_shallow_update.yes() && is_provisional {
431-
return VerifyResult::changed();
401+
if can_shallow_update == ShallowUpdate::Verified && is_provisional {
402+
return VerifyResult::Changed;
432403
}
433404

434-
let mut cycle_heads = CycleHeads::default();
435405
'cycle: loop {
436406
// Fully tracked inputs? Iterate over the inputs and check them, one by one.
437407
//
@@ -449,16 +419,12 @@ where
449419
dyn_db,
450420
zalsa,
451421
last_verified_at,
452-
!cycle_heads.is_empty(),
422+
cycle_heads,
453423
) {
454-
VerifyResult::Changed(heads) => {
455-
// Carry over the heads from the inner query to avoid
456-
// backdating the outer query.
457-
cycle_heads.extend(&heads);
458-
break 'cycle VerifyResult::Changed(cycle_heads);
424+
VerifyResult::Changed => {
425+
break 'cycle VerifyResult::Changed;
459426
}
460-
VerifyResult::Unchanged(input_accumulated, cycles) => {
461-
cycle_heads.extend(&cycles);
427+
VerifyResult::Unchanged(input_accumulated) => {
462428
inputs |= input_accumulated;
463429
}
464430
}
@@ -514,11 +480,7 @@ where
514480
let in_heads = cycle_heads.remove(&database_key_index);
515481

516482
if cycle_heads.is_empty() {
517-
old_memo.mark_as_verified(
518-
zalsa,
519-
zalsa.current_revision(),
520-
database_key_index,
521-
);
483+
old_memo.mark_as_verified(zalsa, database_key_index);
522484
old_memo.revisions.accumulated_inputs.store(inputs);
523485

524486
if is_provisional {
@@ -532,7 +494,7 @@ where
532494
continue 'cycle;
533495
}
534496
}
535-
break 'cycle VerifyResult::Unchanged(inputs, cycle_heads);
497+
break 'cycle VerifyResult::Unchanged(inputs);
536498
}
537499
}
538500
}
@@ -546,7 +508,7 @@ pub(super) enum ShallowUpdate {
546508

547509
/// The revision for the memo's durability hasn't changed. It can be marked as verified
548510
/// in this revision.
549-
HigherDurability(Revision),
511+
HigherDurability,
550512

551513
/// The memo requires a deep verification.
552514
No,
@@ -556,7 +518,7 @@ impl ShallowUpdate {
556518
pub(super) fn yes(&self) -> bool {
557519
matches!(
558520
self,
559-
ShallowUpdate::Verified | ShallowUpdate::HigherDurability(_)
521+
ShallowUpdate::Verified | ShallowUpdate::HigherDurability
560522
)
561523
}
562524
}

0 commit comments

Comments
 (0)