diff --git a/Cargo.toml b/Cargo.toml index 767fbbbb..1f19a3c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,14 @@ repository = "https://github.com/salsa-rs/salsa" description = "A generic framework for on-demand, incrementalized computation (experimental)" [dependencies] +append-only-vec = { git = "https://github.com/nikomatsakis/append-only-vec.git", version = "0.1.4" } arc-swap = "1.6.0" crossbeam = "0.8.1" dashmap = "5.3.4" hashlink = "0.8.0" indexmap = "2" log = "0.4.5" +orx-concurrent-vec = "1.9.0" parking_lot = "0.12.1" rustc-hash = "1.1.0" salsa-macros = { path = "components/salsa-macros" } diff --git a/src/accumulator.rs b/src/accumulator.rs index 4447b582..cb2cf14f 100644 --- a/src/accumulator.rs +++ b/src/accumulator.rs @@ -1,29 +1,49 @@ //! Basic test of accumulator functionality. -use std::fmt; +use std::{any::Any, fmt, marker::PhantomData}; use crate::{ cycle::CycleRecoveryStrategy, hash::FxDashMap, - ingredient::{fmt_index, Ingredient, IngredientRequiresReset}, + ingredient::{fmt_index, Ingredient, IngredientRequiresReset, Jar}, key::DependencyIndex, runtime::local_state::QueryOrigin, - storage::HasJar, - DatabaseKeyIndex, Event, EventKind, IngredientIndex, Revision, Runtime, + storage::IngredientIndex, + Database, DatabaseKeyIndex, Event, EventKind, Revision, Runtime, }; -pub trait Accumulator { - type Data: Clone; - type Jar; +pub trait Accumulator: Jar { + const DEBUG_NAME: &'static str; - fn accumulator_ingredient(db: &Db) -> &AccumulatorIngredient - where - Db: ?Sized + HasJar; + type Data: Clone; } -pub struct AccumulatorIngredient { + +pub struct AccumulatorJar { + phantom: PhantomData, +} + +impl Default for AccumulatorJar { + fn default() -> Self { + Self { + phantom: Default::default(), + } + } +} + +impl Jar for AccumulatorJar { + type DbView = dyn crate::Database; + + fn create_ingredients( + &self, + first_index: IngredientIndex, + ) -> Vec>> { + vec![Box::new(>::new(first_index))] + } +} + +pub struct AccumulatorIngredient { index: IngredientIndex, - map: FxDashMap>, - debug_name: &'static str, + map: FxDashMap>, } struct AccumulatedValues { @@ -31,12 +51,22 @@ struct AccumulatedValues { values: Vec, } -impl AccumulatorIngredient { - pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self { +impl AccumulatorIngredient { + /// Find the accumulator ingrediate for `A` in the database, if any. + pub fn from_db(db: &Db) -> Option<&Self> + where + Db: ?Sized + Database, + { + let jar: AccumulatorJar = Default::default(); + let index = db.jar_index_by_type_id(jar.type_id())?; + let ingredient = db.ingredient(index).assert_type::(); + Some(ingredient) + } + + pub fn new(index: IngredientIndex) -> Self { Self { map: FxDashMap::default(), index, - debug_name, } } @@ -47,7 +77,7 @@ impl AccumulatorIngredient { } } - pub fn push(&self, runtime: &Runtime, value: Data) { + pub fn push(&self, runtime: &Runtime, value: A::Data) { let current_revision = runtime.current_revision(); let (active_query, _) = match runtime.active_query() { Some(pair) => pair, @@ -77,7 +107,7 @@ impl AccumulatorIngredient { &self, runtime: &Runtime, query: DatabaseKeyIndex, - output: &mut Vec, + output: &mut Vec, ) { let current_revision = runtime.current_revision(); if let Some(v) = self.map.get(&query) { @@ -98,16 +128,19 @@ impl AccumulatorIngredient { } } -impl Ingredient for AccumulatorIngredient -where - DB: crate::Database, - Data: Clone + 'static, -{ +impl Ingredient for AccumulatorIngredient { + type DbView = dyn crate::Database; + fn ingredient_index(&self) -> IngredientIndex { self.index } - fn maybe_changed_after(&self, _db: &DB, _input: DependencyIndex, _revision: Revision) -> bool { + fn maybe_changed_after( + &self, + _db: &Self::DbView, + _input: DependencyIndex, + _revision: Revision, + ) -> bool { panic!("nothing should ever depend on an accumulator directly") } @@ -121,7 +154,7 @@ where fn mark_validated_output( &self, - db: &DB, + db: &Self::DbView, executor: DatabaseKeyIndex, output_key: Option, ) { @@ -135,7 +168,7 @@ where fn remove_stale_output( &self, - db: &DB, + db: &Self::DbView, executor: DatabaseKeyIndex, stale_output_key: Option, ) { @@ -155,18 +188,23 @@ where panic!("unexpected reset on accumulator") } - fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) { + fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: crate::Id) { panic!("unexpected call: accumulator is not registered as a dependent fn"); } fn fmt_index(&self, index: Option, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt_index(self.debug_name, index, fmt) + fmt_index(A::DEBUG_NAME, index, fmt) + } + + fn upcast_to_raw(&self) -> &dyn crate::ingredient::RawIngredient { + self + } + + fn upcast_to_raw_mut(&mut self) -> &mut dyn crate::ingredient::RawIngredient { + self } } -impl IngredientRequiresReset for AccumulatorIngredient -where - Data: Clone, -{ +impl IngredientRequiresReset for AccumulatorIngredient { const RESET_ON_NEW_REVISION: bool = false; } diff --git a/src/database.rs b/src/database.rs index 64599196..72d8ba3a 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,4 +1,10 @@ -use crate::{storage::HasJarsDyn, DebugWithDb, Durability, Event}; +use std::any::Any; + +use crate::{ + ingredient::Ingredient, + storage::{HasJarsDyn, StorageForView}, + DebugWithDb, Durability, Event, +}; pub trait Database: HasJarsDyn + AsSalsaDatabase { /// This function is invoked at key points in the salsa @@ -32,6 +38,12 @@ pub trait Database: HasJarsDyn + AsSalsaDatabase { } } +pub trait DatabaseView: Database { + fn as_dyn(&self) -> &Dyn; + fn as_dyn_mut(&mut self) -> &mut Dyn; + fn storage_for_view(&self) -> &dyn StorageForView; +} + /// Indicates a database that also supports parallel query /// evaluation. All of Salsa's base query support is capable of /// parallel execution, but for it to work, your query key/value types diff --git a/src/function.rs b/src/function.rs index b636ebce..330c1e7d 100644 --- a/src/function.rs +++ b/src/function.rs @@ -5,16 +5,16 @@ use crossbeam::atomic::AtomicCell; use crate::{ cycle::CycleRecoveryStrategy, ingredient::{fmt_index, IngredientRequiresReset}, - jar::Jar, key::{DatabaseKeyIndex, DependencyIndex}, runtime::local_state::QueryOrigin, salsa_struct::SalsaStructInDb, - Cycle, DbWithJar, Event, EventKind, Id, Revision, + storage::{HasJarsDyn, IngredientIndex}, + Cycle, Database, Event, EventKind, Id, Revision, }; use self::delete::DeletedEntries; -use super::{ingredient::Ingredient, routes::IngredientIndex}; +use super::ingredient::Ingredient; mod accumulated; mod backdate; @@ -29,6 +29,47 @@ mod memo; mod specify; mod store; mod sync; +pub trait Configuration: 'static { + const DEBUG_NAME: &'static str; + + /// The database that this function is associated with. + type DbView: ?Sized + crate::Database; + + /// The "salsa struct type" that this function is associated with. + /// This can be just `salsa::Id` for functions that intern their arguments + /// and are not clearly associated with any one salsa struct. + type SalsaStruct<'db>: SalsaStructInDb; + + /// The input to the function + type Input<'db>; + + /// The value computed by the function. + type Value<'db>: fmt::Debug; + + /// Determines whether this function can recover from being a participant in a cycle + /// (and, if so, how). + const CYCLE_STRATEGY: CycleRecoveryStrategy; + + /// Invokes after a new result `new_value`` has been computed for which an older memoized + /// value existed `old_value`. Returns true if the new value is equal to the older one + /// and hence should be "backdated" (i.e., marked as having last changed in an older revision, + /// even though it was recomputed). + /// + /// This invokes user's code in form of the `Eq` impl. + fn should_backdate_value(old_value: &Self::Value<'_>, new_value: &Self::Value<'_>) -> bool; + + /// Invoked when we need to compute the value for the given key, either because we've never + /// computed it before or because the old one relied on inputs that have changed. + /// + /// This invokes the function the user wrote. + fn execute<'db>(db: &'db Self::DbView, key: Id) -> Self::Value<'db>; + + /// If the cycle strategy is `Recover`, then invoked when `key` is a participant + /// in a cycle to find out what value it should have. + /// + /// This invokes the recovery function given by the user. + fn recover_from_cycle<'db>(db: &'db Self::DbView, cycle: &Cycle, key: Id) -> Self::Value<'db>; +} /// Function ingredients are the "workhorse" of salsa. /// They are used for tracked functions, for the "value" fields of tracked structs, and for the fields of input structs. @@ -74,47 +115,6 @@ pub struct FunctionIngredient { registered: AtomicCell, } -pub trait Configuration: 'static { - const DEBUG_NAME: &'static str; - - type Jar: Jar; - - /// The "salsa struct type" that this function is associated with. - /// This can be just `salsa::Id` for functions that intern their arguments - /// and are not clearly associated with any one salsa struct. - type SalsaStruct<'db>: SalsaStructInDb>; - - /// The input to the function - type Input<'db>; - - /// The value computed by the function. - type Value<'db>: fmt::Debug; - - /// Determines whether this function can recover from being a participant in a cycle - /// (and, if so, how). - const CYCLE_STRATEGY: CycleRecoveryStrategy; - - /// Invokes after a new result `new_value`` has been computed for which an older memoized - /// value existed `old_value`. Returns true if the new value is equal to the older one - /// and hence should be "backdated" (i.e., marked as having last changed in an older revision, - /// even though it was recomputed). - /// - /// This invokes user's code in form of the `Eq` impl. - fn should_backdate_value(old_value: &Self::Value<'_>, new_value: &Self::Value<'_>) -> bool; - - /// Invoked when we need to compute the value for the given key, either because we've never - /// computed it before or because the old one relied on inputs that have changed. - /// - /// This invokes the function the user wrote. - fn execute<'db>(db: &'db DynDb, key: Id) -> Self::Value<'db>; - - /// If the cycle strategy is `Recover`, then invoked when `key` is a participant - /// in a cycle to find out what value it should have. - /// - /// This invokes the recovery function given by the user. - fn recover_from_cycle<'db>(db: &'db DynDb, cycle: &Cycle, key: Id) -> Self::Value<'db>; -} - /// True if `old_value == new_value`. Invoked by the generated /// code for `should_backdate_value` so as to give a better /// error message. @@ -122,8 +122,6 @@ pub fn should_backdate_value(old_value: &V, new_value: &V) -> bool { old_value == new_value } -pub type DynDb = <::Jar as Jar>::DynDb; - /// This type is used to make configuration types for the functions in entities; /// e.g. you can do `Config` and `Config`. pub struct Config(std::marker::PhantomData<[(); C]>); @@ -171,7 +169,7 @@ where fn insert_memo<'db>( &'db self, - db: &'db DynDb, + db: &'db C::DbView, key: Id, memo: memo::Memo>, ) -> Option<&C::Value<'db>> { @@ -193,25 +191,30 @@ where /// Register this function as a dependent fn of the given salsa struct. /// When instances of that salsa struct are deleted, we'll get a callback /// so we can remove any data keyed by them. - fn register<'db>(&self, db: &'db DynDb) { + fn register<'db>(&self, db: &'db C::DbView) { if !self.registered.fetch_or(true) { as SalsaStructInDb<_>>::register_dependent_fn(db, self.index) } } } -impl Ingredient for FunctionIngredient +impl Ingredient for FunctionIngredient where - DB: ?Sized + DbWithJar, C: Configuration, { + type DbView = C::DbView; + fn ingredient_index(&self) -> IngredientIndex { self.index } - fn maybe_changed_after(&self, db: &DB, input: DependencyIndex, revision: Revision) -> bool { + fn maybe_changed_after( + &self, + db: &C::DbView, + input: DependencyIndex, + revision: Revision, + ) -> bool { let key = input.key_index.unwrap(); - let db = db.as_jar_db(); self.maybe_changed_after(db, key, revision) } @@ -225,17 +228,17 @@ where fn mark_validated_output( &self, - db: &DB, + db: &C::DbView, executor: DatabaseKeyIndex, output_key: Option, ) { let output_key = output_key.unwrap(); - self.validate_specified_value(db.as_jar_db(), executor, output_key); + self.validate_specified_value(db, executor, output_key); } fn remove_stale_output( &self, - _db: &DB, + _db: &C::DbView, _executor: DatabaseKeyIndex, _stale_output_key: Option, ) { @@ -248,7 +251,7 @@ where std::mem::take(&mut self.deleted_entries); } - fn salsa_struct_deleted(&self, db: &DB, id: Id) { + fn salsa_struct_deleted(&self, db: &C::DbView, id: Id) { // Remove any data keyed by `id`, since `id` no longer // exists in this revision. @@ -270,6 +273,14 @@ where fn fmt_index(&self, index: Option, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt_index(C::DEBUG_NAME, index, fmt) } + + fn upcast_to_raw(&self) -> &dyn crate::ingredient::RawIngredient { + self + } + + fn upcast_to_raw_mut(&mut self) -> &mut dyn crate::ingredient::RawIngredient { + self + } } impl IngredientRequiresReset for FunctionIngredient diff --git a/src/function/accumulated.rs b/src/function/accumulated.rs index 8d8a0cb2..b8d848a0 100644 --- a/src/function/accumulated.rs +++ b/src/function/accumulated.rs @@ -1,11 +1,9 @@ use crate::{ - hash::FxHashSet, - runtime::local_state::QueryOrigin, - storage::{HasJar, HasJarsDyn}, - DatabaseKeyIndex, Id, + accumulator::AccumulatorIngredient, hash::FxHashSet, runtime::local_state::QueryOrigin, + storage::HasJarsDyn, DatabaseKeyIndex, Id, }; -use super::{Configuration, DynDb, FunctionIngredient}; +use super::{Configuration, FunctionIngredient}; use crate::accumulator::Accumulator; impl FunctionIngredient @@ -14,20 +12,22 @@ where { /// Returns all the values accumulated into `accumulator` by this query and its /// transitive inputs. - pub fn accumulated<'db, A>(&'db self, db: &'db DynDb, key: Id) -> Vec + pub fn accumulated<'db, A>(&'db self, db: &'db C::DbView, key: Id) -> Vec where - DynDb: HasJar, A: Accumulator, { // To start, ensure that the value is up to date: self.fetch(db, key); + let Some(accumulator_ingredient) = >::from_db(db) else { + return vec![]; + }; + // Now walk over all the things that the value depended on // and find the values they accumulated into the given // accumulator: let runtime = db.runtime(); let mut result = vec![]; - let accumulator_ingredient = A::accumulator_ingredient(db); let mut stack = Stack::new(self.database_key_index(key)); while let Some(input) = stack.pop() { accumulator_ingredient.produced_by(runtime, input, &mut result); diff --git a/src/function/diff_outputs.rs b/src/function/diff_outputs.rs index bfb1c8ee..720e0a6c 100644 --- a/src/function/diff_outputs.rs +++ b/src/function/diff_outputs.rs @@ -3,7 +3,7 @@ use crate::{ storage::HasJarsDyn, Database, DatabaseKeyIndex, Event, EventKind, }; -use super::{memo::Memo, Configuration, DynDb, FunctionIngredient}; +use super::{memo::Memo, Configuration, FunctionIngredient}; impl FunctionIngredient where @@ -13,7 +13,7 @@ where /// for each output that was generated before but is not generated now. pub(super) fn diff_outputs( &self, - db: &DynDb, + db: &C::DbView, key: DatabaseKeyIndex, old_memo: &Memo>, revisions: &QueryRevisions, @@ -37,7 +37,7 @@ where } } - fn report_stale_output(db: &DynDb, key: DatabaseKeyIndex, output: DependencyIndex) { + fn report_stale_output(db: &C::DbView, key: DatabaseKeyIndex, output: DependencyIndex) { let runtime_id = db.runtime().id(); db.salsa_event(Event { runtime_id, diff --git a/src/function/execute.rs b/src/function/execute.rs index eabf1eba..d77b2e61 100644 --- a/src/function/execute.rs +++ b/src/function/execute.rs @@ -7,7 +7,7 @@ use crate::{ Cycle, Database, Event, EventKind, }; -use super::{memo::Memo, Configuration, DynDb, FunctionIngredient}; +use super::{memo::Memo, Configuration, FunctionIngredient}; impl FunctionIngredient where @@ -24,7 +24,7 @@ where /// * `opt_old_memo`, the older memo, if any existed. Used for backdated. pub(super) fn execute<'db>( &'db self, - db: &'db DynDb, + db: &'db C::DbView, active_query: ActiveQueryGuard<'_>, opt_old_memo: Option>>>, ) -> StampedValue<&C::Value<'db>> { diff --git a/src/function/fetch.rs b/src/function/fetch.rs index 326160e7..39e331a5 100644 --- a/src/function/fetch.rs +++ b/src/function/fetch.rs @@ -2,13 +2,13 @@ use arc_swap::Guard; use crate::{database::AsSalsaDatabase, runtime::StampedValue, storage::HasJarsDyn, Id}; -use super::{Configuration, DynDb, FunctionIngredient}; +use super::{Configuration, FunctionIngredient}; impl FunctionIngredient where C: Configuration, { - pub fn fetch<'db>(&'db self, db: &'db DynDb, key: Id) -> &C::Value<'db> { + pub fn fetch<'db>(&'db self, db: &'db C::DbView, key: Id) -> &C::Value<'db> { let runtime = db.runtime(); runtime.unwind_if_revision_cancelled(db); @@ -35,7 +35,7 @@ where #[inline] fn compute_value<'db>( &'db self, - db: &'db DynDb, + db: &'db C::DbView, key: Id, ) -> StampedValue<&'db C::Value<'db>> { loop { @@ -48,7 +48,7 @@ where #[inline] fn fetch_hot<'db>( &'db self, - db: &'db DynDb, + db: &'db C::DbView, key: Id, ) -> Option>> { let memo_guard = self.memo_map.get(key); @@ -69,7 +69,7 @@ where fn fetch_cold<'db>( &'db self, - db: &'db DynDb, + db: &'db C::DbView, key: Id, ) -> Option>> { let runtime = db.runtime(); diff --git a/src/function/maybe_changed_after.rs b/src/function/maybe_changed_after.rs index f3a4d7f3..def4a939 100644 --- a/src/function/maybe_changed_after.rs +++ b/src/function/maybe_changed_after.rs @@ -12,7 +12,7 @@ use crate::{ Id, Revision, Runtime, }; -use super::{memo::Memo, Configuration, DynDb, FunctionIngredient}; +use super::{memo::Memo, Configuration, FunctionIngredient}; impl FunctionIngredient where @@ -20,7 +20,7 @@ where { pub(super) fn maybe_changed_after<'db>( &'db self, - db: &'db DynDb, + db: &'db C::DbView, key: Id, revision: Revision, ) -> bool { @@ -57,7 +57,7 @@ where fn maybe_changed_after_cold<'db>( &'db self, - db: &'db DynDb, + db: &'db C::DbView, key_index: Id, revision: Revision, ) -> Option { @@ -106,7 +106,7 @@ where #[inline] pub(super) fn shallow_verify_memo( &self, - db: &DynDb, + db: &C::DbView, runtime: &Runtime, database_key_index: DatabaseKeyIndex, memo: &Memo>, @@ -146,7 +146,7 @@ where /// query is on the stack. pub(super) fn deep_verify_memo( &self, - db: &DynDb, + db: &C::DbView, old_memo: &Memo>, active_query: &ActiveQueryGuard<'_>, ) -> bool { diff --git a/src/function/specify.rs b/src/function/specify.rs index 82e9ed92..8f2bad95 100644 --- a/src/function/specify.rs +++ b/src/function/specify.rs @@ -8,7 +8,7 @@ use crate::{ DatabaseKeyIndex, DebugWithDb, Id, }; -use super::{memo::Memo, Configuration, DynDb, FunctionIngredient}; +use super::{memo::Memo, Configuration, FunctionIngredient}; impl FunctionIngredient where @@ -19,12 +19,12 @@ where /// It only works if the key is a tracked struct created in the current query. fn specify<'db>( &'db self, - db: &'db DynDb, + db: &'db C::DbView, key: Id, value: C::Value<'db>, origin: impl Fn(DatabaseKeyIndex) -> QueryOrigin, ) where - C::Input<'db>: TrackedStructInDb>, + C::Input<'db>: TrackedStructInDb, { let runtime = db.runtime(); @@ -94,9 +94,9 @@ where /// Specify the value for `key` *and* record that we did so. /// Used for explicit calls to `specify`, but not needed for pre-declared tracked struct fields. - pub fn specify_and_record<'db>(&'db self, db: &'db DynDb, key: Id, value: C::Value<'db>) + pub fn specify_and_record<'db>(&'db self, db: &'db C::DbView, key: Id, value: C::Value<'db>) where - C::Input<'db>: TrackedStructInDb>, + C::Input<'db>: TrackedStructInDb, { self.specify(db, key, value, |database_key_index| { QueryOrigin::Assigned(database_key_index) @@ -113,7 +113,7 @@ where /// it would have specified `key` again. pub(super) fn validate_specified_value( &self, - db: &DynDb, + db: &C::DbView, executor: DatabaseKeyIndex, key: Id, ) { diff --git a/src/ingredient.rs b/src/ingredient.rs index 4820abf2..825cc809 100644 --- a/src/ingredient.rs +++ b/src/ingredient.rs @@ -1,12 +1,31 @@ -use std::{any::Any, fmt}; +use std::{ + any::{Any, TypeId}, + fmt, +}; use crate::{ cycle::CycleRecoveryStrategy, key::DependencyIndex, runtime::local_state::QueryOrigin, - DatabaseKeyIndex, Id, IngredientIndex, + storage::IngredientIndex, DatabaseKeyIndex, Id, }; use super::Revision; +pub(crate) mod adaptor; + +/// A "jar" is a group of ingredients that are added atomically. +/// Each type implementing jar can be added to the database at most once. +pub trait Jar: Any { + /// The database view trait required by this jar (and all its ingredients). + type DbView: ?Sized; + + /// Create the ingredients given the index of the first one. + /// All subsequent ingredients will be assigned contiguous indices. + fn create_ingredients( + &self, + first_index: IngredientIndex, + ) -> Vec>>; +} + /// "Ingredients" are the bits of data that are stored within the database to make salsa work. /// Each jar will define some number of ingredients that it requires. /// Each use salsa macro (e.g., `#[salsa::tracked]`, `#[salsa::interned]`) adds one or more @@ -16,7 +35,16 @@ use super::Revision; /// The exact ingredients are determined by /// [`IngredientsFor`](`crate::storage::IngredientsFor`) implementations generated by the /// macro. -pub trait Ingredient: Any { +pub trait Ingredient: RawIngredient { + /// The database view trait required by this ingredient. + type DbView: ?Sized; + + /// Workaround lack of builtin trait upcasting; just return `self` in your impl. + fn upcast_to_raw(&self) -> &dyn RawIngredient; + + /// Workaround lack of builtin trait upcasting; just return `self` in your impl. + fn upcast_to_raw_mut(&mut self) -> &mut dyn RawIngredient; + /// Returns the [`IngredientIndex`] of this ingredient. fn ingredient_index(&self) -> IngredientIndex; @@ -28,7 +56,7 @@ pub trait Ingredient: Any { /// Has the value for `input` in this ingredient changed after `revision`? fn maybe_changed_after<'db>( &'db self, - db: &'db DB, + db: &'db Self::DbView, input: DependencyIndex, revision: Revision, ) -> bool; @@ -41,7 +69,7 @@ pub trait Ingredient: Any { /// in the current revision. fn mark_validated_output<'db>( &'db self, - db: &'db DB, + db: &'db Self::DbView, executor: DatabaseKeyIndex, output_key: Option, ); @@ -52,7 +80,7 @@ pub trait Ingredient: Any { /// This hook is used to clear out the stale value so others cannot read it. fn remove_stale_output( &self, - db: &DB, + db: &Self::DbView, executor: DatabaseKeyIndex, stale_output_key: Option, ); @@ -61,7 +89,7 @@ pub trait Ingredient: Any { /// This gives `self` a chance to remove any memoized data dependent on `id`. /// To receive this callback, `self` must register itself as a dependent function using /// [`SalsaStructInDb::register_dependent_fn`](`crate::salsa_struct::SalsaStructInDb::register_dependent_fn`). - fn salsa_struct_deleted(&self, db: &DB, id: Id); + fn salsa_struct_deleted(&self, db: &Self::DbView, id: Id); /// Invoked when a new revision is about to start. /// This moment is important because it means that we have an `&mut`-reference to the @@ -78,6 +106,27 @@ pub trait Ingredient: Any { fn fmt_index(&self, index: Option, fmt: &mut fmt::Formatter<'_>) -> fmt::Result; } +/// The "raw" version of an ingredient can be downcast to more specific types. +/// This trait is automatically implemented and basically just defines an alias +/// for [`Any`][]. +pub trait RawIngredient: Any {} +impl RawIngredient for T {} + +impl dyn RawIngredient { + pub fn assert_type(&self) -> &I { + assert_eq!( + self.type_id(), + TypeId::of::(), + "expecetd a value of type `{}` but type check failed", + std::any::type_name::(), + ); + + let raw: *const dyn RawIngredient = self; + let raw: *const I = raw as _; // disregards the metadata along the way + unsafe { &*raw } // valid b/c of type check above + } +} + /// A helper function to show human readable fmt. pub(crate) fn fmt_index( debug_name: &str, diff --git a/src/ingredient/adaptor.rs b/src/ingredient/adaptor.rs new file mode 100644 index 00000000..464f644a --- /dev/null +++ b/src/ingredient/adaptor.rs @@ -0,0 +1,226 @@ +use std::{any::Any, fmt, marker::PhantomData}; + +use crate::{ + cycle::CycleRecoveryStrategy, key::DependencyIndex, runtime::local_state::QueryOrigin, + storage::IngredientIndex, Database, DatabaseKeyIndex, DatabaseView, Id, Revision, +}; + +use super::{Ingredient, RawIngredient}; + +/// Encapsulates an ingredient whose methods expect some database view that is supported by our database. +/// This is current implemented via double indirection. +/// We can theoretically implement more efficient methods in the future if that ever becomes worthwhile. +pub(crate) struct AdaptedIngredient { + ingredient: Box>, +} + +impl AdaptedIngredient { + pub fn new(ingredient: Box>) -> Self + where + Db: DatabaseView, + DbView: ?Sized + Any, + { + Self { + ingredient: Box::new(AdaptedIngredientImpl::new(ingredient)), + } + } + + /// Return the raw version of the underlying, unadapted ingredient. + pub fn unadapted_ingredient(&self) -> &dyn RawIngredient { + self.ingredient.unadapted_ingredient() + } + + /// Return the raw version of the underlying, unadapted ingredient. + pub fn unadapted_ingredient_mut(&mut self) -> &mut dyn RawIngredient { + self.ingredient.unadapted_ingredient_mut() + } +} + +/// This impl is kind of annoying, it just delegates to self.ingredient, +/// it is meant to be used with static dispatch and hence adds no overhead. +/// The only reason it exists is that it gives us the freedom to implement +/// `AdaptedIngredient` via some more efficient (but unsafe) means +/// in the future. +impl Ingredient for AdaptedIngredient { + type DbView = Db; + + fn ingredient_index(&self) -> IngredientIndex { + self.ingredient.ingredient_index() + } + + fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy { + self.ingredient.cycle_recovery_strategy() + } + + fn maybe_changed_after<'db>( + &'db self, + db: &'db Db, + input: DependencyIndex, + revision: Revision, + ) -> bool { + self.ingredient.maybe_changed_after(db, input, revision) + } + + fn origin(&self, key_index: Id) -> Option { + self.ingredient.origin(key_index) + } + + fn mark_validated_output<'db>( + &'db self, + db: &'db Db, + executor: DatabaseKeyIndex, + output_key: Option, + ) { + self.ingredient + .mark_validated_output(db, executor, output_key) + } + + fn remove_stale_output( + &self, + db: &Db, + executor: DatabaseKeyIndex, + stale_output_key: Option, + ) { + self.ingredient + .remove_stale_output(db, executor, stale_output_key) + } + + fn salsa_struct_deleted(&self, db: &Self::DbView, id: Id) { + self.ingredient.salsa_struct_deleted(db, id) + } + + fn reset_for_new_revision(&mut self) { + self.ingredient.reset_for_new_revision() + } + + fn fmt_index(&self, index: Option, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + self.ingredient.fmt_index(index, fmt) + } + + fn upcast_to_raw(&self) -> &dyn RawIngredient { + // We *would* return `self` here, but this should never be executed. + // We are never really interested in the "raw" version of an adapted ingredient. + // Instead we use `unadapted_ingredient`, above. + panic!("use `unadapted_ingredient` instead") + } + + fn upcast_to_raw_mut(&mut self) -> &mut dyn RawIngredient { + // We *would* return `self` here, but this should never be executed. + // We are never really interested in the "raw" version of an adapted ingredient. + // Instead we use `unadapted_ingredient`, above. + panic!("use `unadapted_ingredient_mut` instead") + } +} + +trait AdaptedIngredientTrait: Ingredient { + fn unadapted_ingredient(&self) -> &dyn RawIngredient; + + fn unadapted_ingredient_mut(&mut self) -> &mut dyn RawIngredient; +} + +/// The adaptation shim we use to add indirection. +/// Given a `DbView`, implements `Ingredient` for any `Db: DatabaseView`. +struct AdaptedIngredientImpl { + ingredient: Box>, + phantom: PhantomData, +} + +impl AdaptedIngredientImpl +where + Db: DatabaseView, +{ + fn new(ingredient: Box>) -> Self { + Self { + ingredient, + phantom: PhantomData, + } + } +} + +impl AdaptedIngredientTrait for AdaptedIngredientImpl +where + Db: DatabaseView, +{ + fn unadapted_ingredient(&self) -> &dyn RawIngredient { + self.ingredient.upcast_to_raw() + } + + fn unadapted_ingredient_mut(&mut self) -> &mut dyn RawIngredient { + self.ingredient.upcast_to_raw_mut() + } +} + +impl Ingredient for AdaptedIngredientImpl +where + Db: DatabaseView, +{ + type DbView = Db; + + fn upcast_to_raw(&self) -> &dyn RawIngredient { + // We *would* return `self` here, but this should never be executed. + // We are never really interested in the "raw" version of an adapted ingredient. + // Instead we use `unadapted_ingredient`, above. + panic!("use `unadapted_ingredient` instead") + } + + fn upcast_to_raw_mut(&mut self) -> &mut dyn RawIngredient { + // We *would* return `self` here, but this should never be executed. + // We are never really interested in the "raw" version of an adapted ingredient. + // Instead we use `unadapted_ingredient`, above. + panic!("use `unadapted_ingredient_mut` instead") + } + + fn ingredient_index(&self) -> IngredientIndex { + self.ingredient.ingredient_index() + } + + fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy { + self.ingredient.cycle_recovery_strategy() + } + + fn maybe_changed_after<'db>( + &'db self, + db: &'db Db, + input: DependencyIndex, + revision: Revision, + ) -> bool { + self.ingredient + .maybe_changed_after(db.as_dyn(), input, revision) + } + + fn origin(&self, key_index: Id) -> Option { + self.ingredient.origin(key_index) + } + + fn mark_validated_output<'db>( + &'db self, + db: &'db Db, + executor: DatabaseKeyIndex, + output_key: Option, + ) { + self.ingredient + .mark_validated_output(db.as_dyn(), executor, output_key) + } + + fn remove_stale_output( + &self, + db: &Db, + executor: DatabaseKeyIndex, + stale_output_key: Option, + ) { + self.ingredient + .remove_stale_output(db.as_dyn(), executor, stale_output_key) + } + + fn salsa_struct_deleted(&self, db: &Self::DbView, id: Id) { + self.ingredient.salsa_struct_deleted(db.as_dyn(), id) + } + + fn reset_for_new_revision(&mut self) { + self.ingredient.reset_for_new_revision() + } + + fn fmt_index(&self, index: Option, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + self.ingredient.fmt_index(index, fmt) + } +} diff --git a/src/ingredient_list.rs b/src/ingredient_list.rs index f80c41ad..3db9a5f5 100644 --- a/src/ingredient_list.rs +++ b/src/ingredient_list.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use arc_swap::{ArcSwapOption, AsRaw}; -use crate::IngredientIndex; +use crate::storage::IngredientIndex; /// A list of ingredients that can be added to in parallel. pub(crate) struct IngredientList { diff --git a/src/input.rs b/src/input.rs index 060b9e17..0d8ae644 100644 --- a/src/input.rs +++ b/src/input.rs @@ -9,7 +9,8 @@ use crate::{ ingredient::{fmt_index, Ingredient, IngredientRequiresReset}, key::{DatabaseKeyIndex, DependencyIndex}, runtime::{local_state::QueryOrigin, Runtime}, - IngredientIndex, Revision, + storage::IngredientIndex, + Database, Revision, }; pub trait InputId: FromId + 'static {} @@ -65,15 +66,22 @@ where } } -impl Ingredient for InputIngredient +impl Ingredient for InputIngredient where Id: InputId, { + type DbView = dyn Database; + fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index } - fn maybe_changed_after(&self, _db: &DB, _input: DependencyIndex, _revision: Revision) -> bool { + fn maybe_changed_after( + &self, + _db: &Self::DbView, + _input: DependencyIndex, + _revision: Revision, + ) -> bool { // Input ingredients are just a counter, they store no data, they are immortal. // Their *fields* are stored in function ingredients elsewhere. false @@ -89,7 +97,7 @@ where fn mark_validated_output( &self, - _db: &DB, + _db: &Self::DbView, executor: DatabaseKeyIndex, output_key: Option, ) { @@ -101,7 +109,7 @@ where fn remove_stale_output( &self, - _db: &DB, + _db: &Self::DbView, executor: DatabaseKeyIndex, stale_output_key: Option, ) { @@ -115,7 +123,7 @@ where panic!("unexpected call to `reset_for_new_revision`") } - fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) { + fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: crate::Id) { panic!( "unexpected call: input ingredients do not register for salsa struct deletion events" ); @@ -124,6 +132,14 @@ where fn fmt_index(&self, index: Option, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt_index(self.debug_name, index, fmt) } + + fn upcast_to_raw(&self) -> &dyn crate::ingredient::RawIngredient { + self + } + + fn upcast_to_raw_mut(&mut self) -> &mut dyn crate::ingredient::RawIngredient { + self + } } impl IngredientRequiresReset for InputIngredient diff --git a/src/input_field.rs b/src/input_field.rs index ca705fef..667340c3 100644 --- a/src/input_field.rs +++ b/src/input_field.rs @@ -5,7 +5,8 @@ use crate::key::DependencyIndex; use crate::plumbing::transmute_lifetime; use crate::runtime::local_state::QueryOrigin; use crate::runtime::StampedValue; -use crate::{DatabaseKeyIndex, Durability, Id, IngredientIndex, Revision, Runtime}; +use crate::storage::IngredientIndex; +use crate::{Database, DatabaseKeyIndex, Durability, Id, Revision, Runtime}; use dashmap::mapref::entry::Entry; use dashmap::DashMap; use std::fmt; @@ -107,11 +108,13 @@ where } } -impl Ingredient for InputFieldIngredient +impl Ingredient for InputFieldIngredient where K: FromId + 'static, F: 'static, { + type DbView = dyn Database; + fn ingredient_index(&self) -> IngredientIndex { self.index } @@ -120,7 +123,12 @@ where CycleRecoveryStrategy::Panic } - fn maybe_changed_after(&self, _db: &DB, input: DependencyIndex, revision: Revision) -> bool { + fn maybe_changed_after( + &self, + _db: &Self::DbView, + input: DependencyIndex, + revision: Revision, + ) -> bool { let key = K::from_id(input.key_index.unwrap()); self.map.get(&key).unwrap().changed_at > revision } @@ -131,7 +139,7 @@ where fn mark_validated_output( &self, - _db: &DB, + _db: &Self::DbView, _executor: DatabaseKeyIndex, _output_key: Option, ) { @@ -139,13 +147,13 @@ where fn remove_stale_output( &self, - _db: &DB, + _db: &Self::DbView, _executor: DatabaseKeyIndex, _stale_output_key: Option, ) { } - fn salsa_struct_deleted(&self, _db: &DB, _id: Id) { + fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: Id) { panic!("unexpected call: input fields are never deleted"); } @@ -156,6 +164,14 @@ where fn fmt_index(&self, index: Option, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt_index(self.debug_name, index, fmt) } + + fn upcast_to_raw(&self) -> &dyn crate::ingredient::RawIngredient { + self + } + + fn upcast_to_raw_mut(&mut self) -> &mut dyn crate::ingredient::RawIngredient { + self + } } impl IngredientRequiresReset for InputFieldIngredient diff --git a/src/interned.rs b/src/interned.rs index acfca12d..c993fe2a 100644 --- a/src/interned.rs +++ b/src/interned.rs @@ -11,11 +11,11 @@ use crate::ingredient::{fmt_index, IngredientRequiresReset}; use crate::key::DependencyIndex; use crate::runtime::local_state::QueryOrigin; use crate::runtime::Runtime; -use crate::{DatabaseKeyIndex, Id}; +use crate::storage::IngredientIndex; +use crate::{Database, DatabaseKeyIndex, Id}; use super::hash::FxDashMap; use super::ingredient::Ingredient; -use super::routes::IngredientIndex; use super::Revision; pub trait Configuration: Sized + 'static { @@ -180,15 +180,22 @@ where } } -impl Ingredient for InternedIngredient +impl Ingredient for InternedIngredient where C: Configuration, { + type DbView = dyn Database; + fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index } - fn maybe_changed_after(&self, _db: &DB, _input: DependencyIndex, revision: Revision) -> bool { + fn maybe_changed_after( + &self, + _db: &Self::DbView, + _input: DependencyIndex, + revision: Revision, + ) -> bool { revision < self.reset_at } @@ -202,7 +209,7 @@ where fn mark_validated_output( &self, - _db: &DB, + _db: &Self::DbView, executor: DatabaseKeyIndex, output_key: Option, ) { @@ -214,7 +221,7 @@ where fn remove_stale_output( &self, - _db: &DB, + _db: &Self::DbView, executor: DatabaseKeyIndex, stale_output_key: Option, ) { @@ -231,13 +238,21 @@ where panic!("unexpected call to `reset_for_new_revision`") } - fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) { + fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: crate::Id) { panic!("unexpected call: interned ingredients do not register for salsa struct deletion events"); } fn fmt_index(&self, index: Option, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt_index(C::DEBUG_NAME, index, fmt) } + + fn upcast_to_raw(&self) -> &dyn crate::ingredient::RawIngredient { + self + } + + fn upcast_to_raw_mut(&mut self) -> &mut dyn crate::ingredient::RawIngredient { + self + } } impl IngredientRequiresReset for InternedIngredient diff --git a/src/key.rs b/src/key.rs index c443b7bb..c3fb9e0d 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use crate::{Database, DebugWithDb, Id, IngredientIndex}; +use crate::{storage::IngredientIndex, Database, DebugWithDb, Id}; /// An integer that uniquely identifies a particular query instance within the /// database. Used to track dependencies between queries. Fully ordered and diff --git a/src/lib.rs b/src/lib.rs index 81245007..0a9781cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,11 +14,10 @@ pub mod ingredient_list; pub mod input; pub mod input_field; pub mod interned; -pub mod jar; pub mod key; +mod nonce; pub mod plumbing; pub mod revision; -pub mod routes; pub mod runtime; pub mod salsa_struct; pub mod setter; @@ -29,6 +28,7 @@ pub mod update; pub use self::cancelled::Cancelled; pub use self::cycle::Cycle; pub use self::database::Database; +pub use self::database::DatabaseView; pub use self::database::ParallelDatabase; pub use self::database::Snapshot; pub use self::debug::DebugWith; @@ -39,9 +39,7 @@ pub use self::event::EventKind; pub use self::id::Id; pub use self::key::DatabaseKeyIndex; pub use self::revision::Revision; -pub use self::routes::IngredientIndex; pub use self::runtime::Runtime; -pub use self::storage::DbWithJar; pub use self::storage::Storage; pub use salsa_macros::accumulator; pub use salsa_macros::db; diff --git a/src/nonce.rs b/src/nonce.rs new file mode 100644 index 00000000..8ebd0856 --- /dev/null +++ b/src/nonce.rs @@ -0,0 +1,33 @@ +use std::{marker::PhantomData, num::NonZeroU32, sync::atomic::AtomicU32}; + +/// A type to generate nonces. Store it in a static and each nonce it produces will be unique from other nonces. +/// The type parameter `T` just serves to distinguish different kinds of nonces. +pub(crate) struct NonceGenerator { + value: AtomicU32, + phantom: PhantomData, +} + +/// A "nonce" is a value that gets created exactly once. +/// We use it to mark the database storage so we can be sure we're seeing the same database. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Nonce(NonZeroU32, PhantomData); + +impl NonceGenerator { + pub(crate) const fn new() -> Self { + Self { + // start at 1 so we can detect rollover more easily + value: AtomicU32::new(1), + phantom: PhantomData, + } + } + + pub(crate) fn nonce(&self) -> Nonce { + let value = self + .value + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + + assert!(value != 0, "nonce rolled over"); + + Nonce(NonZeroU32::new(value).unwrap(), self.phantom) + } +} diff --git a/src/plumbing.rs b/src/plumbing.rs index ddf672d2..7be05616 100644 --- a/src/plumbing.rs +++ b/src/plumbing.rs @@ -1,32 +1,3 @@ -use std::{alloc, ptr}; - -use crate::storage::HasJars; - -/// Initializes the `DB`'s jars in-place -/// -/// # Safety -/// -/// `init` must fully initialize all of jars fields -pub unsafe fn create_jars_inplace(init: impl FnOnce(*mut DB::Jars)) -> Box { - let layout = alloc::Layout::new::(); - - if layout.size() == 0 { - // SAFETY: This is the recommended way of creating a Box - // to a ZST in the std docs - unsafe { Box::from_raw(ptr::NonNull::dangling().as_ptr()) } - } else { - // SAFETY: We've checked that the size isn't 0 - let place = unsafe { alloc::alloc_zeroed(layout) }; - let place = place.cast::(); - - init(place); - - // SAFETY: Caller invariant requires that `init` must've - // initialized all of the fields - unsafe { Box::from_raw(place) } - } -} - // Returns `u` but with the lifetime of `t`. // // Safe if you know that data at `u` will remain shared diff --git a/src/routes.rs b/src/routes.rs deleted file mode 100644 index 655b43fe..00000000 --- a/src/routes.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::ingredient::IngredientRequiresReset; - -use super::{ingredient::Ingredient, storage::HasJars}; - -/// An ingredient index identifies a particular [`Ingredient`] in the database. -/// The database contains a number of jars, and each jar contains a number of ingredients. -/// Each ingredient is given a unique index as the database is being created. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct IngredientIndex(u32); - -impl IngredientIndex { - /// Create an ingredient index from a usize. - pub(crate) fn from(v: usize) -> Self { - assert!(v < (u32::MAX as usize)); - Self(v as u32) - } - - pub(crate) fn as_u32(self) -> u32 { - self.0 - } - - /// Convert the ingredient index back into a usize. - pub(crate) fn as_usize(self) -> usize { - self.0 as usize - } -} - -/// A "route" is a function that, given a `&DB::Jars`, returns an `&dyn Ingredient`. -/// Routes are constructed (in part) from closures generated by the salsa macros. -/// These closures look essentially like `|jar| &jar.some_field` -- i.e., if a jar is a struct, -/// the closure returns a reference to some particular field of the struct -/// (whichever field has the database for this ingredient). -/// -/// The key point here is: the struct definitions that are being referencd here come from -/// crates that consume this crate, and hence we cannot name them directly. -/// We have to navigate them through closures generated by that downstream crate. -#[allow(type_alias_bounds)] -#[allow(unused_parens)] -pub type DynRoute = dyn Fn(&DB::Jars) -> (&dyn Ingredient) + Send + Sync; - -/// Like a `DynRoute`, but for `&mut` references. -#[allow(type_alias_bounds)] -#[allow(unused_parens)] -pub type DynMutRoute = - dyn Fn(&mut DB::Jars) -> (&mut dyn Ingredient) + Send + Sync; - -/// The "routes" structure is used to navigate the database. -/// The database contains a number of jars, and each jar contains a number of ingredients. -/// When the database is created, it creates each jar in turn. -/// Each jar then creates its ingredients. -/// Each ingredient is registered with the database by invoking the [`Routes::push`] method. -/// This method assigns it a unique [`IngredientIndex`] and stores some callbacks indicating -/// how to find the ingredient later based only on the index. -pub struct Routes { - /// Vector indexed by ingredient index. Yields the `DynRoute`, - /// a function which can be applied to the `DB::Jars` to yield - /// the `dyn Ingredient. - #[allow(clippy::type_complexity)] - routes: Vec<(Box>, Box>)>, - - /// Indices of routes which need a 'reset' call. - needs_reset: Vec, -} - -impl Routes { - /// Construct an empty ingredients listing. - pub(super) fn new() -> Self { - Routes { - routes: vec![], - needs_reset: vec![], - } - } - - /// Adds a new ingredient into the ingredients table, returning - /// the `IngredientIndex` that can be used in a `DatabaseKeyIndex`. - /// This index can then be used to fetch the "route" so that we can - /// dispatch calls to `maybe_changed_after`. - /// - /// # Parameters - /// - /// * `requires_reset` -- if true, the [`Ingredient::reset_for_new_revision`] method will be called on this ingredient - /// at each new revision. See that method for more information. - /// * `route` -- a closure which, given a database, will identify the ingredient. - /// This closure will be invoked to dispatch calls to `maybe_changed_after`. - /// * `mut_route` -- a closure which identifies the ingredient in a mut - /// database. - pub fn push( - &mut self, - route: impl (Fn(&DB::Jars) -> &I) + Send + Sync + 'static, - mut_route: impl (Fn(&mut DB::Jars) -> &mut I) + Send + Sync + 'static, - ) -> IngredientIndex - where - I: Ingredient + IngredientRequiresReset + 'static, - { - let len = self.routes.len(); - self.routes.push(( - Box::new(move |jars| route(jars)), - Box::new(move |jars| mut_route(jars)), - )); - let index = IngredientIndex::from(len); - - if I::RESET_ON_NEW_REVISION { - self.needs_reset.push(index); - } - - index - } - - /// Given an ingredient index, return the "route" - /// (a function that, given a `&Jars`, returns the ingredient). - pub fn route(&self, index: IngredientIndex) -> &dyn Fn(&DB::Jars) -> &dyn Ingredient { - &self.routes[index.as_usize()].0 - } - - /// Given an ingredient index, return the "mut route" - /// (a function that, given an `&mut Jars`, returns the ingredient). - pub fn route_mut( - &self, - index: IngredientIndex, - ) -> &dyn Fn(&mut DB::Jars) -> &mut dyn Ingredient { - &self.routes[index.as_usize()].1 - } - - /// Returns the mut routes for ingredients that need to be reset at the start of each revision. - pub fn reset_routes( - &self, - ) -> impl Iterator &mut dyn Ingredient> + '_ { - self.needs_reset.iter().map(|&index| self.route_mut(index)) - } -} diff --git a/src/runtime.rs b/src/runtime.rs index 727f8342..a61d5b3b 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -9,6 +9,7 @@ use crate::{ durability::Durability, key::{DatabaseKeyIndex, DependencyIndex}, runtime::active_query::ActiveQuery, + storage::IngredientIndex, Cancelled, Cycle, Database, Event, EventKind, Revision, }; @@ -17,7 +18,7 @@ use self::{ local_state::{ActiveQueryGuard, EdgeKind}, }; -use super::{tracked_struct::Disambiguator, IngredientIndex}; +use super::tracked_struct::Disambiguator; mod active_query; mod dependency_graph; diff --git a/src/salsa_struct.rs b/src/salsa_struct.rs index 913d85d6..1ea8c885 100644 --- a/src/salsa_struct.rs +++ b/src/salsa_struct.rs @@ -1,4 +1,4 @@ -use crate::{Database, IngredientIndex}; +use crate::{storage::IngredientIndex, Database}; pub trait SalsaStructInDb { fn register_dependent_fn(db: &DB, index: IngredientIndex); diff --git a/src/storage.rs b/src/storage.rs index c857a639..85017e35 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,32 +1,65 @@ +use std::any::{Any, TypeId}; +use std::ptr::NonNull; use std::{fmt, sync::Arc}; -use parking_lot::Condvar; +use append_only_vec::AppendOnlyVec; +use crossbeam::atomic::AtomicCell; +use parking_lot::{Condvar, Mutex}; +use rustc_hash::FxHashMap; use crate::cycle::CycleRecoveryStrategy; -use crate::ingredient::Ingredient; -use crate::jar::Jar; +use crate::ingredient::adaptor::AdaptedIngredient; +use crate::ingredient::{Ingredient, Jar, RawIngredient}; use crate::key::DependencyIndex; +use crate::nonce::{Nonce, NonceGenerator}; use crate::runtime::local_state::QueryOrigin; use crate::runtime::Runtime; -use crate::{Database, DatabaseKeyIndex, Id, IngredientIndex}; +use crate::{Database, DatabaseKeyIndex, DatabaseView, Id}; -use super::routes::Routes; use super::{ParallelDatabase, Revision}; +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct StorageNonce; +static NONCE: NonceGenerator = NonceGenerator::new(); + +/// An ingredient index identifies a particular [`Ingredient`] in the database. +/// The database contains a number of jars, and each jar contains a number of ingredients. +/// Each ingredient is given a unique index as the database is being created. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct IngredientIndex(u32); + +impl IngredientIndex { + /// Create an ingredient index from a usize. + pub(crate) fn from(v: usize) -> Self { + assert!(v < (u32::MAX as usize)); + Self(v as u32) + } + + pub(crate) fn as_u32(self) -> u32 { + self.0 + } + + /// Convert the ingredient index back into a usize. + pub(crate) fn as_usize(self) -> usize { + self.0 as usize + } +} + +impl std::ops::Add for IngredientIndex { + type Output = IngredientIndex; + + fn add(self, rhs: u32) -> Self::Output { + IngredientIndex(self.0.checked_add(rhs).unwrap()) + } +} + /// The "storage" struct stores all the data for the jars. /// It is shared between the main database and any active snapshots. -pub struct Storage { +pub struct Storage { /// Data shared across all databases. This contains the ingredients needed by each jar. /// See the ["jars and ingredients" chapter](https://salsa-rs.github.io/salsa/plumbing/jars_and_ingredients.html) /// for more detailed description. - shared: Shared, - - /// The "ingredients" structure stores the information about how to find each ingredient in the database. - /// It allows us to take the [`IngredientIndex`] assigned to a particular ingredient - /// and get back a [`dyn Ingredient`][`Ingredient`] for the struct that stores its data. - /// - /// This is kept separate from `shared` so that we can clone it and retain `&`-access even when we have `&mut` access to `shared`. - routes: Arc>, + shared: Shared, /// The runtime for this particular salsa database handle. /// Each handle gets its own runtime, but the runtimes have shared state between them. @@ -36,99 +69,111 @@ pub struct Storage { /// Data shared between all threads. /// This is where the actual data for tracked functions, structs, inputs, etc lives, /// along with some coordination variables between treads. -struct Shared { - /// Contains the data for each jar in the database. - /// Each jar stores its own structs in there that ultimately contain ingredients - /// (types that implement the [`Ingredient`] trait, like [`crate::function::FunctionIngredient`]). +struct Shared { + nonce: Nonce, + + /// Map from the type-id of an `impl Jar` to the index of its first ingredient. + /// This is using a `Mutex` (versus, say, a `FxDashMap`) + /// so that we can protect `ingredients_vec` as well and predict what the + /// first ingredient index will be. This allows ingredients to store their own indices. + /// This may be worth refactoring in the future because it naturally adds more overhead to + /// adding new kinds of ingredients. + jar_map: Arc>>, + + /// Vector of ingredients. /// - /// Even though these jars are stored in an `Arc`, we sometimes get mutable access to them - /// by using `Arc::get_mut`. This is only possible when all parallel snapshots have been dropped. - jars: Option>, + /// Immutable unless the mutex on `ingredients_map` is held. + ingredients_vec: Arc>>, /// Conditional variable that is used to coordinate cancellation. /// When the main thread writes to the database, it blocks until each of the snapshots can be cancelled. cvar: Arc, + /// A dummy varible that we use to coordinate how many outstanding database handles exist. + /// This is set to `None` when dropping only. + sync: Option>, + /// Mutex that is used to protect the `jars` field when waiting for snapshots to be dropped. noti_lock: Arc>, } // ANCHOR: default -impl Default for Storage -where - DB: HasJars, -{ +impl Default for Storage { fn default() -> Self { - let mut routes = Routes::new(); - let jars = DB::create_jars(&mut routes); Self { shared: Shared { - jars: Some(Arc::from(jars)), + nonce: NONCE.nonce(), cvar: Arc::new(Default::default()), noti_lock: Arc::new(parking_lot::Mutex::new(())), + jar_map: Default::default(), + ingredients_vec: Arc::new(AppendOnlyVec::new()), + sync: Some(Arc::new(())), }, - routes: Arc::new(routes), runtime: Runtime::default(), } } } // ANCHOR_END: default -impl Storage -where - DB: HasJars, -{ - pub fn snapshot(&self) -> Storage +impl Storage { + /// Adds the ingredients in `jar` to the database if not already present. + /// If a jar of this type is already present, returns the index. + fn add_or_lookup_adapted_jar_by_type( + &self, + jar: &dyn Jar, + ) -> IngredientIndex where - DB: ParallelDatabase, + Db: DatabaseView, + DbView: ?Sized + Any, + { + let jar_type_id = jar.type_id(); + let mut jar_map = self.shared.jar_map.lock(); + *jar_map + .entry(jar_type_id) + .or_insert_with(|| { + let index = IngredientIndex::from(self.shared.ingredients_vec.len()); + let ingredients = jar.create_ingredients(index); + for ingredient in ingredients { + let expected_index = ingredient.ingredient_index(); + let actual_index = self + .shared + .ingredients_vec + .push(AdaptedIngredient::new(ingredient)); + assert_eq!( + expected_index.as_usize(), + actual_index, + "index predicted for ingredient (`{:?}`) does not align with assigned index (`{:?}`)", + expected_index, + actual_index, + ); + } + index + }) + } + + /// Return the index of the 1st ingredient from the given jar. + pub fn lookup_jar_by_type(&self, jar_type_id: TypeId) -> Option { + self.shared.jar_map.lock().get(&jar_type_id).copied() + } + + pub fn lookup_ingredient(&self, index: IngredientIndex) -> &dyn RawIngredient { + self.shared.ingredients_vec[index.as_usize()].unadapted_ingredient() + } + + pub fn snapshot(&self) -> Storage + where + Db: ParallelDatabase, { Self { shared: self.shared.clone(), - routes: self.routes.clone(), runtime: self.runtime.snapshot(), } } - pub fn jars(&self) -> (&DB::Jars, &Runtime) { - (self.shared.jars.as_ref().unwrap(), &self.runtime) - } - pub fn runtime(&self) -> &Runtime { &self.runtime } - pub fn runtime_mut(&mut self) -> &mut Runtime { - self.jars_mut().1 - } - - // ANCHOR: jars_mut - /// Gets mutable access to the jars. This will trigger a new revision - /// and it will also cancel any ongoing work in the current revision. - /// Any actual writes that occur to data in a jar should use - /// [`Runtime::report_tracked_write`]. - pub fn jars_mut(&mut self) -> (&mut DB::Jars, &mut Runtime) { - // Wait for all snapshots to be dropped. - self.cancel_other_workers(); - - // Increment revision counter. - self.runtime.new_revision(); - - // Acquire `&mut` access to `self.shared` -- this is only possible because - // the snapshots have all been dropped, so we hold the only handle to the `Arc`. - let jars = Arc::get_mut(self.shared.jars.as_mut().unwrap()).unwrap(); - - // Inform other ingredients that a new revision has begun. - // This gives them a chance to free resources that were being held until the next revision. - let routes = self.routes.clone(); - for route in routes.reset_routes() { - route(jars).reset_for_new_revision(); - } - - // Return mut ref to jars + runtime. - (jars, &mut self.runtime) - } - // ANCHOR_END: jars_mut - // ANCHOR: cancel_other_workers /// Sets cancellation flag and blocks until all other workers with access /// to this storage have completed. @@ -148,8 +193,9 @@ where // unique access and go to sleep waiting on the condvar atomically, // as described in PR #474. let mut guard = self.shared.noti_lock.lock(); - // If we have unique access to the jars, we are done. - if Arc::get_mut(self.shared.jars.as_mut().unwrap()).is_some() { + + // If we have unique access to the ingredients vec, we are done. + if Arc::get_mut(self.shared.sync.as_mut().unwrap()).is_some() { return; } @@ -160,69 +206,36 @@ where } } // ANCHOR_END: cancel_other_workers - - pub fn ingredient(&self, ingredient_index: IngredientIndex) -> &dyn Ingredient { - let route = self.routes.route(ingredient_index); - route(self.shared.jars.as_ref().unwrap()) - } } -impl Clone for Shared -where - DB: HasJars, -{ +impl Clone for Shared { fn clone(&self) -> Self { Self { - jars: self.jars.clone(), + nonce: self.nonce.clone(), + jar_map: self.jar_map.clone(), + ingredients_vec: self.ingredients_vec.clone(), cvar: self.cvar.clone(), noti_lock: self.noti_lock.clone(), + sync: self.sync.clone(), } } } -impl Drop for Storage -where - DB: HasJars, -{ +impl Drop for Storage { fn drop(&mut self) { - // Drop the Arc reference before the cvar is notified, - // since other threads are sleeping, waiting for it to reach 1. + // Careful: if this is a snapshot on the main handle, + // we need to notify `shared.cvar` to make sure that the + // master thread wakes up. *And*, when it does wake-up, we need to be sure + // that the ref count on `self.shared.sync` has already been decremented. + // So we take the value of `self.shared.sync` now and then notify the cvar. + // + // If this is the master thread, this dance has no real effect. let _guard = self.shared.noti_lock.lock(); - drop(self.shared.jars.take()); + drop(self.shared.sync.take()); self.shared.cvar.notify_all(); } } -pub trait HasJars: HasJarsDyn + Sized { - type Jars; - - fn jars(&self) -> (&Self::Jars, &Runtime); - - /// Gets mutable access to the jars. This will trigger a new revision - /// and it will also cancel any ongoing work in the current revision. - fn jars_mut(&mut self) -> (&mut Self::Jars, &mut Runtime); - - fn create_jars(routes: &mut Routes) -> Box; -} - -pub trait DbWithJar: HasJar + Database { - fn as_jar_db<'db>(&'db self) -> &'db ::DynDb - where - J: Jar; -} - -pub trait JarFromJars: HasJars { - fn jar_from_jars(jars: &Self::Jars) -> &J; - - fn jar_from_jars_mut(jars: &mut Self::Jars) -> &mut J; -} - -pub trait HasJar { - fn jar(&self) -> (&J, &Runtime); - - fn jar_mut(&mut self) -> (&mut J, &mut Runtime); -} - // ANCHOR: HasJarsDyn /// Dyn friendly subset of HasJars pub trait HasJarsDyn: 'static { @@ -230,6 +243,10 @@ pub trait HasJarsDyn: 'static { fn runtime_mut(&mut self) -> &mut Runtime; + fn ingredient(&self, index: IngredientIndex) -> &dyn RawIngredient; + + fn jar_index_by_type_id(&self, type_id: TypeId) -> Option; + fn maybe_changed_after(&self, input: DependencyIndex, revision: Revision) -> bool; fn cycle_recovery_strategy(&self, input: IngredientIndex) -> CycleRecoveryStrategy; @@ -256,19 +273,82 @@ pub trait HasJarsDyn: 'static { } // ANCHOR_END: HasJarsDyn -pub trait HasIngredientsFor +pub trait StorageForView { + fn nonce(&self) -> Nonce; + + /// Lookup the index assigned to the given jar (if any). This lookup is based purely on the jar's type. + fn lookup_jar_by_type(&self, jar: &dyn Jar) -> Option; + + /// Adds a jar to the database, returning the index of the first ingredient. + /// If a jar of this type is already present, returns the existing index. + fn add_or_lookup_jar_by_type(&self, jar: &dyn Jar) -> IngredientIndex; + + /// Gets an ingredient by index + fn lookup_ingredient(&self, index: IngredientIndex) -> &dyn RawIngredient; +} + +impl StorageForView for Storage where - I: IngredientsFor, + Db: DatabaseView, + DbView: ?Sized + Any, { - fn ingredient(&self) -> &I::Ingredients; - fn ingredient_mut(&mut self) -> &mut I::Ingredients; + fn add_or_lookup_jar_by_type(&self, jar: &dyn Jar) -> IngredientIndex { + self.add_or_lookup_adapted_jar_by_type(jar) + } + + fn nonce(&self) -> Nonce { + self.shared.nonce + } + + fn lookup_jar_by_type(&self, jar: &dyn Jar) -> Option { + self.lookup_jar_by_type(jar.type_id()) + } + + fn lookup_ingredient(&self, index: IngredientIndex) -> &dyn RawIngredient { + self.lookup_ingredient(index) + } } -pub trait IngredientsFor { - type Jar; - type Ingredients; - - fn create_ingredients(routes: &mut Routes) -> Self::Ingredients - where - DB: DbWithJar + JarFromJars; +/// Caches a pointer to an ingredient in a database. +/// Optimized for the case of a single database. +pub struct IngredientCache +where + I: Ingredient, + DbView: ?Sized, +{ + cached_data: std::sync::OnceLock<(Nonce, *const I)>, +} + +impl IngredientCache +where + I: Ingredient, + DbView: ?Sized, +{ + /// Get a reference to the ingredient in the database. + /// If the ingredient is not already in the cache, it will be created. + pub fn get_or_create<'s>( + &self, + storage: &'s dyn StorageForView, + create_index: impl Fn() -> IngredientIndex, + ) -> &'s I { + let &(nonce, ingredient) = self.cached_data.get_or_init(|| { + let ingredient = self.create_ingredient(storage, &create_index); + (storage.nonce(), ingredient as *const I) + }); + + if storage.nonce() == nonce { + unsafe { &*ingredient } + } else { + self.create_ingredient(storage, &create_index) + } + } + + fn create_ingredient<'s>( + &self, + storage: &'s dyn StorageForView, + create_index: &impl Fn() -> IngredientIndex, + ) -> &'s I { + let index = create_index(); + storage.lookup_ingredient(index).assert_type::() + } } diff --git a/src/tracked_struct.rs b/src/tracked_struct.rs index 95b53860..27eda4d6 100644 --- a/src/tracked_struct.rs +++ b/src/tracked_struct.rs @@ -1,4 +1,4 @@ -use std::{fmt, hash::Hash, ptr::NonNull}; +use std::{fmt, hash::Hash, marker::PhantomData, ptr::NonNull}; use crossbeam::atomic::AtomicCell; use dashmap::mapref::entry::Entry; @@ -7,12 +7,13 @@ use crate::{ cycle::CycleRecoveryStrategy, hash::FxDashMap, id::AsId, - ingredient::{fmt_index, Ingredient, IngredientRequiresReset}, + ingredient::{fmt_index, Ingredient, IngredientRequiresReset, Jar}, ingredient_list::IngredientList, key::{DatabaseKeyIndex, DependencyIndex}, runtime::{local_state::QueryOrigin, Runtime}, salsa_struct::SalsaStructInDb, - Database, Durability, Event, Id, IngredientIndex, Revision, + storage::IngredientIndex, + Database, Durability, Event, Id, Revision, }; use self::struct_map::{StructMap, Update}; @@ -25,8 +26,9 @@ mod tracked_field; /// Trait that defines the key properties of a tracked struct. /// Implemented by the `#[salsa::tracked]` macro when applied /// to a struct. -pub trait Configuration: Sized + 'static { +pub trait Configuration: Jar + Sized + 'static { const DEBUG_NAME: &'static str; + const FIELD_DEBUG_NAMES: &'static [&'static str]; /// A (possibly empty) tuple of the fields for this struct. type Fields<'db>; @@ -97,6 +99,45 @@ pub trait Configuration: Sized + 'static { } // ANCHOR_END: Configuration +pub struct TrackedStructJar +where + C: Configuration, +{ + phantom: PhantomData, +} + +impl Default for TrackedStructJar { + fn default() -> Self { + Self { + phantom: Default::default(), + } + } +} + +impl Jar for TrackedStructJar { + type DbView = dyn Database; + + fn create_ingredients( + &self, + struct_index: crate::storage::IngredientIndex, + ) -> Vec>> { + let struct_ingredient = TrackedStructIngredient::new(struct_index); + let struct_map = &struct_ingredient.struct_map.view(); + + std::iter::once(Box::new(struct_ingredient) as _) + .chain( + (0..u32::try_from(C::FIELD_DEBUG_NAMES.len()).unwrap()).map(|field_index| { + Box::new(TrackedFieldIngredient::::new( + struct_index, + field_index, + struct_map, + )) as _ + }), + ) + .collect() + } +} + pub trait TrackedStructInDb: SalsaStructInDb { /// Converts the identifier for this tracked struct into a `DatabaseKeyIndex`. fn database_key_index(db: &DB, id: Id) -> DatabaseKeyIndex; @@ -215,7 +256,7 @@ where /// Create a tracked struct ingredient. Generated by the `#[tracked]` macro, /// not meant to be called directly by end-users. - pub fn new(index: IngredientIndex) -> Self { + fn new(index: IngredientIndex) -> Self { Self { ingredient_index: index, keys: FxDashMap::default(), @@ -225,27 +266,6 @@ where } } - /// Creates and returns a new field ingredient for the database. - /// Invoked by the `#[tracked]` struct and not meant to be called by end-users. - pub fn new_field_ingredient( - &self, - field_ingredient_index: IngredientIndex, - field_index: u32, - field_debug_name: &'static str, - ) -> TrackedFieldIngredient { - assert_eq!( - field_ingredient_index.as_u32() - self.ingredient_index.as_u32() - 1, - field_index, - ); - - TrackedFieldIngredient:: { - ingredient_index: field_ingredient_index, - field_index, - struct_map: self.struct_map.view(), - field_debug_name, - } - } - /// Returns the database key index for a tracked struct with the given id. pub fn database_key_index(&self, id: Id) -> DatabaseKeyIndex { DatabaseKeyIndex { @@ -401,16 +421,22 @@ where } } -impl Ingredient for TrackedStructIngredient +impl Ingredient for TrackedStructIngredient where - DB: Database, C: Configuration, { + type DbView = dyn Database; + fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index } - fn maybe_changed_after(&self, _db: &DB, _input: DependencyIndex, _revision: Revision) -> bool { + fn maybe_changed_after( + &self, + _db: &Self::DbView, + _input: DependencyIndex, + _revision: Revision, + ) -> bool { false } @@ -424,7 +450,7 @@ where fn mark_validated_output<'db>( &'db self, - db: &'db DB, + db: &'db Self::DbView, _executor: DatabaseKeyIndex, output_key: Option, ) { @@ -435,7 +461,7 @@ where fn remove_stale_output( &self, - db: &DB, + db: &Self::DbView, _executor: DatabaseKeyIndex, stale_output_key: Option, ) { @@ -450,13 +476,21 @@ where self.struct_map.drop_deleted_entries(); } - fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) { + fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: crate::Id) { panic!("unexpected call: interned ingredients do not register for salsa struct deletion events"); } fn fmt_index(&self, index: Option, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt_index(C::DEBUG_NAME, index, fmt) } + + fn upcast_to_raw(&self) -> &dyn crate::ingredient::RawIngredient { + self + } + + fn upcast_to_raw_mut(&mut self) -> &mut dyn crate::ingredient::RawIngredient { + self + } } impl IngredientRequiresReset for TrackedStructIngredient diff --git a/src/tracked_struct/struct_map.rs b/src/tracked_struct/struct_map.rs index 4bf98206..d1dcd40c 100644 --- a/src/tracked_struct/struct_map.rs +++ b/src/tracked_struct/struct_map.rs @@ -35,6 +35,14 @@ where map: Arc>>>, } +impl Clone for StructMapView { + fn clone(&self) -> Self { + Self { + map: self.map.clone(), + } + } +} + /// Return value for [`StructMap`][]'s `update` method. pub(crate) enum Update<'db, C> where diff --git a/src/tracked_struct/tracked_field.rs b/src/tracked_struct/tracked_field.rs index 0774a471..1df7525b 100644 --- a/src/tracked_struct/tracked_field.rs +++ b/src/tracked_struct/tracked_field.rs @@ -2,7 +2,8 @@ use crate::{ id::AsId, ingredient::{Ingredient, IngredientRequiresReset}, key::DependencyIndex, - Database, Id, IngredientIndex, Runtime, + storage::IngredientIndex, + Database, Id, Runtime, }; use super::{struct_map::StructMapView, Configuration}; @@ -20,16 +21,27 @@ where C: Configuration, { /// Index of this ingredient in the database (used to construct database-ids, etc). - pub(super) ingredient_index: IngredientIndex, - pub(super) field_index: u32, - pub(super) struct_map: StructMapView, - pub(super) field_debug_name: &'static str, + ingredient_index: IngredientIndex, + field_index: u32, + struct_map: StructMapView, } impl TrackedFieldIngredient where C: Configuration, { + pub(super) fn new( + struct_index: IngredientIndex, + field_index: u32, + struct_map: &StructMapView, + ) -> Self { + Self { + ingredient_index: struct_index + field_index, + field_index, + struct_map: struct_map.clone(), + } + } + unsafe fn to_self_ref<'db>(&'db self, fields: &'db C::Fields<'static>) -> &'db C::Fields<'db> { unsafe { std::mem::transmute(fields) } } @@ -56,11 +68,12 @@ where } } -impl Ingredient for TrackedFieldIngredient +impl Ingredient for TrackedFieldIngredient where - DB: Database, C: Configuration, { + type DbView = dyn Database; + fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index } @@ -71,7 +84,7 @@ where fn maybe_changed_after<'db>( &'db self, - db: &'db DB, + db: &'db Self::DbView, input: crate::key::DependencyIndex, revision: crate::Revision, ) -> bool { @@ -89,7 +102,7 @@ where fn mark_validated_output( &self, - _db: &DB, + _db: &Self::DbView, _executor: crate::DatabaseKeyIndex, _output_key: Option, ) { @@ -98,14 +111,14 @@ where fn remove_stale_output( &self, - _db: &DB, + _db: &Self::DbView, _executor: crate::DatabaseKeyIndex, _stale_output_key: Option, ) { panic!("tracked field ingredients have no outputs") } - fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) { + fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: crate::Id) { panic!("tracked field ingredients are not registered as dependent") } @@ -122,10 +135,18 @@ where fmt, "{}.{}({:?})", C::DEBUG_NAME, - self.field_debug_name, + C::FIELD_DEBUG_NAMES[self.field_index as usize], index.unwrap() ) } + + fn upcast_to_raw(&self) -> &dyn crate::ingredient::RawIngredient { + self + } + + fn upcast_to_raw_mut(&mut self) -> &mut dyn crate::ingredient::RawIngredient { + self + } } impl IngredientRequiresReset for TrackedFieldIngredient