Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions src/active_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,6 @@ impl ActiveQuery {
accumulated_inputs,
} = self;

let origin = if untracked_read {
QueryOrigin::derived_untracked(input_outputs.drain(..).collect())
} else {
QueryOrigin::derived(input_outputs.drain(..).collect())
};
disambiguator_map.clear();

#[cfg(feature = "accumulator")]
let accumulated_inputs = AtomicInputAccumulatedValues::new(accumulated_inputs);
let verified_final = cycle_heads.is_empty();
Expand All @@ -222,6 +215,23 @@ impl ActiveQuery {
mem::take(cycle_heads),
iteration_count,
);
#[cfg(not(feature = "accumulator"))]
let has_accumulated_inputs = || false;
#[cfg(feature = "accumulator")]
let has_accumulated_inputs = || accumulated_inputs.load().is_any();
let origin =
if durability == Durability::IMMUTABLE && extra.is_empty() && !has_accumulated_inputs()
{
// We only depend on immutable inputs, we can discard our dependencies
// as we will never be invalidated again
input_outputs.clear();
QueryOrigin::derived_immutable()
} else if untracked_read {
QueryOrigin::derived_untracked(input_outputs.drain(..).collect())
} else {
QueryOrigin::derived(input_outputs.drain(..).collect())
};
disambiguator_map.clear();

let revisions = QueryRevisions {
changed_at,
Expand Down
4 changes: 4 additions & 0 deletions src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ pub trait Database: Send + ZalsaDatabase + AsDynDatabase {
/// cancellation. If you invoke it while a snapshot exists, it
/// will block until that snapshot is dropped -- if that snapshot
/// is owned by the current thread, this could trigger deadlock.
///
/// # Panics
///
/// Panics if `durability` is `Durability::IMMUTABLE`.
fn synthetic_write(&mut self, durability: Durability) {
let zalsa_mut = self.zalsa_mut();
zalsa_mut.new_revision();
Expand Down
52 changes: 36 additions & 16 deletions src/durability.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

/// Describes how likely a value is to change—how "durable" it is.
///
/// By default, inputs have `Durability::LOW` and interned values have
Expand Down Expand Up @@ -42,11 +44,7 @@ impl<'de> serde::Deserialize<'de> for Durability {
impl std::fmt::Debug for Durability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
match self.0 {
DurabilityVal::Low => f.write_str("Durability::LOW"),
DurabilityVal::Medium => f.write_str("Durability::MEDIUM"),
DurabilityVal::High => f.write_str("Durability::HIGH"),
}
fmt::Display::fmt(self, f)
} else {
f.debug_tuple("Durability")
.field(&(self.0 as usize))
Expand All @@ -55,12 +53,26 @@ impl std::fmt::Debug for Durability {
}
}

impl fmt::Display for Durability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Durability::")?;
match self.0 {
DurabilityVal::Low => write!(f, "Low"),
DurabilityVal::Medium => write!(f, "Medium"),
DurabilityVal::High => write!(f, "High"),
DurabilityVal::Immutable => write!(f, "Immutable"),
}
}
}

// We use an enum here instead of a u8 for niches.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
// Note that the order is important here
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
enum DurabilityVal {
Low = 0,
Medium = 1,
High = 2,
Immutable = 3,
}

impl From<u8> for DurabilityVal {
Expand All @@ -69,7 +81,8 @@ impl From<u8> for DurabilityVal {
0 => DurabilityVal::Low,
1 => DurabilityVal::Medium,
2 => DurabilityVal::High,
_ => panic!("invalid durability"),
3 => DurabilityVal::Immutable,
_ => unreachable!("invalid durability"),
}
}
}
Expand All @@ -91,26 +104,33 @@ impl Durability {
/// Example: the standard library or something from crates.io
pub const HIGH: Durability = Durability(DurabilityVal::High);

/// Immutable durability: things that are not expected to change.
///
/// Example: the standard library or something from crates.io
pub const IMMUTABLE: Durability = Durability(DurabilityVal::Immutable);
}

impl Default for Durability {
fn default() -> Self {
Durability::LOW
}
}

impl Durability {
/// The minimum possible durability; equivalent to LOW but
/// "conceptually" distinct (i.e., if we add more durability
/// levels, this could change).
pub(crate) const MIN: Durability = Self::LOW;

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

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

pub(crate) fn index(self) -> usize {
self.0 as usize
}
}

