Skip to content

Commit 8ae8c88

Browse files
committed
Record immutable memos in ingredient buffer
1 parent c762869 commit 8ae8c88

File tree

16 files changed

+157
-53
lines changed

16 files changed

+157
-53
lines changed

src/active_query.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,6 @@ impl ActiveQuery {
203203
accumulated_inputs,
204204
} = self;
205205

206-
let origin = if untracked_read {
207-
QueryOrigin::derived_untracked(input_outputs.drain(..).collect())
208-
} else {
209-
QueryOrigin::derived(input_outputs.drain(..).collect())
210-
};
211-
disambiguator_map.clear();
212-
213206
#[cfg(feature = "accumulator")]
214207
let accumulated_inputs = AtomicInputAccumulatedValues::new(accumulated_inputs);
215208
let verified_final = cycle_heads.is_empty();
@@ -222,6 +215,23 @@ impl ActiveQuery {
222215
mem::take(cycle_heads),
223216
iteration_count,
224217
);
218+
#[cfg(not(feature = "accumulator"))]
219+
let has_accumulated_inputs = || false;
220+
#[cfg(feature = "accumulator")]
221+
let has_accumulated_inputs = || accumulated_inputs.load().is_any();
222+
let origin =
223+
if durability == Durability::IMMUTABLE && extra.is_empty() && !has_accumulated_inputs()
224+
{
225+
// We only depend on immutable inputs, we can discard our dependencies
226+
// as we will never be invalidated again
227+
input_outputs.clear();
228+
QueryOrigin::derived_immutable()
229+
} else if untracked_read {
230+
QueryOrigin::derived_untracked(input_outputs.drain(..).collect())
231+
} else {
232+
QueryOrigin::derived(input_outputs.drain(..).collect())
233+
};
234+
disambiguator_map.clear();
225235

