Skip to content

Commit 0a35966

Browse files
committed
feat: Implement Immutable durability level
This durability has slightly special semantics. Values for it are considered unchanging and as such, queries that only rely on inputs of this durability can discard their edges as the query can't possibily be invalidated again (unless LRU cleared).
1 parent c762869 commit 0a35966

File tree

13 files changed

+132
-47
lines changed

13 files changed

+132
-47
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: 34 additions & 14 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,6 +104,19 @@ 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).
@@ -102,15 +128,9 @@ impl Durability {
102128
pub(crate) const MAX: Durability = Self::HIGH;
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/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(

src/input/setter.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ use crate::{Durability, Runtime};
66
/// Setter for a field of an input.
77
pub trait Setter: Sized {
88
type FieldTy;
9+
/// Sets a new durability for the field.
910
fn with_durability(self, durability: Durability) -> Self;
11+
/// Sets the value of the field.
12+
///
13+
/// # Panics
14+
///
15+
/// Panics if `with_durability` was called with [`Durability::IMMUTABLE`].
1016
fn to(self, value: Self::FieldTy) -> Self::FieldTy;
1117
}
1218

src/interned.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ where
475475
.map(|(_, stamp)| (stamp.durability, current_revision))
476476
// If there is no active query this durability does not actually matter.
477477
// `last_interned_at` needs to be `Revision::MAX`, see the `intern_access_in_different_revision` test.
478-
.unwrap_or((Durability::MAX, Revision::max()));
478+
.unwrap_or((Durability::IMMUTABLE, Revision::max()));
479479

480480
let old_id = value_shared.id;
481481

src/runtime.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ pub struct Runtime {
1919
#[cfg_attr(feature = "persistence", serde(skip))]
2020
revision_canceled: AtomicBool,
2121

22-
/// Stores the "last change" revision for values of each duration.
23-
/// This vector is always of length at least 1 (for Durability 0)
24-
/// but its total length depends on the number of durations. The
22+
/// Stores the "last change" revision for values of each [`Durability`].
23+
/// This vector is always of length at least 1 (for [`Durability::MIN`])
24+
/// but its total length depends on the number of durabilities. The
2525
/// element at index 0 is special as it represents the "current
2626
/// revision". In general, we have the invariant that revisions
2727
/// in here are *declining* -- that is, `revisions[i] >=
@@ -209,7 +209,16 @@ impl Runtime {
209209
/// Reports that an input with durability `durability` changed.
210210
/// This will update the 'last changed at' values for every durability
211211
/// less than or equal to `durability` to the current revision.
212+
///
213+
/// # Panics
214+
///
215+
/// Panics if `durability` is `Durability::IMMUTABLE`.
212216
pub(crate) fn report_tracked_write(&mut self, durability: Durability) {
217+
assert_ne!(
218+
durability,
219+
Durability::IMMUTABLE,
220+
"can't write revision of immutable durability"
221+
);
213222
let new_revision = self.current_revision();
214223
self.revisions[1..=durability.index()].fill(new_revision);
215224
}

0 commit comments

Comments
 (0)