impl Default for Durability {
fn default() -> Self {
Durability::LOW
}
}
3 changes: 3 additions & 0 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ pub struct IngredientImpl<C: Configuration> {
/// we don't know that we can trust the database to give us the same runtime
/// everytime and so forth.
deleted_entries: DeletedEntries<C>,
immutable_memos: crossbeam_queue::SegQueue<Id>,
}

impl<C> IngredientImpl<C>
Expand All @@ -206,6 +207,7 @@ where
deleted_entries: Default::default(),
view_caster: OnceLock::new(),
sync_table: SyncTable::new(index),
immutable_memos: crossbeam_queue::SegQueue::new(),
}
}

Expand Down Expand Up @@ -659,6 +661,7 @@ mod persistence {

QueryOrigin::derived_untracked(flattened_edges.drain(..).collect())
}
QueryOriginRef::DerivedImmutable => QueryOrigin::derived_immutable(),
QueryOriginRef::Assigned(key) => {
let dependency = zalsa.lookup_ingredient(key.ingredient_index());
assert!(
Expand Down
9 changes: 8 additions & 1 deletion src/function/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ where
// outputs and update the tracked struct IDs for seeding the next revision.
self.diff_outputs(zalsa, database_key_index, old_memo, &completed_query);
}

let immutable = completed_query.revisions.origin.is_immutable();
let memo = self.insert_memo(
zalsa,
id,
Expand All @@ -144,6 +144,9 @@ where
if claim_guard.drop() {
None
} else {
if immutable {
self.immutable_memos.push(id);
}
Some(memo)
}
}
Expand Down Expand Up @@ -470,6 +473,7 @@ where
.revisions
.update_iteration_count_mut(database_key_index, iteration_count);

let immutable = completed_query.revisions.origin.is_immutable();
let new_memo = self.insert_memo(
zalsa,
id,
Expand All @@ -480,6 +484,9 @@ where
),
memo_ingredient_index,
);
if immutable {
self.immutable_memos.push(id);
}

last_provisional_memo = Some(new_memo);

Expand Down
9 changes: 7 additions & 2 deletions src/function/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,8 @@ where
));
// We need this for `cycle_heads()` to work. We will unset this in the outer `execute()`.
*completed_query.revisions.verified_final.get_mut() = false;
self.insert_memo(
let immutable = completed_query.revisions.origin.is_immutable();
let memo = self.insert_memo(
zalsa,
id,
Memo::new(
Expand All @@ -283,7 +284,11 @@ where
completed_query.revisions,
),
memo_ingredient_index,
)
);
if immutable {
self.immutable_memos.push(id);
}
memo
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/function/maybe_changed_after.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ pub enum VerifyResult {
}

