Skip to content

Commit 6e42e23

Browse files
committed
Port over changes from other branch
1 parent 8e4cad1 commit 6e42e23

File tree

10 files changed

+272
-177
lines changed

10 files changed

+272
-177
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ intrusive-collections = "0.9.7"
2222
parking_lot = "0.12"
2323
portable-atomic = "1"
2424
rustc-hash = "2"
25-
smallvec = "1"
25+
smallvec = { version = "1", features = ["const_new"] }
2626
thin-vec = { version = "0.2.14" }
2727
tracing = { version = "0.1", default-features = false, features = ["std"] }
2828

src/cycle.rs

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,31 @@ pub enum CycleRecoveryStrategy {
102102
pub struct CycleHead {
103103
pub(crate) database_key_index: DatabaseKeyIndex,
104104
pub(crate) iteration_count: AtomicIterationCount,
105+
106+
/// Marks a cycle head as removed within its `CycleHeads` container.
107+
///
108+
/// Cycle heads are marked as removed when the memo from the last iteration (a provisional memo)
109+
/// is used as the initial value for the next iteration. It's necessary to remove all but its own
110+
/// head from the `CycleHeads` container, because the query might now depend on fewer cycles
111+
/// (in case of conditional dependencies). However, we can't actually remove the cycle head
112+
/// within `fetch_cold_cycle` because we only have a readonly memo. That's what `removed` is used for.
105113
#[cfg_attr(feature = "persistence", serde(skip))]
106114
removed: AtomicBool,
107115
}
108116

117+
impl CycleHead {
118+
pub const fn new(
119+
database_key_index: DatabaseKeyIndex,
120+
iteration_count: IterationCount,
121+
) -> Self {
122+
Self {
123+
database_key_index,
124+
iteration_count: AtomicIterationCount(AtomicU8::new(iteration_count.0)),
125+
removed: AtomicBool::new(false),
126+
}
127+
}
128+
}
129+
109130
impl Clone for CycleHead {
110131
fn clone(&self) -> Self {
111132
Self {
@@ -130,10 +151,19 @@ impl IterationCount {
130151
self.0 == 0
131152
}
132153

154+
/// Iteration count reserved for panicked cycles.
155+
///
156+
/// Using a special iteration count ensures that `validate_same_iteration` and `validate_provisional`
157+
/// return `false` for queries depending on this panicked cycle, because the iteration count is guaranteed
158+
/// to be different (which isn't guaranteed if the panicked memo uses [`Self::initial`]).
133159
pub(crate) const fn panicked() -> Self {
134160
Self(u8::MAX)
135161
}
136162

163+
pub(crate) const fn is_panicked(self) -> bool {
164+
self.0 == u8::MAX
165+
}
166+
137167
pub(crate) const fn increment(self) -> Option<Self> {
138168
let next = Self(self.0 + 1);
139169
if next.0 <= MAX_ITERATIONS.0 {
@@ -150,7 +180,7 @@ impl IterationCount {
150180

151181
impl std::fmt::Display for IterationCount {
152182
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153-
write!(f, "iteration={}", self.0)
183+
self.0.fmt(f)
154184
}
155185
}
156186

@@ -162,6 +192,10 @@ impl AtomicIterationCount {
162192
IterationCount(self.0.load(Ordering::Relaxed))
163193
}
164194

195+
pub(crate) fn load_mut(&mut self) -> IterationCount {
196+
IterationCount(*self.0.get_mut())
197+
}
198+
165199
pub(crate) fn store(&self, value: IterationCount) {
166200
self.0.store(value.0, Ordering::Release);
167201
}
@@ -214,10 +248,13 @@ impl CycleHeads {
214248
self.0.is_empty()
215249
}
216250

217-
pub(crate) fn initial(database_key_index: DatabaseKeyIndex) -> Self {
251+
pub(crate) fn initial(
252+
database_key_index: DatabaseKeyIndex,
253+
iteration_count: IterationCount,
254+
) -> Self {
218255
Self(thin_vec![CycleHead {
219256
database_key_index,
220-
iteration_count: IterationCount::initial().into(),
257+
iteration_count: iteration_count.into(),
221258
removed: false.into()
222259
}])
223260
}
@@ -228,22 +265,35 @@ impl CycleHeads {
228265
}
229266
}
230267

268+
/// Iterates over all cycle heads that aren't equal to `own`.
269+
pub(crate) fn iter_not_eq(&self, own: DatabaseKeyIndex) -> impl Iterator<Item = &CycleHead> {
270+
self.iter()
271+
.filter(move |head| head.database_key_index != own)
272+
}
273+
231274
pub(crate) fn contains(&self, value: &DatabaseKeyIndex) -> bool {
232275
self.into_iter()
233276
.any(|head| head.database_key_index == *value && !head.removed.load(Ordering::Relaxed))
234277
}
235278

236-
pub(crate) fn clear_except(&self, except: DatabaseKeyIndex) {
279+
/// Removes all cycle heads except `except` by marking them as removed.
280+
///
281+
/// Note that the heads aren't actually removed. They're only marked as removed and will be
282+
/// skipped when iterating. This is because we might not have a mutable reference.
283+
pub(crate) fn remove_all_except(&self, except: DatabaseKeyIndex) {
237284
for head in self.0.iter() {
238285
if head.database_key_index == except {
239286
continue;
240287
}
241288

242-
// TODO: verify ordering
243289
head.removed.store(true, Ordering::Release);
244290
}
245291
}
246292

293+
/// Updates the iteration count for the head `cycle_head_index` to `new_iteration_count`.
294+
///
295+
/// Unlike [`update_iteration_count`], this method takes a `&mut self` reference. It should
296+
/// be preferred if possible, as it avoids atomic operations.
247297
pub(crate) fn update_iteration_count_mut(
248298
&mut self,
249299
cycle_head_index: DatabaseKeyIndex,
@@ -258,6 +308,9 @@ impl CycleHeads {
258308
}
259309
}
260310

311+
/// Updates the iteration count for the head `cycle_head_index` to `new_iteration_count`.
312+
///
313+
/// Unlike [`update_iteration_count_mut`], this method takes a `&self` reference.
261314
pub(crate) fn update_iteration_count(
262315
&self,
263316
cycle_head_index: DatabaseKeyIndex,
@@ -277,15 +330,20 @@ impl CycleHeads {
277330
self.0.reserve(other.0.len());
278331

279332
for head in other {
280-
self.insert(head);
333+
debug_assert!(!head.removed.load(Ordering::Relaxed));
334+
self.insert(head.database_key_index, head.iteration_count.load());
281335
}
282336
}
283337

284-
pub(crate) fn insert(&mut self, head: &CycleHead) -> bool {
338+
pub(crate) fn insert(
339+
&mut self,
340+
database_key_index: DatabaseKeyIndex,
341+
iteration_count: IterationCount,
342+
) -> bool {
285343
if let Some(existing) = self
286344
.0
287345
.iter_mut()
288-
.find(|candidate| candidate.database_key_index == head.database_key_index)
346+
.find(|candidate| candidate.database_key_index == database_key_index)
289347
{
290348
let removed = existing.removed.get_mut();
291349

@@ -294,23 +352,19 @@ impl CycleHeads {
294352

295353
true
296354
} else {
297-
let existing_count = existing.iteration_count.load();
298-
let head_count = head.iteration_count.load();
355+
let existing_count = existing.iteration_count.load_mut();
299356

300-
// It's now possible that a query can depend on different iteration counts of the same query
301-
// This because some queries (inner) read the provisional value of the last iteration
302-
// while outer queries read the value from the last iteration (which is i+1 if the head didn't converge).
303357
assert_eq!(
304-
existing_count, head_count,
305-
"Can't merge cycle heads {:?} with different iteration counts ({existing_count:?}, {head_count:?})",
358+
existing_count, iteration_count,
359+
"Can't merge cycle heads {:?} with different iteration counts ({existing_count:?}, {iteration_count:?})",
306360
existing.database_key_index
307361
);
308362

309363
false
310364
}
311365
} else {
312-
debug_assert!(!head.removed.load(Ordering::Relaxed));
313-
self.0.push(head.clone());
366+
self.0
367+
.push(CycleHead::new(database_key_index, iteration_count));
314368
true
315369
}
316370
}

src/function.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ pub trait Configuration: Any {
9494
/// Decide whether to iterate a cycle again or fallback. `value` is the provisional return
9595
/// value from the latest iteration of this cycle. `count` is the number of cycle iterations
9696
/// we've already completed.
97+
///
98+
/// Note: There is no guarantee that `count` always starts at 0. It's possible that
99+
/// the function is called with a non-zero value even if it is the first time around for
100+
/// this specific query if the query has become the outermost cycle of a larger cycle.
101+
/// In this case, Salsa uses the `count` value of the already iterating cycle as the start.
102+
///
103+
/// It's also not guaranteed that `count` values are contiguous. The function might not be called
104+
/// if this query converged in this specific iteration OR if the query only participates conditionally
105+
/// in the cycle (e.g. every other iteration).
97106
fn recover_from_cycle<'db>(
98107
db: &'db Self::DbView,
99108
value: &Self::Output<'db>,
@@ -372,7 +381,7 @@ where
372381
.set_iteration_count(Self::database_key_index(self, input), iteration_count);
373382
}
374383

375-
fn set_cycle_finalized(&self, zalsa: &Zalsa, input: Id) {
384+
fn finalize_cycle_head(&self, zalsa: &Zalsa, input: Id) {
376385
let Some(memo) =
377386
self.get_memo_from_table_for(zalsa, input, self.memo_ingredient_index(zalsa, input))
378387
else {

0 commit comments

Comments
 (0)