226236
let revisions = QueryRevisions {
227237
changed_at,

src/database.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ pub trait Database: Send + ZalsaDatabase + AsDynDatabase {
5353
/// cancellation. If you invoke it while a snapshot exists, it
5454
/// will block until that snapshot is dropped -- if that snapshot
5555
/// is owned by the current thread, this could trigger deadlock.
56+
///
57+
/// # Panics
58+
///
59+
/// Panics if `durability` is `Durability::IMMUTABLE`.
5660
fn synthetic_write(&mut self, durability: Durability) {
5761
let zalsa_mut = self.zalsa_mut();
5862
zalsa_mut.new_revision();

src/durability.rs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::fmt;
2+
13
/// Describes how likely a value is to change—how "durable" it is.
24
///
35
/// By default, inputs have `Durability::LOW` and interned values have
@@ -42,11 +44,7 @@ impl<'de> serde::Deserialize<'de> for Durability {
4244
impl std::fmt::Debug for Durability {
4345
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4446
if f.alternate() {
45-
match self.0 {
46-
DurabilityVal::Low => f.write_str("Durability::LOW"),
47-
DurabilityVal::Medium => f.write_str("Durability::MEDIUM"),
48-
DurabilityVal::High => f.write_str("Durability::HIGH"),
49-
}
47+
fmt::Display::fmt(self, f)
5048
} else {
5149
f.debug_tuple("Durability")
5250
.field(&(self.0 as usize))
@@ -55,12 +53,26 @@ impl std::fmt::Debug for Durability {
5553
}
5654
}
5755

56+
impl fmt::Display for Durability {
57+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58+
write!(f, "Durability::")?;
59+
match self.0 {
60+
DurabilityVal::Low => write!(f, "Low"),
61+
DurabilityVal::Medium => write!(f, "Medium"),
62+
DurabilityVal::High => write!(f, "High"),
63+
DurabilityVal::Immutable => write!(f, "Immutable"),
64+
}
65+
}
66+
}
67+
5868
// We use an enum here instead of a u8 for niches.
59-
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
69+
// Note that the order is important here
70+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
6071
enum DurabilityVal {
6172
Low = 0,
6273
Medium = 1,
6374
High = 2,
75+
Immutable = 3,
6476
}
6577

6678
impl From<u8> for DurabilityVal {
@@ -69,7 +81,8 @@ impl From<u8> for DurabilityVal {
6981
0 => DurabilityVal::Low,
7082
1 => DurabilityVal::Medium,
7183
2 => DurabilityVal::High,
72-
_ => panic!("invalid durability"),
84+
3 => DurabilityVal::Immutable,
85+
_ => unreachable!("invalid durability"),
7386
}
7487
}
7588
}
@@ -91,26 +104,33 @@ impl Durability {
91104
/// Example: the standard library or something from crates.io
92105
pub const HIGH: Durability = Durability(DurabilityVal::High);
93106

107+
/// Immutable durability: things that are not expected to change.
108+
///
109+
/// Example: the standard library or something from crates.io
110+
pub const IMMUTABLE: Durability = Durability(DurabilityVal::Immutable);
111+
}
112+
113+
impl Default for Durability {
114+
fn default() -> Self {
115+
Durability::LOW
116+
}
117+
}
118+
119+
impl Durability {
94120
/// The minimum possible durability; equivalent to LOW but
95121
/// "conceptually" distinct (i.e., if we add more durability
96122
/// levels, this could change).
97123
pub(crate) const MIN: Durability = Self::LOW;
98124

99-
/// The maximum possible durability; equivalent to HIGH but
125+
/// The maximum possible durability; equivalent to IMMUTABLE but
100126
/// "conceptually" distinct (i.e., if we add more durability
101127
/// levels, this could change).
102-
pub(crate) const MAX: Durability = Self::HIGH;
128+
pub(crate) const MAX: Durability = Self::IMMUTABLE;
103129

104130
/// Number of durability levels.
105-
pub(crate) const LEN: usize = Self::HIGH.0 as usize + 1;
131+
pub(crate) const LEN: usize = Self::IMMUTABLE.0 as usize + 1;
106132

107133
pub(crate) fn index(self) -> usize {
108134
self.0 as usize
109135
}
110136
}
111-
112-
impl Default for Durability {
113-
fn default() -> Self {
114-
Durability::LOW
115-
}
116-
}

src/function.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ pub struct IngredientImpl<C: Configuration> {
188188
/// we don't know that we can trust the database to give us the same runtime
189189
/// everytime and so forth.
190190
deleted_entries: DeletedEntries<C>,
191+
immutable_memos: crossbeam_queue::SegQueue<Id>,
191192
}
192193

193194
impl<C> IngredientImpl<C>
@@ -206,6 +207,7 @@ where
206207
deleted_entries: Default::default(),
207208
view_caster: OnceLock::new(),
208209
sync_table: SyncTable::new(index),
210+
immutable_memos: crossbeam_queue::SegQueue::new(),
209211
}
210212
}
211213

@@ -659,6 +661,7 @@ mod persistence {
659661

660662
QueryOrigin::derived_untracked(flattened_edges.drain(..).collect())
661663
}
664+
QueryOriginRef::DerivedImmutable => QueryOrigin::derived_immutable(),
662665
QueryOriginRef::Assigned(key) => {
663666
let dependency = zalsa.lookup_ingredient(key.ingredient_index());
664667
assert!(

src/function/execute.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ where
129129
// outputs and update the tracked struct IDs for seeding the next revision.
130130
self.diff_outputs(zalsa, database_key_index, old_memo, &completed_query);
131131
}
132-
132+
let immutable = completed_query.revisions.origin.is_immutable();
133133
let memo = self.insert_memo(
134134
zalsa,
135135
id,
@@ -144,6 +144,9 @@ where
144144
if claim_guard.drop() {
145145
None
146146
} else {
147+
if immutable {
148+
self.immutable_memos.push(id);
149+
}
147150
Some(memo)
148151
}
149152
}
@@ -470,6 +473,7 @@ where
470473
.revisions
471474
.update_iteration_count_mut(database_key_index, iteration_count);
472475

476+
let immutable = completed_query.revisions.origin.is_immutable();
473477
let new_memo = self.insert_memo(
474478
zalsa,
475479
id,
@@ -480,6 +484,9 @@ where
480484
),
481485
memo_ingredient_index,
482486
);
487+
if immutable {
488+
self.immutable_memos.push(id);
489+
}
483490

484491
last_provisional_memo = Some(new_memo);
485492

src/function/fetch.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ where
274274
));
275275
// We need this for `cycle_heads()` to work. We will unset this in the outer `execute()`.
276276
*completed_query.revisions.verified_final.get_mut() = false;
277-
self.insert_memo(
277+
let immutable = completed_query.revisions.origin.is_immutable();
278+
let memo = self.insert_memo(
278279
zalsa,
279280
id,
280281
Memo::new(
@@ -283,7 +284,11 @@ where
283284
completed_query.revisions,
284285
),
285286
memo_ingredient_index,
286-
)
287+
);
288+
if immutable {
289+
self.immutable_memos.push(id);
290+
}
291+
memo
287292
}
288293
}
289294
}