impl VerifyResult {
pub(crate) const fn changed_if(changed: bool) -> Self {
if changed {
pub(crate) fn changed_after(revision: Revision, after: Revision) -> Self {
if revision > after {
Self::changed()
} else {
Self::unchanged()
Expand Down Expand Up @@ -557,6 +557,8 @@ where
debug_assert!(!cycle_heads.contains_head(database_key_index));

match old_memo.revisions.origin.as_ref() {
// Shouldn't end up here, shallow verify ought to always pass
QueryOriginRef::DerivedImmutable => VerifyResult::unchanged(),
QueryOriginRef::Derived(edges) => {
#[cfg(feature = "accumulator")]
let mut inputs = InputAccumulatedValues::Empty;
Expand Down
3 changes: 2 additions & 1 deletion src/function/memo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ impl<C: Configuration> IngredientImpl<C> {
match memo.revisions.origin.as_ref() {
QueryOriginRef::Assigned(_)
| QueryOriginRef::DerivedUntracked(_)
| QueryOriginRef::FixpointInitial => {
| QueryOriginRef::FixpointInitial
| QueryOriginRef::DerivedImmutable => {
// Careful: Cannot evict memos whose values were
// assigned as output of another query
// or those with untracked inputs
Expand Down
8 changes: 5 additions & 3 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ impl<C: Configuration> IngredientImpl<C> {
/// * `field_index`, index of the field that will be changed
/// * `durability`, durability of the new value. If omitted, uses the durability of the previous value.
/// * `setter`, function that modifies the fields tuple; should only modify the element for `field_index`
///
/// # Panics
///
/// Panics if `durability` is [`Durability::IMMUTABLE`].
pub fn set_field<R>(
&mut self,
runtime: &mut Runtime,
Expand All @@ -195,9 +199,7 @@ impl<C: Configuration> IngredientImpl<C> {
data.revisions[field_index] = runtime.current_revision();

let field_durability = &mut data.durabilities[field_index];
if *field_durability != Durability::MIN {
runtime.report_tracked_write(*field_durability);
}
runtime.report_tracked_write(*field_durability);
*field_durability = durability.unwrap_or(*field_durability);

setter(&mut data.fields)
Expand Down
2 changes: 1 addition & 1 deletion src/input/input_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ where
_cycle_heads: &mut VerifyCycleHeads,
) -> VerifyResult {
let value = <IngredientImpl<C>>::data(zalsa, input);
VerifyResult::changed_if(value.revisions[self.field_index] > revision)
VerifyResult::changed_after(value.revisions[self.field_index], revision)
}

fn collect_minimum_serialized_edges(
Expand Down
6 changes: 6 additions & 0 deletions src/input/setter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ use crate::{Durability, Runtime};
/// Setter for a field of an input.
pub trait Setter: Sized {
type FieldTy;
/// Sets a new durability for the field.
fn with_durability(self, durability: Durability) -> Self;
/// Sets the value of the field.
///
/// # Panics
///
/// Panics if `with_durability` was called with [`Durability::IMMUTABLE`].
fn to(self, value: Self::FieldTy) -> Self::FieldTy;
}

Expand Down
4 changes: 2 additions & 2 deletions src/interned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ where
.map(|(_, stamp)| (stamp.durability, current_revision))
// If there is no active query this durability does not actually matter.
// `last_interned_at` needs to be `Revision::MAX`, see the `intern_access_in_different_revision` test.
.unwrap_or((Durability::MAX, Revision::max()));
.unwrap_or((Durability::IMMUTABLE, Revision::max()));

let old_id = value_shared.id;

Expand Down Expand Up @@ -605,7 +605,7 @@ where
.map(|(_, stamp)| (stamp.durability, current_revision))
// If there is no active query this durability does not actually matter.
// `last_interned_at` needs to be `Revision::MAX`, see the `intern_access_in_different_revision` test.
.unwrap_or((Durability::MAX, Revision::max()));
.unwrap_or((Durability::IMMUTABLE, Revision::max()));

// Allocate the value slot.
let (id, value) = zalsa_local.allocate(zalsa, self.ingredient_index, |id| Value::<C> {
Expand Down
15 changes: 12 additions & 3 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ pub struct Runtime {
#[cfg_attr(feature = "persistence", serde(skip))]
revision_canceled: AtomicBool,

/// Stores the "last change" revision for values of each duration.
/// This vector is always of length at least 1 (for Durability 0)
/// but its total length depends on the number of durations. The
/// Stores the "last change" revision for values of each [`Durability`].
/// This vector is always of length at least 1 (for [`Durability::MIN`])
/// but its total length depends on the number of durabilities. The
/// element at index 0 is special as it represents the "current
/// revision". In general, we have the invariant that revisions
/// in here are *declining* -- that is, `revisions[i] >=
Expand Down Expand Up @@ -209,7 +209,16 @@ impl Runtime {
/// Reports that an input with durability `durability` changed.
/// This will update the 'last changed at' values for every durability
/// less than or equal to `durability` to the current revision.
///
/// # Panics
///
/// Panics if `durability` is `Durability::IMMUTABLE`.
pub(crate) fn report_tracked_write(&mut self, durability: Durability) {
assert_ne!(
durability,
Durability::IMMUTABLE,
"can't write revision of immutable durability"
);
let new_revision = self.current_revision();
self.revisions[1..=durability.index()].fill(new_revision);
}
Expand Down
2 changes: 1 addition & 1 deletion src/tracked_struct/tracked_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ where
) -> VerifyResult {
let data = <super::IngredientImpl<C>>::data(zalsa.table(), input);
let field_changed_at = data.revisions[self.field_index];
VerifyResult::changed_if(field_changed_at > revision)
VerifyResult::changed_after(field_changed_at, revision)
}

fn collect_minimum_serialized_edges(
Expand Down
Loading
Loading