From c84bd1b5c2feb441c0b70ab4dcb9d625131371f4 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 10 Jul 2024 08:13:00 -0400 Subject: [PATCH] WIP new approach with upcasts --- Cargo.toml | 1 + src/accumulator.rs | 52 +++--- src/cycle.rs | 55 +++--- src/database.rs | 103 +++++++++-- src/debug.rs | 264 ---------------------------- src/event.rs | 118 +------------ src/function.rs | 36 ++-- src/function/accumulated.rs | 4 +- src/function/diff_outputs.rs | 4 +- src/function/execute.rs | 13 +- src/function/fetch.rs | 2 +- src/function/maybe_changed_after.rs | 37 ++-- src/function/memo.rs | 2 +- src/function/specify.rs | 12 +- src/ingredient.rs | 84 +++------ src/ingredient/adaptor.rs | 226 ------------------------ src/input.rs | 31 ++-- src/input_field.rs | 33 ++-- src/interned.rs | 31 ++-- src/key.rs | 75 ++++++-- src/lib.rs | 4 +- src/runtime.rs | 14 +- src/storage.rs | 211 +++++++++++----------- src/tracked_struct.rs | 34 ++-- src/tracked_struct/tracked_field.rs | 30 ++-- src/upcast.rs | 189 ++++++++++++++++++++ 26 files changed, 655 insertions(+), 1010 deletions(-) delete mode 100644 src/debug.rs delete mode 100644 src/ingredient/adaptor.rs create mode 100644 src/upcast.rs diff --git a/Cargo.toml b/Cargo.toml index 1f19a3c1b..969753be4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ description = "A generic framework for on-demand, incrementalized computation (e [dependencies] append-only-vec = { git = "https://github.com/nikomatsakis/append-only-vec.git", version = "0.1.4" } arc-swap = "1.6.0" +boomphf = "0.6.0" crossbeam = "0.8.1" dashmap = "5.3.4" hashlink = "0.8.0" diff --git a/src/accumulator.rs b/src/accumulator.rs index cb2cf14f4..6bc4db89e 100644 --- a/src/accumulator.rs +++ b/src/accumulator.rs @@ -1,6 +1,10 @@ //! Basic test of accumulator functionality. -use std::{any::Any, fmt, marker::PhantomData}; +use std::{ + any::Any, + fmt::{self, Debug}, + marker::PhantomData, +}; use crate::{ cycle::CycleRecoveryStrategy, @@ -9,13 +13,13 @@ use crate::{ key::DependencyIndex, runtime::local_state::QueryOrigin, storage::IngredientIndex, - Database, DatabaseKeyIndex, Event, EventKind, Revision, Runtime, + Database, DatabaseKeyIndex, Event, EventKind, Id, Revision, Runtime, }; pub trait Accumulator: Jar { const DEBUG_NAME: &'static str; - type Data: Clone; + type Data: Clone + Debug; } pub struct AccumulatorJar { @@ -31,12 +35,7 @@ impl Default for AccumulatorJar { } impl Jar for AccumulatorJar { - type DbView = dyn crate::Database; - - fn create_ingredients( - &self, - first_index: IngredientIndex, - ) -> Vec>> { + fn create_ingredients(&self, first_index: IngredientIndex) -> Vec> { vec![Box::new(>::new(first_index))] } } @@ -58,8 +57,8 @@ impl AccumulatorIngredient { 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::(); + let index = db.add_or_lookup_jar_by_type(&jar); + let ingredient = db.lookup_ingredient(index).assert_type::(); Some(ingredient) } @@ -129,16 +128,14 @@ impl AccumulatorIngredient { } impl Ingredient for AccumulatorIngredient { - type DbView = dyn crate::Database; - fn ingredient_index(&self) -> IngredientIndex { self.index } fn maybe_changed_after( &self, - _db: &Self::DbView, - _input: DependencyIndex, + _db: &dyn Database, + _input: Option, _revision: Revision, ) -> bool { panic!("nothing should ever depend on an accumulator directly") @@ -154,7 +151,7 @@ impl Ingredient for AccumulatorIngredient { fn mark_validated_output( &self, - db: &Self::DbView, + db: &dyn Database, executor: DatabaseKeyIndex, output_key: Option, ) { @@ -168,7 +165,7 @@ impl Ingredient for AccumulatorIngredient { fn remove_stale_output( &self, - db: &Self::DbView, + db: &dyn Database, executor: DatabaseKeyIndex, stale_output_key: Option, ) { @@ -188,23 +185,26 @@ impl Ingredient for AccumulatorIngredient { panic!("unexpected reset on accumulator") } - fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: crate::Id) { + fn salsa_struct_deleted(&self, _db: &dyn Database, _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(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 { const RESET_ON_NEW_REVISION: bool = false; } + +impl std::fmt::Debug for AccumulatorIngredient +where + A: Accumulator, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(std::any::type_name::()) + .field("index", &self.index) + .finish() + } +} diff --git a/src/cycle.rs b/src/cycle.rs index d3ba93ce6..6ed227aac 100644 --- a/src/cycle.rs +++ b/src/cycle.rs @@ -1,5 +1,4 @@ -use crate::debug::DebugWithDb; -use crate::{key::DatabaseKeyIndex, Database}; +use crate::{database, key::DatabaseKeyIndex, Database}; use std::{panic::AssertUnwindSafe, sync::Arc}; /// Captures the participants of a cycle that occurred when executing a query. @@ -17,7 +16,7 @@ use std::{panic::AssertUnwindSafe, sync::Arc}; /// /// You can read more about cycle handling in /// the [salsa book](https://https://salsa-rs.github.io/salsa/cycles.html). -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Cycle { participants: CycleParticipants, } @@ -59,47 +58,33 @@ impl Cycle { /// Returns a vector with the debug information for /// all the participants in the cycle. - pub fn all_participants(&self, db: &DB) -> Vec { - self.participant_keys() - .map(|d| format!("{:?}", d.debug(db))) - .collect() + pub fn all_participants(&self, _db: &dyn Database) -> Vec { + self.participant_keys().collect() } /// Returns a vector with the debug information for /// those participants in the cycle that lacked recovery /// information. - pub fn unexpected_participants(&self, db: &DB) -> Vec { + pub fn unexpected_participants(&self, db: &dyn Database) -> Vec { self.participant_keys() - .filter(|&d| { - db.cycle_recovery_strategy(d.ingredient_index) == CycleRecoveryStrategy::Panic - }) - .map(|d| format!("{:?}", d.debug(db))) + .filter(|&d| d.cycle_recovery_strategy(db) == CycleRecoveryStrategy::Panic) .collect() } +} - /// Returns a "debug" view onto this strict that can be used to print out information. - pub fn debug<'me, DB: ?Sized + Database>(&'me self, db: &'me DB) -> impl std::fmt::Debug + 'me { - struct UnexpectedCycleDebug<'me> { - c: &'me Cycle, - db: &'me dyn Database, - } - - impl<'me> std::fmt::Debug for UnexpectedCycleDebug<'me> { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fmt.debug_struct("UnexpectedCycle") - .field("all_participants", &self.c.all_participants(self.db)) - .field( - "unexpected_participants", - &self.c.unexpected_participants(self.db), - ) - .finish() - } - } - - UnexpectedCycleDebug { - c: self, - db: db.as_salsa_database(), - } +impl std::fmt::Debug for Cycle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + database::with_attached_database(|db| { + f.debug_struct("UnexpectedCycle") + .field("all_participants", &self.all_participants(db)) + .field("unexpected_participants", &self.unexpected_participants(db)) + .finish() + }) + .unwrap_or_else(|| { + f.debug_struct("Cycle") + .field("participants", &self.participants) + .finish() + }) } } diff --git a/src/database.rs b/src/database.rs index 72d8ba3a0..44868f009 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,12 +1,11 @@ -use std::any::Any; +use std::{any::Any, cell::Cell, ptr::NonNull}; -use crate::{ - ingredient::Ingredient, - storage::{HasJarsDyn, StorageForView}, - DebugWithDb, Durability, Event, -}; +use crossbeam::atomic::AtomicCell; +use parking_lot::Mutex; -pub trait Database: HasJarsDyn + AsSalsaDatabase { +use crate::{storage::DatabaseGen, Durability, Event}; + +pub trait Database: DatabaseGen { /// This function is invoked at key points in the salsa /// runtime. It permits the database to be customized and to /// inject logging or other custom behavior. @@ -14,7 +13,7 @@ pub trait Database: HasJarsDyn + AsSalsaDatabase { /// By default, the event is logged at level debug using /// the standard `log` facade. fn salsa_event(&self, event: Event) { - log::debug!("salsa_event: {:?}", event.debug(self)); + log::debug!("salsa_event: {:?}", event) } /// A "synthetic write" causes the system to act *as though* some @@ -38,10 +37,19 @@ pub trait Database: HasJarsDyn + AsSalsaDatabase { } } +/// The database view trait allows you to define your own views on the database. +/// This lets you add extra context beyond what is stored in the salsa database itself. pub trait DatabaseView: Database { - fn as_dyn(&self) -> &Dyn; - fn as_dyn_mut(&mut self) -> &mut Dyn; - fn storage_for_view(&self) -> &dyn StorageForView; + /// Registers this database view in the database. + /// This is normally invoked automatically by tracked functions that require a given view. + fn add_view_to_db(&self); +} + +impl DatabaseView for Db { + fn add_view_to_db(&self) { + let upcasts = self.upcasts_for_self(); + upcasts.add::(|t| t, |t| t); + } } /// Indicates a database that also supports parallel query @@ -109,9 +117,6 @@ pub trait ParallelDatabase: Database + Send { /// ``` fn snapshot(&self) -> Snapshot; } -pub trait AsSalsaDatabase { - fn as_salsa_database(&self) -> &dyn Database; -} /// Simple wrapper struct that takes ownership of a database `DB` and /// only gives `&self` access to it. See [the `snapshot` method][fm] @@ -148,3 +153,73 @@ where &self.db } } + +thread_local! { + static DATABASE: Cell = Cell::new(AttachedDatabase::null()); +} + +/// Access the "attached" database. Returns `None` if no database is attached. +/// Databases are attached with `attach_database`. +pub fn with_attached_database(op: impl FnOnce(&dyn Database) -> R) -> Option { + // SAFETY: We always attach the database in for the entire duration of a function, + // so it cannot become "unattached" while this function is running. + let db = DATABASE.get(); + Some(op(unsafe { db.ptr?.as_ref() })) +} + +/// Attach database and returns a guard that will un-attach the database when dropped. +/// Has no effect if a database is already attached. +pub fn attach_database(db: &Db, op: impl FnOnce() -> R) -> R { + let _guard = AttachedDb::new(db); + op() +} + +#[derive(Copy, Clone, PartialEq, Eq)] +struct AttachedDatabase { + ptr: Option>, +} + +impl AttachedDatabase { + pub const fn null() -> Self { + Self { ptr: None } + } + + pub fn from(db: &Db) -> Self { + unsafe { + let db: *const dyn Database = db.as_salsa_database(); + Self { + ptr: Some(NonNull::new_unchecked(db as *mut dyn Database)), + } + } + } +} + +unsafe impl Send for AttachedDatabase where dyn Database: Sync {} + +unsafe impl Sync for AttachedDatabase where dyn Database: Sync {} + +struct AttachedDb<'db, Db: ?Sized + Database> { + db: &'db Db, + previous: AttachedDatabase, +} + +impl<'db, Db: ?Sized + Database> AttachedDb<'db, Db> { + pub fn new(db: &'db Db) -> Self { + let previous = DATABASE.replace(AttachedDatabase::from(db)); + AttachedDb { db, previous } + } +} + +impl Drop for AttachedDb<'_, Db> { + fn drop(&mut self) { + DATABASE.set(self.previous); + } +} + +impl std::ops::Deref for AttachedDb<'_, Db> { + type Target = Db; + + fn deref(&self) -> &Db { + &self.db + } +} diff --git a/src/debug.rs b/src/debug.rs deleted file mode 100644 index 547d66823..000000000 --- a/src/debug.rs +++ /dev/null @@ -1,264 +0,0 @@ -use std::{ - collections::{HashMap, HashSet}, - fmt, - rc::Rc, - sync::Arc, -}; - -use crate::database::AsSalsaDatabase; - -/// `DebugWithDb` is a version of the traditional [`Debug`](`std::fmt::Debug`) -/// trait that gives access to the salsa database, allowing tracked -/// structs to print the values of their fields. It is typically not used -/// directly, instead you should write (e.g.) `format!("{:?}", foo.debug(db))`. -/// Implementations are automatically provided for `#[salsa::tracked]` -/// items, though you can opt-out from that if you wish to provide a manual -/// implementation. -/// -/// # WARNING: Intended for debug use only! -/// -/// Debug print-outs of tracked structs include the value of all their fields, -/// but the reads of those fields are ignored by salsa. This avoids creating -/// spurious dependencies from debugging code, but if you use the resulting -/// string to influence the outputs (return value, accumulators, etc) from your -/// query, salsa's dependency tracking will be undermined. -/// -/// If for some reason you *want* to incorporate dependency output into -/// your query, do not use the `debug` or `into_debug` helpers and instead -/// invoke `fmt` manually. -pub trait DebugWithDb { - /// Creates a wrapper type that implements `Debug` but which - /// uses the `DebugWithDb::fmt`. - /// - /// # WARNING: Intended for debug use only! - /// - /// The wrapper type Debug impl will access the value of all - /// fields but those accesses are ignored by salsa. This is only - /// suitable for debug output. See [`DebugWithDb`][] trait comment - /// for more details. - fn debug<'me, 'db>(&'me self, db: &'me Db) -> DebugWith<'me, Db> - where - Self: Sized + 'me, - { - DebugWith { - value: BoxRef::Ref(self), - db, - } - } - - /// Creates a wrapper type that implements `Debug` but which - /// uses the `DebugWithDb::fmt`. - /// - /// # WARNING: Intended for debug use only! - /// - /// The wrapper type Debug impl will access the value of all - /// fields but those accesses are ignored by salsa. This is only - /// suitable for debug output. See [`DebugWithDb`][] trait comment - /// for more details. - fn into_debug<'me, 'db>(self, db: &'me Db) -> DebugWith<'me, Db> - where - Self: Sized + 'me, - { - DebugWith { - value: BoxRef::Box(Box::new(self)), - db, - } - } - - /// Format `self` given the database `db`. - /// - /// # Dependency tracking - /// - /// When invoked manually, field accesses that occur - /// within this method are tracked by salsa. But when invoked - /// the [`DebugWith`][] value returned by the [`debug`](`Self::debug`) - /// and [`into_debug`][`Self::into_debug`] methods, - /// those accesses are ignored. - fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result; -} - -/// Helper type for the [`DebugWithDb`][] trait that -/// wraps a value and implements [`std::fmt::Debug`][], -/// redirecting calls to the `fmt` method from [`DebugWithDb`][]. -/// -/// # WARNING: Intended for debug use only! -/// -/// This type intentionally ignores salsa dependencies used -/// to generate the debug output. See the [`DebugWithDb`][] trait -/// for more notes on this. -pub struct DebugWith<'me, Db: ?Sized + AsSalsaDatabase> { - value: BoxRef<'me, dyn DebugWithDb + 'me>, - db: &'me Db, -} - -enum BoxRef<'me, T: ?Sized> { - Box(Box), - Ref(&'me T), -} - -impl std::ops::Deref for BoxRef<'_, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - match self { - BoxRef::Box(b) => b, - BoxRef::Ref(r) => r, - } - } -} - -impl fmt::Debug for DebugWith<'_, Db> -where - Db: AsSalsaDatabase, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let db = self.db.as_salsa_database(); - db.runtime() - .debug_probe(|| DebugWithDb::fmt(&*self.value, f, self.db)) - } -} - -impl DebugWithDb for &T -where - T: DebugWithDb, - Db: AsSalsaDatabase, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result { - T::fmt(self, f, db) - } -} - -impl DebugWithDb for Box -where - T: DebugWithDb, - Db: AsSalsaDatabase, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result { - T::fmt(self, f, db) - } -} - -impl DebugWithDb for Rc -where - T: DebugWithDb, - Db: AsSalsaDatabase, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result { - T::fmt(self, f, db) - } -} - -impl DebugWithDb for Arc -where - T: DebugWithDb, - Db: AsSalsaDatabase, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result { - T::fmt(self, f, db) - } -} - -impl DebugWithDb for Vec -where - T: DebugWithDb, - Db: AsSalsaDatabase, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result { - let elements = self.iter().map(|e| e.debug(db)); - f.debug_list().entries(elements).finish() - } -} - -impl DebugWithDb for Option -where - T: DebugWithDb, - Db: AsSalsaDatabase, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result { - let me = self.as_ref().map(|v| v.debug(db)); - fmt::Debug::fmt(&me, f) - } -} - -impl DebugWithDb for HashMap -where - K: DebugWithDb, - V: DebugWithDb, - Db: AsSalsaDatabase, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result { - let elements = self.iter().map(|(k, v)| (k.debug(db), v.debug(db))); - f.debug_map().entries(elements).finish() - } -} - -impl DebugWithDb for (A, B) -where - A: DebugWithDb, - B: DebugWithDb, - Db: AsSalsaDatabase, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result { - f.debug_tuple("") - .field(&self.0.debug(db)) - .field(&self.1.debug(db)) - .finish() - } -} - -impl DebugWithDb for (A, B, C) -where - A: DebugWithDb, - B: DebugWithDb, - C: DebugWithDb, - Db: AsSalsaDatabase, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result { - f.debug_tuple("") - .field(&self.0.debug(db)) - .field(&self.1.debug(db)) - .field(&self.2.debug(db)) - .finish() - } -} - -impl DebugWithDb for HashSet -where - V: DebugWithDb, - Db: AsSalsaDatabase, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result { - let elements = self.iter().map(|e| e.debug(db)); - f.debug_list().entries(elements).finish() - } -} - -/// This is used by the macro generated code. -/// If the field type implements `DebugWithDb`, uses that, otherwise, uses `Debug`. -/// That's the "has impl" trick (https://github.com/nvzqz/impls#how-it-works) -#[doc(hidden)] -pub mod helper { - use super::{AsSalsaDatabase, DebugWith, DebugWithDb}; - use std::{fmt, marker::PhantomData}; - - pub trait Fallback { - fn salsa_debug<'a>(a: &'a T, _db: &Db) -> &'a dyn fmt::Debug { - a - } - } - - impl Fallback for Everything {} - - pub struct SalsaDebug(PhantomData, PhantomData); - - impl SalsaDebug - where - T: DebugWithDb, - Db: AsSalsaDatabase, - { - #[allow(dead_code)] - pub fn salsa_debug<'a>(a: &'a T, db: &'a Db) -> DebugWith<'a, Db> { - a.debug(db) - } - } -} diff --git a/src/event.rs b/src/event.rs index fdbef7155..432a284bf 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,11 +1,9 @@ -use crate::{ - debug::DebugWithDb, key::DatabaseKeyIndex, key::DependencyIndex, runtime::RuntimeId, Database, -}; -use std::fmt; +use crate::{key::DatabaseKeyIndex, key::DependencyIndex, runtime::RuntimeId}; /// The `Event` struct identifies various notable things that can /// occur during salsa execution. Instances of this struct are given /// to `salsa_event`. +#[derive(Debug)] pub struct Event { /// The id of the snapshot that triggered the event. Usually /// 1-to-1 with a thread, as well. @@ -15,28 +13,8 @@ pub struct Event { pub kind: EventKind, } -impl fmt::Debug for Event { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("Event") - .field("runtime_id", &self.runtime_id) - .field("kind", &self.kind) - .finish() - } -} - -impl DebugWithDb for Event -where - Db: ?Sized + Database, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result { - f.debug_struct("Event") - .field("runtime_id", &self.runtime_id) - .field("kind", &self.kind.debug(db)) - .finish() - } -} - /// An enum identifying the various kinds of events that can occur. +#[derive(Debug)] pub enum EventKind { /// Occurs when we found that all inputs to a memoized value are /// up-to-date and hence the value can be re-used without @@ -101,93 +79,3 @@ pub enum EventKind { accumulator: DependencyIndex, }, } - -impl fmt::Debug for EventKind { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - EventKind::DidValidateMemoizedValue { database_key } => fmt - .debug_struct("DidValidateMemoizedValue") - .field("database_key", database_key) - .finish(), - EventKind::WillBlockOn { - other_runtime_id, - database_key, - } => fmt - .debug_struct("WillBlockOn") - .field("other_runtime_id", other_runtime_id) - .field("database_key", database_key) - .finish(), - EventKind::WillExecute { database_key } => fmt - .debug_struct("WillExecute") - .field("database_key", database_key) - .finish(), - EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(), - EventKind::WillDiscardStaleOutput { - execute_key, - output_key, - } => fmt - .debug_struct("WillDiscardStaleOutput") - .field("execute_key", &execute_key) - .field("output_key", &output_key) - .finish(), - EventKind::DidDiscard { key } => { - fmt.debug_struct("DidDiscard").field("key", &key).finish() - } - EventKind::DidDiscardAccumulated { - executor_key, - accumulator, - } => fmt - .debug_struct("DidDiscardAccumulated") - .field("executor_key", executor_key) - .field("accumulator", accumulator) - .finish(), - } - } -} - -impl DebugWithDb for EventKind -where - Db: ?Sized + Database, -{ - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result { - match self { - EventKind::DidValidateMemoizedValue { database_key } => fmt - .debug_struct("DidValidateMemoizedValue") - .field("database_key", &database_key.debug(db)) - .finish(), - EventKind::WillBlockOn { - other_runtime_id, - database_key, - } => fmt - .debug_struct("WillBlockOn") - .field("other_runtime_id", other_runtime_id) - .field("database_key", &database_key.debug(db)) - .finish(), - EventKind::WillExecute { database_key } => fmt - .debug_struct("WillExecute") - .field("database_key", &database_key.debug(db)) - .finish(), - EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(), - EventKind::WillDiscardStaleOutput { - execute_key, - output_key, - } => fmt - .debug_struct("WillDiscardStaleOutput") - .field("execute_key", &execute_key.debug(db)) - .field("output_key", &output_key.debug(db)) - .finish(), - EventKind::DidDiscard { key } => fmt - .debug_struct("DidDiscard") - .field("key", &key.debug(db)) - .finish(), - EventKind::DidDiscardAccumulated { - executor_key, - accumulator, - } => fmt - .debug_struct("DidDiscardAccumulated") - .field("executor_key", &executor_key.debug(db)) - .field("accumulator", &accumulator.debug(db)) - .finish(), - } - } -} diff --git a/src/function.rs b/src/function.rs index 330c1e7d6..60e890ca0 100644 --- a/src/function.rs +++ b/src/function.rs @@ -8,7 +8,7 @@ use crate::{ key::{DatabaseKeyIndex, DependencyIndex}, runtime::local_state::QueryOrigin, salsa_struct::SalsaStructInDb, - storage::{HasJarsDyn, IngredientIndex}, + storage::IngredientIndex, Cycle, Database, Event, EventKind, Id, Revision, }; @@ -29,6 +29,7 @@ mod memo; mod specify; mod store; mod sync; + pub trait Configuration: 'static { const DEBUG_NAME: &'static str; @@ -202,19 +203,18 @@ impl Ingredient for FunctionIngredient where C: Configuration, { - type DbView = C::DbView; - fn ingredient_index(&self) -> IngredientIndex { self.index } fn maybe_changed_after( &self, - db: &C::DbView, - input: DependencyIndex, + db: &dyn Database, + input: Option, revision: Revision, ) -> bool { - let key = input.key_index.unwrap(); + let key = input.unwrap(); + let db = db.as_view::(); self.maybe_changed_after(db, key, revision) } @@ -228,7 +228,7 @@ where fn mark_validated_output( &self, - db: &C::DbView, + db: &dyn Database, executor: DatabaseKeyIndex, output_key: Option, ) { @@ -238,7 +238,7 @@ where fn remove_stale_output( &self, - _db: &C::DbView, + _db: &dyn Database, _executor: DatabaseKeyIndex, _stale_output_key: Option, ) { @@ -251,7 +251,7 @@ where std::mem::take(&mut self.deleted_entries); } - fn salsa_struct_deleted(&self, db: &C::DbView, id: Id) { + fn salsa_struct_deleted(&self, db: &dyn Database, id: Id) { // Remove any data keyed by `id`, since `id` no longer // exists in this revision. @@ -265,7 +265,8 @@ where // Anything that was output by this memoized execution // is now itself stale. for stale_output in origin.outputs() { - db.remove_stale_output(key, stale_output) + db.lookup_ingredient(stale_output.ingredient_index) + .remove_stale_output(db, key, stale_output.key_index); } } } @@ -273,13 +274,16 @@ 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 std::fmt::Debug for FunctionIngredient +where + C: Configuration, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(std::any::type_name::()) + .field("index", &self.index) + .finish() } } diff --git a/src/function/accumulated.rs b/src/function/accumulated.rs index b8d848a0d..0e7474288 100644 --- a/src/function/accumulated.rs +++ b/src/function/accumulated.rs @@ -1,6 +1,6 @@ use crate::{ accumulator::AccumulatorIngredient, hash::FxHashSet, runtime::local_state::QueryOrigin, - storage::HasJarsDyn, DatabaseKeyIndex, Id, + storage::DatabaseGen, DatabaseKeyIndex, Id, }; use super::{Configuration, FunctionIngredient}; @@ -31,7 +31,7 @@ where let mut stack = Stack::new(self.database_key_index(key)); while let Some(input) = stack.pop() { accumulator_ingredient.produced_by(runtime, input, &mut result); - stack.extend(db.origin(input)); + stack.extend(input.origin(db.as_salsa_database())); } result } diff --git a/src/function/diff_outputs.rs b/src/function/diff_outputs.rs index 720e0a6c7..ced2dc1ca 100644 --- a/src/function/diff_outputs.rs +++ b/src/function/diff_outputs.rs @@ -1,6 +1,6 @@ use crate::{ hash::FxHashSet, key::DependencyIndex, runtime::local_state::QueryRevisions, - storage::HasJarsDyn, Database, DatabaseKeyIndex, Event, EventKind, + storage::DatabaseGen, Database, DatabaseKeyIndex, Event, EventKind, }; use super::{memo::Memo, Configuration, FunctionIngredient}; @@ -47,6 +47,6 @@ where }, }); - db.remove_stale_output(key, output); + key.remove_stale_output(db.as_salsa_database(), output.try_into().unwrap()); } } diff --git a/src/function/execute.rs b/src/function/execute.rs index d77b2e61a..60ab4918e 100644 --- a/src/function/execute.rs +++ b/src/function/execute.rs @@ -1,9 +1,8 @@ use std::sync::Arc; use crate::{ - debug::DebugWithDb, runtime::{local_state::ActiveQueryGuard, StampedValue}, - storage::HasJarsDyn, + storage::DatabaseGen, Cycle, Database, Event, EventKind, }; @@ -49,9 +48,7 @@ where Ok(v) => v, Err(cycle) => { log::debug!( - "{:?}: caught cycle {:?}, have strategy {:?}", - database_key_index.debug(db), - cycle, + "{database_key_index:?}: caught cycle {cycle:?}, have strategy {:?}", C::CYCLE_STRATEGY ); match C::CYCLE_STRATEGY { @@ -101,11 +98,7 @@ where let stamped_value = revisions.stamped_value(value); - log::debug!( - "{:?}: read_upgrade: result.revisions = {:#?}", - database_key_index.debug(db), - revisions - ); + log::debug!("{database_key_index:?}: read_upgrade: result.revisions = {revisions:#?}"); stamped_value } diff --git a/src/function/fetch.rs b/src/function/fetch.rs index 39e331a51..a41afc78b 100644 --- a/src/function/fetch.rs +++ b/src/function/fetch.rs @@ -1,6 +1,6 @@ use arc_swap::Guard; -use crate::{database::AsSalsaDatabase, runtime::StampedValue, storage::HasJarsDyn, Id}; +use crate::{runtime::StampedValue, storage::DatabaseGen, Id}; use super::{Configuration, FunctionIngredient}; diff --git a/src/function/maybe_changed_after.rs b/src/function/maybe_changed_after.rs index def4a939b..916a57347 100644 --- a/src/function/maybe_changed_after.rs +++ b/src/function/maybe_changed_after.rs @@ -1,14 +1,12 @@ use arc_swap::Guard; use crate::{ - database::AsSalsaDatabase, - debug::DebugWithDb, key::DatabaseKeyIndex, runtime::{ local_state::{ActiveQueryGuard, EdgeKind, QueryOrigin}, StampedValue, }, - storage::HasJarsDyn, + storage::DatabaseGen, Id, Revision, Runtime, }; @@ -30,11 +28,7 @@ where loop { let database_key_index = self.database_key_index(key); - log::debug!( - "{:?}: maybe_changed_after(revision = {:?})", - database_key_index.debug(db), - revision, - ); + log::debug!("{database_key_index:?}: maybe_changed_after(revision = {revision:?})"); // Check if we have a verified version: this is the hot path. let memo_guard = self.memo_map.get(key); @@ -77,10 +71,8 @@ where }; log::debug!( - "{:?}: maybe_changed_after_cold, successful claim, revision = {:?}, old_memo = {:#?}", - database_key_index.debug(db), - revision, - old_memo + "{database_key_index:?}: maybe_changed_after_cold, successful claim, \ + revision = {revision:?}, old_memo = {old_memo:#?}", ); // Check if the inputs are still valid and we can just compare `changed_at`. @@ -114,11 +106,7 @@ where let verified_at = memo.verified_at.load(); let revision_now = runtime.current_revision(); - log::debug!( - "{:?}: shallow_verify_memo(memo = {:#?})", - database_key_index.debug(db), - memo, - ); + log::debug!("{database_key_index:?}: shallow_verify_memo(memo = {memo:#?})",); if verified_at == revision_now { // Already verified. @@ -153,11 +141,7 @@ where let runtime = db.runtime(); let database_key_index = active_query.database_key_index; - log::debug!( - "{:?}: deep_verify_memo(old_memo = {:#?})", - database_key_index.debug(db), - old_memo - ); + log::debug!("{database_key_index:?}: deep_verify_memo(old_memo = {old_memo:#?})",); if self.shallow_verify_memo(db, runtime, database_key_index, old_memo) { return true; @@ -197,7 +181,9 @@ where for &(edge_kind, dependency_index) in edges.input_outputs.iter() { match edge_kind { EdgeKind::Input => { - if db.maybe_changed_after(dependency_index, last_verified_at) { + if dependency_index + .maybe_changed_after(db.as_salsa_database(), last_verified_at) + { return false; } } @@ -218,7 +204,10 @@ where // by this function cannot be read until this function is marked green, // so even if we mark them as valid here, the function will re-execute // and overwrite the contents. - db.mark_validated_output(database_key_index, dependency_index); + database_key_index.mark_validated_output( + db.as_salsa_database(), + dependency_index.try_into().unwrap(), + ); } } } diff --git a/src/function/memo.rs b/src/function/memo.rs index 072cfdb0f..5fc5b3b89 100644 --- a/src/function/memo.rs +++ b/src/function/memo.rs @@ -165,7 +165,7 @@ impl Memo { database_key_index: DatabaseKeyIndex, ) { for output in self.revisions.origin.outputs() { - db.mark_validated_output(database_key_index, output); + output.mark_validated_output(db, database_key_index); } } } diff --git a/src/function/specify.rs b/src/function/specify.rs index 8f2bad95b..3b70563a0 100644 --- a/src/function/specify.rs +++ b/src/function/specify.rs @@ -1,11 +1,10 @@ use crossbeam::atomic::AtomicCell; use crate::{ - database::AsSalsaDatabase, runtime::local_state::{QueryOrigin, QueryRevisions}, - storage::HasJarsDyn, + storage::DatabaseGen, tracked_struct::TrackedStructInDb, - DatabaseKeyIndex, DebugWithDb, Id, + Database, DatabaseKeyIndex, Id, }; use super::{memo::Memo, Configuration, FunctionIngredient}; @@ -111,9 +110,9 @@ where /// and `key` is a value that was specified by `executor`. /// Marks `key` as valid in the current revision since if `executor` had re-executed, /// it would have specified `key` again. - pub(super) fn validate_specified_value( + pub(super) fn validate_specified_value( &self, - db: &C::DbView, + db: &Db, executor: DatabaseKeyIndex, key: Id, ) { @@ -130,8 +129,7 @@ where QueryOrigin::Assigned(by_query) => assert_eq!(by_query, executor), _ => panic!( "expected a query assigned by `{:?}`, not `{:?}`", - executor.debug(db), - memo.revisions.origin, + executor, memo.revisions.origin, ), } diff --git a/src/ingredient.rs b/src/ingredient.rs index 825cc8098..46fa381a2 100644 --- a/src/ingredient.rs +++ b/src/ingredient.rs @@ -5,59 +5,25 @@ use std::{ use crate::{ cycle::CycleRecoveryStrategy, key::DependencyIndex, runtime::local_state::QueryOrigin, - storage::IngredientIndex, DatabaseKeyIndex, Id, + storage::IngredientIndex, Database, 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>>; + 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 -/// ingredients to the jar struct that together are used to create the salsa concept. -/// For example, a tracked struct defines a [`crate::interned::InternedIngredient`] to store -/// its identity plus [`crate::function::FunctionIngredient`] values to store its fields. -/// The exact ingredients are determined by -/// [`IngredientsFor`](`crate::storage::IngredientsFor`) implementations generated by the -/// macro. -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; - - /// If this ingredient is a participant in a cycle, what is its cycle recovery strategy? - /// (Really only relevant to [`crate::function::FunctionIngredient`], - /// since only function ingredients push themselves onto the active query stack.) - fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy; - +pub trait Ingredient: Any + std::fmt::Debug { /// Has the value for `input` in this ingredient changed after `revision`? fn maybe_changed_after<'db>( &'db self, - db: &'db Self::DbView, - input: DependencyIndex, + db: &'db dyn Database, + input: Option, revision: Revision, ) -> bool; @@ -69,7 +35,7 @@ pub trait Ingredient: RawIngredient { /// in the current revision. fn mark_validated_output<'db>( &'db self, - db: &'db Self::DbView, + db: &'db dyn Database, executor: DatabaseKeyIndex, output_key: Option, ); @@ -80,7 +46,7 @@ pub trait Ingredient: RawIngredient { /// This hook is used to clear out the stale value so others cannot read it. fn remove_stale_output( &self, - db: &Self::DbView, + db: &dyn Database, executor: DatabaseKeyIndex, stale_output_key: Option, ); @@ -89,7 +55,15 @@ pub trait Ingredient: RawIngredient { /// 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: &Self::DbView, id: Id); + fn salsa_struct_deleted(&self, db: &dyn Database, id: Id); + + /// Returns the [`IngredientIndex`] of this ingredient. + fn ingredient_index(&self) -> IngredientIndex; + + /// If this ingredient is a participant in a cycle, what is its cycle recovery strategy? + /// (Really only relevant to [`crate::function::FunctionIngredient`], + /// since only function ingredients push themselves onto the active query stack.) + fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy; /// Invoked when a new revision is about to start. /// This moment is important because it means that we have an `&mut`-reference to the @@ -106,24 +80,22 @@ pub trait Ingredient: RawIngredient { 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 { +impl dyn Ingredient { + /// Equivalent to the `downcast` methods on `any`. + /// Because we do not have dyn-upcasting support, we need this workaround. + pub fn assert_type(&self) -> &T { assert_eq!( self.type_id(), - TypeId::of::(), - "expecetd a value of type `{}` but type check failed", - std::any::type_name::(), + TypeId::of::(), + "ingredient `{self:?}` is not of type `{}`", + 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 + // SAFETY: We know that the underlying data pointer + // refers to a value of type T because of the `TypeId` check above. + let this: *const dyn Ingredient = self; + let this = this as *const T; // discards the vtable + unsafe { &*this } } } diff --git a/src/ingredient/adaptor.rs b/src/ingredient/adaptor.rs deleted file mode 100644 index 464f644aa..000000000 --- a/src/ingredient/adaptor.rs +++ /dev/null @@ -1,226 +0,0 @@ -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/input.rs b/src/input.rs index 0d8ae6444..162f4b04d 100644 --- a/src/input.rs +++ b/src/input.rs @@ -70,16 +70,14 @@ 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: &Self::DbView, - _input: DependencyIndex, + _db: &dyn Database, + _input: Option, _revision: Revision, ) -> bool { // Input ingredients are just a counter, they store no data, they are immortal. @@ -97,7 +95,7 @@ where fn mark_validated_output( &self, - _db: &Self::DbView, + _db: &dyn Database, executor: DatabaseKeyIndex, output_key: Option, ) { @@ -109,7 +107,7 @@ where fn remove_stale_output( &self, - _db: &Self::DbView, + _db: &dyn Database, executor: DatabaseKeyIndex, stale_output_key: Option, ) { @@ -123,7 +121,7 @@ where panic!("unexpected call to `reset_for_new_revision`") } - fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: crate::Id) { + fn salsa_struct_deleted(&self, _db: &dyn Database, _id: crate::Id) { panic!( "unexpected call: input ingredients do not register for salsa struct deletion events" ); @@ -132,14 +130,6 @@ 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 @@ -148,3 +138,14 @@ where { const RESET_ON_NEW_REVISION: bool = false; } + +impl std::fmt::Debug for InputIngredient +where + Id: InputId, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(std::any::type_name::()) + .field("index", &self.ingredient_index) + .finish() + } +} diff --git a/src/input_field.rs b/src/input_field.rs index 667340c30..445540340 100644 --- a/src/input_field.rs +++ b/src/input_field.rs @@ -113,8 +113,6 @@ where K: FromId + 'static, F: 'static, { - type DbView = dyn Database; - fn ingredient_index(&self) -> IngredientIndex { self.index } @@ -125,11 +123,11 @@ where fn maybe_changed_after( &self, - _db: &Self::DbView, - input: DependencyIndex, + _db: &dyn Database, + input: Option, revision: Revision, ) -> bool { - let key = K::from_id(input.key_index.unwrap()); + let key = K::from_id(input.unwrap()); self.map.get(&key).unwrap().changed_at > revision } @@ -139,7 +137,7 @@ where fn mark_validated_output( &self, - _db: &Self::DbView, + _db: &dyn Database, _executor: DatabaseKeyIndex, _output_key: Option, ) { @@ -147,13 +145,13 @@ where fn remove_stale_output( &self, - _db: &Self::DbView, + _db: &dyn Database, _executor: DatabaseKeyIndex, _stale_output_key: Option, ) { } - fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: Id) { + fn salsa_struct_deleted(&self, _db: &dyn Database, _id: Id) { panic!("unexpected call: input fields are never deleted"); } @@ -164,14 +162,6 @@ 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 @@ -180,3 +170,14 @@ where { const RESET_ON_NEW_REVISION: bool = false; } + +impl std::fmt::Debug for InputFieldIngredient +where + K: AsId, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(std::any::type_name::()) + .field("index", &self.index) + .finish() + } +} diff --git a/src/interned.rs b/src/interned.rs index c993fe2a4..04760c1e4 100644 --- a/src/interned.rs +++ b/src/interned.rs @@ -184,16 +184,14 @@ 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: &Self::DbView, - _input: DependencyIndex, + _db: &dyn Database, + _input: Option, revision: Revision, ) -> bool { revision < self.reset_at @@ -209,7 +207,7 @@ where fn mark_validated_output( &self, - _db: &Self::DbView, + _db: &dyn Database, executor: DatabaseKeyIndex, output_key: Option, ) { @@ -221,7 +219,7 @@ where fn remove_stale_output( &self, - _db: &Self::DbView, + _db: &dyn Database, executor: DatabaseKeyIndex, stale_output_key: Option, ) { @@ -238,21 +236,13 @@ where panic!("unexpected call to `reset_for_new_revision`") } - fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: crate::Id) { + fn salsa_struct_deleted(&self, _db: &dyn Database, _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 @@ -262,6 +252,17 @@ where const RESET_ON_NEW_REVISION: bool = false; } +impl std::fmt::Debug for InternedIngredient +where + C: Configuration, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(std::any::type_name::()) + .field("index", &self.ingredient_index) + .finish() + } +} + pub struct IdentityInterner where C: Configuration, diff --git a/src/key.rs b/src/key.rs index c3fb9e0d1..0f1f53511 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,12 +1,13 @@ -use std::fmt::Debug; - -use crate::{storage::IngredientIndex, Database, DebugWithDb, Id}; +use crate::{ + cycle::CycleRecoveryStrategy, database, runtime::local_state::QueryOrigin, + storage::IngredientIndex, Database, Id, +}; /// An integer that uniquely identifies a particular query instance within the /// database. Used to track dependencies between queries. Fully ordered and /// equatable but those orderings are arbitrary, and meant to be used only for /// inserting into maps and the like. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DependencyIndex { pub(crate) ingredient_index: IngredientIndex, pub(crate) key_index: Option, @@ -32,14 +33,38 @@ impl DependencyIndex { pub fn key_index(self) -> Option { self.key_index } + + pub(crate) fn mark_validated_output( + &self, + db: &dyn Database, + database_key_index: DatabaseKeyIndex, + ) { + db.lookup_ingredient(self.ingredient_index) + .mark_validated_output(db, database_key_index, self.key_index) + } + + pub(crate) fn maybe_changed_after( + &self, + db: &dyn Database, + last_verified_at: crate::Revision, + ) -> bool { + db.lookup_ingredient(self.ingredient_index) + .maybe_changed_after(db, self.key_index, last_verified_at) + } } -impl crate::debug::DebugWithDb for DependencyIndex -where - Db: ?Sized + Database, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result { - db.fmt_index(*self, f) +impl std::fmt::Debug for DependencyIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + database::with_attached_database(|db| { + let ingredient = db.lookup_ingredient(self.ingredient_index); + ingredient.fmt_index(self.key_index, f) + }) + .unwrap_or_else(|| { + f.debug_tuple("DependencyIndex") + .field(&self.ingredient_index) + .field(&self.key_index) + .finish() + }) } } @@ -47,7 +72,7 @@ where /// An "active" database key index represents a database key index /// that is actively executing. In that case, the `key_index` cannot be /// None. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DatabaseKeyIndex { pub(crate) ingredient_index: IngredientIndex, pub(crate) key_index: Id, @@ -62,15 +87,31 @@ impl DatabaseKeyIndex { pub fn key_index(self) -> Id { self.key_index } + + pub(crate) fn cycle_recovery_strategy(&self, db: &dyn Database) -> CycleRecoveryStrategy { + self.ingredient_index.cycle_recovery_strategy(db) + } + + pub(crate) fn mark_validated_output(&self, db: &dyn Database, executor: DatabaseKeyIndex) { + db.lookup_ingredient(self.ingredient_index) + .mark_validated_output(db, executor, Some(self.key_index)) + } + + pub(crate) fn remove_stale_output(&self, db: &dyn Database, executor: DatabaseKeyIndex) { + db.lookup_ingredient(self.ingredient_index) + .remove_stale_output(db, executor, Some(self.key_index)) + } + + pub(crate) fn origin(&self, db: &dyn Database) -> Option { + db.lookup_ingredient(self.ingredient_index) + .origin(self.key_index) + } } -impl crate::debug::DebugWithDb for DatabaseKeyIndex -where - Db: ?Sized + Database, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result { +impl std::fmt::Debug for DatabaseKeyIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let i: DependencyIndex = (*self).into(); - DebugWithDb::fmt(&i, f, db) + std::fmt::Debug::fmt(&i, f) } } diff --git a/src/lib.rs b/src/lib.rs index 0a9781cf7..b264499b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ mod alloc; pub mod cancelled; pub mod cycle; pub mod database; -pub mod debug; pub mod durability; pub mod event; pub mod function; @@ -23,6 +22,7 @@ pub mod salsa_struct; pub mod setter; pub mod storage; pub mod tracked_struct; +mod upcast; pub mod update; pub use self::cancelled::Cancelled; @@ -31,8 +31,6 @@ 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; -pub use self::debug::DebugWithDb; pub use self::durability::Durability; pub use self::event::Event; pub use self::event::EventKind; diff --git a/src/runtime.rs b/src/runtime.rs index a61d5b3b0..e6ee068d4 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -5,7 +5,6 @@ use std::{ use crate::{ cycle::CycleRecoveryStrategy, - debug::DebugWithDb, durability::Durability, key::{DatabaseKeyIndex, DependencyIndex}, runtime::active_query::ActiveQuery, @@ -395,11 +394,7 @@ impl Runtime { Cycle::new(Arc::new(v)) }; - log::debug!( - "cycle {:?}, cycle_query {:#?}", - cycle.debug(db), - cycle_query, - ); + log::debug!("cycle {cycle:?}, cycle_query {cycle_query:#?}"); // We can remove the cycle participants from the list of dependencies; // they are a strongly connected component (SCC) and we only care about @@ -413,13 +408,16 @@ impl Runtime { dg.for_each_cycle_participant(from_id, &mut from_stack, database_key_index, to_id, |aqs| { aqs.iter_mut() .skip_while(|aq| { - match db.cycle_recovery_strategy(aq.database_key_index.ingredient_index) { + match db + .lookup_ingredient(aq.database_key_index.ingredient_index) + .cycle_recovery_strategy() + { CycleRecoveryStrategy::Panic => true, CycleRecoveryStrategy::Fallback => false, } }) .for_each(|aq| { - log::debug!("marking {:?} for fallback", aq.database_key_index.debug(db)); + log::debug!("marking {:?} for fallback", aq.database_key_index); aq.take_inputs_from(&cycle_query); assert!(aq.cycle.is_none()); aq.cycle = Some(cycle.clone()); diff --git a/src/storage.rs b/src/storage.rs index 85017e354..d05ba863a 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,25 +1,93 @@ use std::any::{Any, TypeId}; -use std::ptr::NonNull; -use std::{fmt, sync::Arc}; +use std::sync::Arc; 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::adaptor::AdaptedIngredient; -use crate::ingredient::{Ingredient, Jar, RawIngredient}; -use crate::key::DependencyIndex; +use crate::ingredient::{Ingredient, Jar}; use crate::nonce::{Nonce, NonceGenerator}; -use crate::runtime::local_state::QueryOrigin; use crate::runtime::Runtime; -use crate::{Database, DatabaseKeyIndex, DatabaseView, Id}; +use crate::upcast::{DynUpcasts, DynUpcastsFor}; +use crate::Database; -use super::{ParallelDatabase, Revision}; +use super::ParallelDatabase; +/// Salsa database methods that are generated by the `#[salsa::database]` procedural macro. +/// +/// # Safety +/// +/// This trait is meant to be implemented by our procedural macro. +/// We need to document any non-obvious conditions that it satisfies. +pub unsafe trait DatabaseGen: Any + Send + Sync { + fn as_salsa_database(&self) -> &dyn Database; + + /// Returns a reference to the underlying "dyn-upcasts" + fn upcasts(&self) -> &DynUpcasts; + + /// Upcast to a `dyn DatabaseGen`. + /// + /// Only required because upcasts not yet stabilized (*grr*). + /// + /// # Ensures + /// + /// Returns the same data pointer as `self`. + fn upcast_to_dyn_database_gen(&self) -> &dyn DatabaseGen; + + /// Returns the upcasts database, tied to the type of `Self`; cannot be used from `dyn DatabaseGen` objects. + fn upcasts_for_self(&self) -> &DynUpcastsFor + where + Self: Sized + Database; + + /// Returns the nonce for the underyling storage. + /// This nonce is guaranteed to be unique for the database and never to be reused. + 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 Ingredient; + + /// Gets the salsa runtime + fn runtime(&self) -> &Runtime; + + /// Gets the salsa runtime + fn runtime_mut(&mut self) -> &mut Runtime; +} + +impl dyn Database { + /// Upcasts `self` to the given view. + /// + /// # Panics + /// + /// If the view has not been added to the database (see [`DatabaseView`][]) + pub fn as_view(&self) -> &DbView { + self.upcasts().try_upcast(self).unwrap() + } + + /// Upcasts `self` to the given view. + /// + /// # Panics + /// + /// If the view has not been added to the database (see [`DatabaseView`][]) + pub fn as_view_mut(&mut self) -> &mut DbView { + // Avoid a borrow check error by cloning. This is the "uncommon" path so it seems fine. + let upcasts = self.upcasts().clone(); + upcasts.try_upcast_mut(self).unwrap() + } +} + +/// Nonce type representing the underlying database storage. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct StorageNonce; + +/// Generator for storage nonces. static NONCE: NonceGenerator = NonceGenerator::new(); /// An ingredient index identifies a particular [`Ingredient`] in the database. @@ -35,14 +103,14 @@ impl IngredientIndex { 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 } + + pub(crate) fn cycle_recovery_strategy(self, db: &dyn Database) -> CycleRecoveryStrategy { + db.lookup_ingredient(self).cycle_recovery_strategy() + } } impl std::ops::Add for IngredientIndex { @@ -70,6 +138,8 @@ pub struct Storage { /// This is where the actual data for tracked functions, structs, inputs, etc lives, /// along with some coordination variables between treads. struct Shared { + upcasts: DynUpcastsFor, + nonce: Nonce, /// Map from the type-id of an `impl Jar` to the index of its first ingredient. @@ -83,7 +153,7 @@ struct Shared { /// Vector of ingredients. /// /// Immutable unless the mutex on `ingredients_map` is held. - ingredients_vec: Arc>>, + 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. @@ -102,6 +172,7 @@ impl Default for Storage { fn default() -> Self { Self { shared: Shared { + upcasts: Default::default(), nonce: NONCE.nonce(), cvar: Arc::new(Default::default()), noti_lock: Arc::new(parking_lot::Mutex::new(())), @@ -116,16 +187,18 @@ impl Default for Storage { // ANCHOR_END: default impl Storage { + /// Add an upcast function to type `T`. + pub fn add_upcast( + &mut self, + func: fn(&Db) -> &T, + func_mut: fn(&mut Db) -> &mut T, + ) { + self.shared.upcasts.add::(func, func_mut) + } + /// 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: DatabaseView, - DbView: ?Sized + Any, - { + fn add_or_lookup_adapted_jar_by_type(&self, jar: &dyn Jar) -> IngredientIndex { let jar_type_id = jar.type_id(); let mut jar_map = self.shared.jar_map.lock(); *jar_map @@ -138,7 +211,7 @@ impl Storage { let actual_index = self .shared .ingredients_vec - .push(AdaptedIngredient::new(ingredient)); + .push(ingredient); assert_eq!( expected_index.as_usize(), actual_index, @@ -156,8 +229,8 @@ impl Storage { 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 lookup_ingredient(&self, index: IngredientIndex) -> &dyn Ingredient { + &*self.shared.ingredients_vec[index.as_usize()] } pub fn snapshot(&self) -> Storage @@ -211,6 +284,7 @@ impl Storage { impl Clone for Shared { fn clone(&self) -> Self { Self { + upcasts: self.upcasts.clone(), nonce: self.nonce.clone(), jar_map: self.jar_map.clone(), ingredients_vec: self.ingredients_vec.clone(), @@ -236,99 +310,24 @@ impl Drop for Storage { } } -// ANCHOR: HasJarsDyn -/// Dyn friendly subset of HasJars -pub trait HasJarsDyn: 'static { - fn runtime(&self) -> &Runtime; - - 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; - - fn origin(&self, input: DatabaseKeyIndex) -> Option; - - fn mark_validated_output(&self, executor: DatabaseKeyIndex, output: DependencyIndex); - - /// Invoked when `executor` used to output `stale_output` but no longer does. - /// This method routes that into a call to the [`remove_stale_output`](`crate::ingredient::Ingredient::remove_stale_output`) - /// method on the ingredient for `stale_output`. - fn remove_stale_output(&self, executor: DatabaseKeyIndex, stale_output: DependencyIndex); - - /// Informs `ingredient` that the salsa struct with id `id` has been deleted. - /// This means that `id` will not be used in this revision and hence - /// any memoized values keyed by that struct can be discarded. - /// - /// In order to receive this callback, `ingredient` must have registered itself - /// as a dependent function using - /// [`SalsaStructInDb::register_dependent_fn`](`crate::salsa_struct::SalsaStructInDb::register_dependent_fn`). - fn salsa_struct_deleted(&self, ingredient: IngredientIndex, id: Id); - - fn fmt_index(&self, index: DependencyIndex, fmt: &mut fmt::Formatter<'_>) -> fmt::Result; -} -// ANCHOR_END: HasJarsDyn - -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 - Db: DatabaseView, - DbView: ?Sized + Any, -{ - 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) - } -} - /// Caches a pointer to an ingredient in a database. /// Optimized for the case of a single database. -pub struct IngredientCache +pub struct IngredientCache where - I: Ingredient, - DbView: ?Sized, + I: Ingredient, { cached_data: std::sync::OnceLock<(Nonce, *const I)>, } -impl IngredientCache +impl IngredientCache where - I: Ingredient, - DbView: ?Sized, + I: Ingredient, { /// 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, + storage: &'s dyn Database, create_index: impl Fn() -> IngredientIndex, ) -> &'s I { let &(nonce, ingredient) = self.cached_data.get_or_init(|| { @@ -345,7 +344,7 @@ where fn create_ingredient<'s>( &self, - storage: &'s dyn StorageForView, + storage: &'s dyn Database, create_index: &impl Fn() -> IngredientIndex, ) -> &'s I { let index = create_index(); diff --git a/src/tracked_struct.rs b/src/tracked_struct.rs index 27eda4d61..458123be5 100644 --- a/src/tracked_struct.rs +++ b/src/tracked_struct.rs @@ -115,12 +115,10 @@ impl Default for TrackedStructJar { } impl Jar for TrackedStructJar { - type DbView = dyn Database; - fn create_ingredients( &self, struct_index: crate::storage::IngredientIndex, - ) -> Vec>> { + ) -> Vec> { let struct_ingredient = TrackedStructIngredient::new(struct_index); let struct_map = &struct_ingredient.struct_map.view(); @@ -409,7 +407,8 @@ where } for dependent_fn in self.dependent_fns.iter() { - db.salsa_struct_deleted(dependent_fn, id.as_id()); + db.lookup_ingredient(dependent_fn) + .salsa_struct_deleted(db, id); } } @@ -425,16 +424,14 @@ impl Ingredient for TrackedStructIngredient where C: Configuration, { - type DbView = dyn Database; - fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index } fn maybe_changed_after( &self, - _db: &Self::DbView, - _input: DependencyIndex, + _db: &dyn Database, + _input: Option, _revision: Revision, ) -> bool { false @@ -450,7 +447,7 @@ where fn mark_validated_output<'db>( &'db self, - db: &'db Self::DbView, + db: &'db dyn Database, _executor: DatabaseKeyIndex, output_key: Option, ) { @@ -461,7 +458,7 @@ where fn remove_stale_output( &self, - db: &Self::DbView, + db: &dyn Database, _executor: DatabaseKeyIndex, stale_output_key: Option, ) { @@ -476,20 +473,23 @@ where self.struct_map.drop_deleted_entries(); } - fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: crate::Id) { + fn salsa_struct_deleted(&self, _db: &dyn Database, _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 std::fmt::Debug for TrackedStructIngredient +where + C: Configuration, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(std::any::type_name::()) + .field("ingredient_index", &self.ingredient_index) + .finish() } } diff --git a/src/tracked_struct/tracked_field.rs b/src/tracked_struct/tracked_field.rs index 1df7525bb..ee754c040 100644 --- a/src/tracked_struct/tracked_field.rs +++ b/src/tracked_struct/tracked_field.rs @@ -72,8 +72,6 @@ impl Ingredient for TrackedFieldIngredient where C: Configuration, { - type DbView = dyn Database; - fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index } @@ -84,12 +82,12 @@ where fn maybe_changed_after<'db>( &'db self, - db: &'db Self::DbView, - input: crate::key::DependencyIndex, + db: &'db dyn Database, + input: Option, revision: crate::Revision, ) -> bool { let runtime = db.runtime(); - let id = input.key_index.unwrap(); + let id = input.unwrap(); let data = self.struct_map.get(runtime, id); let data = C::deref_struct(data); let field_changed_at = C::revision(&data.revisions, self.field_index); @@ -102,7 +100,7 @@ where fn mark_validated_output( &self, - _db: &Self::DbView, + _db: &dyn Database, _executor: crate::DatabaseKeyIndex, _output_key: Option, ) { @@ -111,14 +109,14 @@ where fn remove_stale_output( &self, - _db: &Self::DbView, + _db: &dyn Database, _executor: crate::DatabaseKeyIndex, _stale_output_key: Option, ) { panic!("tracked field ingredients have no outputs") } - fn salsa_struct_deleted(&self, _db: &Self::DbView, _id: crate::Id) { + fn salsa_struct_deleted(&self, _db: &dyn Database, _id: crate::Id) { panic!("tracked field ingredients are not registered as dependent") } @@ -139,13 +137,17 @@ where 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 std::fmt::Debug for TrackedFieldIngredient +where + C: Configuration, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(std::any::type_name::()) + .field("ingredient_index", &self.ingredient_index) + .field("field_index", &self.field_index) + .finish() } } diff --git a/src/upcast.rs b/src/upcast.rs new file mode 100644 index 000000000..510017a6b --- /dev/null +++ b/src/upcast.rs @@ -0,0 +1,189 @@ +use std::{ + any::{Any, TypeId}, + marker::PhantomData, + ops::Deref, + sync::Arc, +}; + +use append_only_vec::AppendOnlyVec; + +use crate::Database; + +pub(crate) struct DynUpcastsFor { + upcasts: DynUpcasts, + phantom: PhantomData, +} + +#[derive(Clone)] +pub(crate) struct DynUpcasts { + source_type_id: TypeId, + vec: Arc>, +} + +struct DynUpcast { + target_type_id: TypeId, + type_name: &'static str, + func: fn(&Dummy) -> &Dummy, + func_mut: fn(&mut Dummy) -> &mut Dummy, +} + +#[allow(dead_code)] +enum Dummy {} + +impl Default for DynUpcastsFor { + fn default() -> Self { + Self { + upcasts: DynUpcasts::new::(), + phantom: Default::default(), + } + } +} + +impl DynUpcastsFor { + /// Add a new upcast from `Db` to `T`, given the upcasting function `func`. + pub fn add( + &self, + func: fn(&Db) -> &DbView, + func_mut: fn(&mut Db) -> &mut DbView, + ) { + self.upcasts.add(func, func_mut); + } +} + +impl Deref for DynUpcastsFor { + type Target = DynUpcasts; + + fn deref(&self) -> &Self::Target { + &self.upcasts + } +} + +impl DynUpcasts { + fn new() -> Self { + let source_type_id = TypeId::of::(); + Self { + source_type_id, + vec: Arc::new(AppendOnlyVec::new()), + } + } + + /// Add a new upcast from `Db` to `T`, given the upcasting function `func`. + fn add( + &self, + func: fn(&Db) -> &DbView, + func_mut: fn(&mut Db) -> &mut DbView, + ) { + assert_eq!(self.source_type_id, TypeId::of::(), "dyn-upcasts"); + + let target_type_id = TypeId::of::(); + + if self.vec.iter().any(|u| u.target_type_id == target_type_id) { + return; + } + + self.vec.push(DynUpcast { + target_type_id, + type_name: std::any::type_name::(), + func: unsafe { std::mem::transmute(func) }, + func_mut: unsafe { std::mem::transmute(func_mut) }, + }); + } + + /// Convert one handle to a salsa database (including a `dyn Database`!) to another. + /// + /// # Panics + /// + /// If the underlying type of `db` is not the same as the database type this upcasts was created for. + pub fn try_upcast<'db, DbView: ?Sized + Any>( + &self, + db: &'db dyn Database, + ) -> Option<&'db DbView> { + let db_type_id = ::type_id(db); + assert_eq!(self.source_type_id, db_type_id, "database type mismatch"); + + let view_type_id = TypeId::of::(); + for upcast in self.vec.iter() { + if upcast.target_type_id == view_type_id { + // SAFETY: We have some function that takes a thin reference to the underlying + // database type `X` and returns a (potentially wide) reference to `View`. + // + // While the compiler doesn't know what `X` is at this point, we know it's the + // same as the true type of `db_data_ptr`, and the memory representation for `()` + // and `&X` are the same (since `X` is `Sized`). + let func: fn(&()) -> &DbView = unsafe { std::mem::transmute(upcast.func) }; + return Some(func(data_ptr(db))); + } + } + + None + } + + /// Convert one handle to a salsa database (including a `dyn Database`!) to another. + /// + /// # Panics + /// + /// If the underlying type of `db` is not the same as the database type this upcasts was created for. + pub fn try_upcast_mut<'db, View: ?Sized + Any>( + &self, + db: &'db mut dyn Database, + ) -> Option<&'db mut View> { + let db_type_id = ::type_id(db); + assert_eq!(self.source_type_id, db_type_id, "database type mismatch"); + + let view_type_id = TypeId::of::(); + for upcast in self.vec.iter() { + if upcast.target_type_id == view_type_id { + // SAFETY: We have some function that takes a thin reference to the underlying + // database type `X` and returns a (potentially wide) reference to `View`. + // + // While the compiler doesn't know what `X` is at this point, we know it's the + // same as the true type of `db_data_ptr`, and the memory representation for `()` + // and `&X` are the same (since `X` is `Sized`). + let func_mut: fn(&mut ()) -> &mut View = + unsafe { std::mem::transmute(upcast.func_mut) }; + return Some(func_mut(data_ptr_mut(db))); + } + } + + None + } +} + +impl std::fmt::Debug for DynUpcasts { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DynUpcasts") + .field("vec", &self.vec) + .finish() + } +} + +impl std::fmt::Debug for DynUpcast { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("DynUpcast").field(&self.type_name).finish() + } +} + +/// Given a wide pointer `T`, extracts the data pointer (typed as `()`). +/// This is safe because `()` gives no access to any data and has no validity requirements in particular. +fn data_ptr(t: &T) -> &() { + let t: *const T = t; + let u: *const () = t as *const (); + unsafe { &*u } +} + +/// Given a wide pointer `T`, extracts the data pointer (typed as `()`). +/// This is safe because `()` gives no access to any data and has no validity requirements in particular. +fn data_ptr_mut(t: &mut T) -> &mut () { + let t: *mut T = t; + let u: *mut () = t as *mut (); + unsafe { &mut *u } +} + +impl Clone for DynUpcastsFor { + fn clone(&self) -> Self { + Self { + upcasts: self.upcasts.clone(), + phantom: self.phantom.clone(), + } + } +}