src/function/maybe_changed_after.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ pub enum VerifyResult {
3030
}
3131

3232
impl VerifyResult {
33-
pub(crate) const fn changed_if(changed: bool) -> Self {
34-
if changed {
33+
pub(crate) fn changed_after(revision: Revision, after: Revision) -> Self {
34+
if revision > after {
3535
Self::changed()
3636
} else {
3737
Self::unchanged()
@@ -557,6 +557,8 @@ where
557557
debug_assert!(!cycle_heads.contains_head(database_key_index));
558558

559559
match old_memo.revisions.origin.as_ref() {
560+
// Shouldn't end up here, shallow verify ought to always pass
561+
QueryOriginRef::DerivedImmutable => VerifyResult::unchanged(),
560562
QueryOriginRef::Derived(edges) => {
561563
#[cfg(feature = "accumulator")]
562564
let mut inputs = InputAccumulatedValues::Empty;

src/function/memo.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ impl<C: Configuration> IngredientImpl<C> {
6868
match memo.revisions.origin.as_ref() {
6969
QueryOriginRef::Assigned(_)
7070
| QueryOriginRef::DerivedUntracked(_)
71-
| QueryOriginRef::FixpointInitial => {
71+
| QueryOriginRef::FixpointInitial
72+
| QueryOriginRef::DerivedImmutable => {
7273
// Careful: Cannot evict memos whose values were
7374
// assigned as output of another query
7475
// or those with untracked inputs

src/input.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ impl<C: Configuration> IngredientImpl<C> {
176176
/// * `field_index`, index of the field that will be changed
177177
/// * `durability`, durability of the new value. If omitted, uses the durability of the previous value.
178178
/// * `setter`, function that modifies the fields tuple; should only modify the element for `field_index`
179+
///
180+
/// # Panics
181+
///
182+
/// Panics if `durability` is [`Durability::IMMUTABLE`].
179183
pub fn set_field<R>(
180184
&mut self,
181185
runtime: &mut Runtime,
@@ -195,9 +199,7 @@ impl<C: Configuration> IngredientImpl<C> {
195199
data.revisions[field_index] = runtime.current_revision();
196200

197201
let field_durability = &mut data.durabilities[field_index];
198-
if *field_durability != Durability::MIN {
199-
runtime.report_tracked_write(*field_durability);
200-
}
202+
runtime.report_tracked_write(*field_durability);
201203
*field_durability = durability.unwrap_or(*field_durability);
202204

203205
setter(&mut data.fields)

src/input/input_field.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ where
6060
_cycle_heads: &mut VerifyCycleHeads,
6161
) -> VerifyResult {
6262
let value = <IngredientImpl<C>>::data(zalsa, input);
63-
VerifyResult::changed_if(value.revisions[self.field_index] > revision)
63+
VerifyResult::changed_after(value.revisions[self.field_index], revision)
6464
}
6565

6666
fn collect_minimum_serialized_edges(

0 commit comments

Comments
 (0)