WIP-- checkpoint, dynamic jars

This commit is contained in:
Niko Matsakis 2024-07-08 06:25:32 -04:00
parent 380b19cc39
commit fe1b06a48a
27 changed files with 888 additions and 487 deletions

View file

@ -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" }

View file

@ -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: &Db) -> &AccumulatorIngredient<Self::Data>
where
Db: ?Sized + HasJar<Self::Jar>;
type Data: Clone;
}
pub struct AccumulatorIngredient<Data: Clone> {
pub struct AccumulatorJar<A: Accumulator> {
phantom: PhantomData<A>,
}
impl<A: Accumulator> Default for AccumulatorJar<A> {
fn default() -> Self {
Self {
phantom: Default::default(),
}
}
}
impl<A: Accumulator> Jar for AccumulatorJar<A> {
type DbView = dyn crate::Database;
fn create_ingredients(
&self,
first_index: IngredientIndex,
) -> Vec<Box<dyn Ingredient<DbView = Self::DbView>>> {
vec![Box::new(<AccumulatorIngredient<A>>::new(first_index))]
}
}
pub struct AccumulatorIngredient<A: Accumulator> {
index: IngredientIndex,
map: FxDashMap<DatabaseKeyIndex, AccumulatedValues<Data>>,
debug_name: &'static str,
map: FxDashMap<DatabaseKeyIndex, AccumulatedValues<A::Data>>,
}
struct AccumulatedValues<Data> {
@ -31,12 +51,22 @@ struct AccumulatedValues<Data> {
values: Vec<Data>,
}
impl<Data: Clone> AccumulatorIngredient<Data> {
pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self {
impl<A: Accumulator> AccumulatorIngredient<A> {
/// Find the accumulator ingrediate for `A` in the database, if any.
pub fn from_db<Db>(db: &Db) -> Option<&Self>
where
Db: ?Sized + Database,
{
let jar: AccumulatorJar<A> = Default::default();
let index = db.jar_index_by_type_id(jar.type_id())?;
let ingredient = db.ingredient(index).assert_type::<Self>();
Some(ingredient)
}
pub fn new(index: IngredientIndex) -> Self {
Self {
map: FxDashMap::default(),
index,
debug_name,
}
}
@ -47,7 +77,7 @@ impl<Data: Clone> AccumulatorIngredient<Data> {
}
}
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<Data: Clone> AccumulatorIngredient<Data> {
&self,
runtime: &Runtime,
query: DatabaseKeyIndex,
output: &mut Vec<Data>,
output: &mut Vec<A::Data>,
) {
let current_revision = runtime.current_revision();
if let Some(v) = self.map.get(&query) {
@ -98,16 +128,19 @@ impl<Data: Clone> AccumulatorIngredient<Data> {
}
}
impl<DB: ?Sized, Data> Ingredient<DB> for AccumulatorIngredient<Data>
where
DB: crate::Database,
Data: Clone + 'static,
{
impl<A: Accumulator> Ingredient for AccumulatorIngredient<A> {
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<crate::Id>,
) {
@ -135,7 +168,7 @@ where
fn remove_stale_output(
&self,
db: &DB,
db: &Self::DbView,
executor: DatabaseKeyIndex,
stale_output_key: Option<crate::Id>,
) {
@ -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<crate::Id>, 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<Data> IngredientRequiresReset for AccumulatorIngredient<Data>
where
Data: Clone,
{
impl<A: Accumulator> IngredientRequiresReset for AccumulatorIngredient<A> {
const RESET_ON_NEW_REVISION: bool = false;
}

View file

@ -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<Dyn: ?Sized + Any>: Database {
fn as_dyn(&self) -> &Dyn;
fn as_dyn_mut(&mut self) -> &mut Dyn;
fn storage_for_view(&self) -> &dyn StorageForView<Dyn>;
}
/// 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

View file

@ -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<Self::DbView>;
/// 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<C: Configuration> {
registered: AtomicCell<bool>,
}
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<DynDb<Self>>;
/// 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<Self>, 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<Self>, 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<V: Eq>(old_value: &V, new_value: &V) -> bool {
old_value == new_value
}
pub type DynDb<C> = <<C as Configuration>::Jar as Jar>::DynDb;
/// This type is used to make configuration types for the functions in entities;
/// e.g. you can do `Config<X, 0>` and `Config<X, 1>`.
pub struct Config<const C: usize>(std::marker::PhantomData<[(); C]>);
@ -171,7 +169,7 @@ where
fn insert_memo<'db>(
&'db self,
db: &'db DynDb<C>,
db: &'db C::DbView,
key: Id,
memo: memo::Memo<C::Value<'db>>,
) -> 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<C>) {
fn register<'db>(&self, db: &'db C::DbView) {
if !self.registered.fetch_or(true) {
<C::SalsaStruct<'db> as SalsaStructInDb<_>>::register_dependent_fn(db, self.index)
}
}
}
impl<DB, C> Ingredient<DB> for FunctionIngredient<C>
impl<C> Ingredient for FunctionIngredient<C>
where
DB: ?Sized + DbWithJar<C::Jar>,
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<crate::Id>,
) {
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<crate::Id>,
) {
@ -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<crate::Id>, 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<C> IngredientRequiresReset for FunctionIngredient<C>

View file

@ -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<C> FunctionIngredient<C>
@ -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<C>, key: Id) -> Vec<A::Data>
pub fn accumulated<'db, A>(&'db self, db: &'db C::DbView, key: Id) -> Vec<A::Data>
where
DynDb<C>: HasJar<A::Jar>,
A: Accumulator,
{
// To start, ensure that the value is up to date:
self.fetch(db, key);
let Some(accumulator_ingredient) = <AccumulatorIngredient<A>>::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);

View file

@ -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<C> FunctionIngredient<C>
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<C>,
db: &C::DbView,
key: DatabaseKeyIndex,
old_memo: &Memo<C::Value<'_>>,
revisions: &QueryRevisions,
@ -37,7 +37,7 @@ where
}
}
fn report_stale_output(db: &DynDb<C>, 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,

View file

@ -7,7 +7,7 @@ use crate::{
Cycle, Database, Event, EventKind,
};
use super::{memo::Memo, Configuration, DynDb, FunctionIngredient};
use super::{memo::Memo, Configuration, FunctionIngredient};
impl<C> FunctionIngredient<C>
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<C>,
db: &'db C::DbView,
active_query: ActiveQueryGuard<'_>,
opt_old_memo: Option<Arc<Memo<C::Value<'_>>>>,
) -> StampedValue<&C::Value<'db>> {

View file

@ -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<C> FunctionIngredient<C>
where
C: Configuration,
{
pub fn fetch<'db>(&'db self, db: &'db DynDb<C>, 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<C>,
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<C>,
db: &'db C::DbView,
key: Id,
) -> Option<StampedValue<&'db C::Value<'db>>> {
let memo_guard = self.memo_map.get(key);
@ -69,7 +69,7 @@ where
fn fetch_cold<'db>(
&'db self,
db: &'db DynDb<C>,
db: &'db C::DbView,
key: Id,
) -> Option<StampedValue<&'db C::Value<'db>>> {
let runtime = db.runtime();

View file

@ -12,7 +12,7 @@ use crate::{
Id, Revision, Runtime,
};
use super::{memo::Memo, Configuration, DynDb, FunctionIngredient};
use super::{memo::Memo, Configuration, FunctionIngredient};
impl<C> FunctionIngredient<C>
where
@ -20,7 +20,7 @@ where
{
pub(super) fn maybe_changed_after<'db>(
&'db self,
db: &'db DynDb<C>,
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<C>,
db: &'db C::DbView,
key_index: Id,
revision: Revision,
) -> Option<bool> {
@ -106,7 +106,7 @@ where
#[inline]
pub(super) fn shallow_verify_memo(
&self,
db: &DynDb<C>,
db: &C::DbView,
runtime: &Runtime,
database_key_index: DatabaseKeyIndex,
memo: &Memo<C::Value<'_>>,
@ -146,7 +146,7 @@ where
/// query is on the stack.
pub(super) fn deep_verify_memo(
&self,
db: &DynDb<C>,
db: &C::DbView,
old_memo: &Memo<C::Value<'_>>,
active_query: &ActiveQueryGuard<'_>,
) -> bool {

View file

@ -8,7 +8,7 @@ use crate::{
DatabaseKeyIndex, DebugWithDb, Id,
};
use super::{memo::Memo, Configuration, DynDb, FunctionIngredient};
use super::{memo::Memo, Configuration, FunctionIngredient};
impl<C> FunctionIngredient<C>
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<C>,
db: &'db C::DbView,
key: Id,
value: C::Value<'db>,
origin: impl Fn(DatabaseKeyIndex) -> QueryOrigin,
) where
C::Input<'db>: TrackedStructInDb<DynDb<C>>,
C::Input<'db>: TrackedStructInDb<C::DbView>,
{
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<C>, 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<DynDb<C>>,
C::Input<'db>: TrackedStructInDb<C::DbView>,
{
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<C>,
db: &C::DbView,
executor: DatabaseKeyIndex,
key: Id,
) {

View file

@ -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<Box<dyn Ingredient<DbView = Self::DbView>>>;
}
/// "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<DB: ?Sized>: 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<DB: ?Sized>: 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<DB: ?Sized>: Any {
/// in the current revision.
fn mark_validated_output<'db>(
&'db self,
db: &'db DB,
db: &'db Self::DbView,
executor: DatabaseKeyIndex,
output_key: Option<Id>,
);
@ -52,7 +80,7 @@ pub trait Ingredient<DB: ?Sized>: 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<Id>,
);
@ -61,7 +89,7 @@ pub trait Ingredient<DB: ?Sized>: 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<DB: ?Sized>: Any {
fn fmt_index(&self, index: Option<crate::Id>, 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<T: ?Sized + Ingredient> RawIngredient for T {}
impl dyn RawIngredient {
pub fn assert_type<I: Any>(&self) -> &I {
assert_eq!(
self.type_id(),
TypeId::of::<I>(),
"expecetd a value of type `{}` but type check failed",
std::any::type_name::<I>(),
);
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,

226
src/ingredient/adaptor.rs Normal file
View file

@ -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<Db: Database> {
ingredient: Box<dyn AdaptedIngredientTrait<DbView = Db>>,
}
impl<Db: Database> AdaptedIngredient<Db> {
pub fn new<DbView>(ingredient: Box<dyn Ingredient<DbView = DbView>>) -> Self
where
Db: DatabaseView<DbView>,
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<Db: Database> Ingredient for AdaptedIngredient<Db> {
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<QueryOrigin> {
self.ingredient.origin(key_index)
}
fn mark_validated_output<'db>(
&'db self,
db: &'db Db,
executor: DatabaseKeyIndex,
output_key: Option<Id>,
) {
self.ingredient
.mark_validated_output(db, executor, output_key)
}
fn remove_stale_output(
&self,
db: &Db,
executor: DatabaseKeyIndex,
stale_output_key: Option<Id>,
) {
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<crate::Id>, 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<DbView>`.
struct AdaptedIngredientImpl<Db, DbView: ?Sized + Any> {
ingredient: Box<dyn Ingredient<DbView = DbView>>,
phantom: PhantomData<Db>,
}
impl<Db, DbView: ?Sized + Any> AdaptedIngredientImpl<Db, DbView>
where
Db: DatabaseView<DbView>,
{
fn new(ingredient: Box<dyn Ingredient<DbView = DbView>>) -> Self {
Self {
ingredient,
phantom: PhantomData,
}
}
}
impl<Db, DbView: ?Sized + Any> AdaptedIngredientTrait for AdaptedIngredientImpl<Db, DbView>
where
Db: DatabaseView<DbView>,
{
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<Db, DbView: ?Sized + Any> Ingredient for AdaptedIngredientImpl<Db, DbView>
where
Db: DatabaseView<DbView>,
{
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<QueryOrigin> {
self.ingredient.origin(key_index)
}
fn mark_validated_output<'db>(
&'db self,
db: &'db Db,
executor: DatabaseKeyIndex,
output_key: Option<Id>,
) {
self.ingredient
.mark_validated_output(db.as_dyn(), executor, output_key)
}
fn remove_stale_output(
&self,
db: &Db,
executor: DatabaseKeyIndex,
stale_output_key: Option<Id>,
) {
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<crate::Id>, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.ingredient.fmt_index(index, fmt)
}
}

View file

@ -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 {

View file

@ -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<DB: ?Sized, Id> Ingredient<DB> for InputIngredient<Id>
impl<Id> Ingredient for InputIngredient<Id>
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<crate::Id>,
) {
@ -101,7 +109,7 @@ where
fn remove_stale_output(
&self,
_db: &DB,
_db: &Self::DbView,
executor: DatabaseKeyIndex,
stale_output_key: Option<crate::Id>,
) {
@ -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<crate::Id>, 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<Id> IngredientRequiresReset for InputIngredient<Id>

View file

@ -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<DB: ?Sized, K, F> Ingredient<DB> for InputFieldIngredient<K, F>
impl<K, F> Ingredient for InputFieldIngredient<K, F>
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<Id>,
) {
@ -139,13 +147,13 @@ where
fn remove_stale_output(
&self,
_db: &DB,
_db: &Self::DbView,
_executor: DatabaseKeyIndex,
_stale_output_key: Option<Id>,
) {
}
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<crate::Id>, 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<K, F> IngredientRequiresReset for InputFieldIngredient<K, F>

View file

@ -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<DB: ?Sized, C> Ingredient<DB> for InternedIngredient<C>
impl<C> Ingredient for InternedIngredient<C>
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<crate::Id>,
) {
@ -214,7 +221,7 @@ where
fn remove_stale_output(
&self,
_db: &DB,
_db: &Self::DbView,
executor: DatabaseKeyIndex,
stale_output_key: Option<crate::Id>,
) {
@ -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<crate::Id>, 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<C> IngredientRequiresReset for InternedIngredient<C>

View file

@ -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

View file

@ -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;

33
src/nonce.rs Normal file
View file

@ -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<T> {
value: AtomicU32,
phantom: PhantomData<T>,
}
/// 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<T>(NonZeroU32, PhantomData<T>);
impl<T> NonceGenerator<T> {
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<T> {
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)
}
}

View file

@ -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<DB: HasJars>(init: impl FnOnce(*mut DB::Jars)) -> Box<DB::Jars> {
let layout = alloc::Layout::new::<DB::Jars>();
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::<DB::Jars>();
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

View file

@ -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<DB: HasJars> = dyn Fn(&DB::Jars) -> (&dyn Ingredient<DB>) + Send + Sync;
/// Like a `DynRoute`, but for `&mut` references.
#[allow(type_alias_bounds)]
#[allow(unused_parens)]
pub type DynMutRoute<DB: HasJars> =
dyn Fn(&mut DB::Jars) -> (&mut dyn Ingredient<DB>) + 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<DB: HasJars> {
/// 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<DynRoute<DB>>, Box<DynMutRoute<DB>>)>,
/// Indices of routes which need a 'reset' call.
needs_reset: Vec<IngredientIndex>,
}
impl<DB: HasJars> Routes<DB> {
/// 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<I>(
&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<DB> + 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<DB> {
&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<DB> {
&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<Item = &dyn Fn(&mut DB::Jars) -> &mut dyn Ingredient<DB>> + '_ {
self.needs_reset.iter().map(|&index| self.route_mut(index))
}
}

View file

@ -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;

View file

@ -1,4 +1,4 @@
use crate::{Database, IngredientIndex};
use crate::{storage::IngredientIndex, Database};
pub trait SalsaStructInDb<DB: ?Sized + Database> {
fn register_dependent_fn(db: &DB, index: IngredientIndex);

View file

@ -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<StorageNonce> = 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<u32> 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<DB: HasJars> {
pub struct Storage<Db: Database> {
/// 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<DB>,
/// 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<Routes<DB>>,
shared: Shared<Db>,
/// 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<DB: HasJars> {
/// 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<DB: HasJars> {
/// 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<Db: Database> {
nonce: Nonce<StorageNonce>,
/// Map from the type-id of an `impl Jar` to the index of its first ingredient.
/// This is using a `Mutex<FxHashMap>` (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<Mutex<FxHashMap<TypeId, IngredientIndex>>>,
/// 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<Arc<DB::Jars>>,
/// Immutable unless the mutex on `ingredients_map` is held.
ingredients_vec: Arc<AppendOnlyVec<AdaptedIngredient<Db>>>,
/// 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<Condvar>,
/// A dummy varible that we use to coordinate how many outstanding database handles exist.
/// This is set to `None` when dropping only.
sync: Option<Arc<()>>,
/// Mutex that is used to protect the `jars` field when waiting for snapshots to be dropped.
noti_lock: Arc<parking_lot::Mutex<()>>,
}
// ANCHOR: default
impl<DB> Default for Storage<DB>
where
DB: HasJars,
{
impl<Db: Database> Default for Storage<Db> {
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<DB> Storage<DB>
impl<Db: Database> Storage<Db> {
/// 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<DbView>(
&self,
jar: &dyn Jar<DbView = DbView>,
) -> IngredientIndex
where
DB: HasJars,
Db: DatabaseView<DbView>,
DbView: ?Sized + Any,
{
pub fn snapshot(&self) -> Storage<DB>
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<IngredientIndex> {
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<Db>
where
DB: ParallelDatabase,
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<DB> {
let route = self.routes.route(ingredient_index);
route(self.shared.jars.as_ref().unwrap())
}
}
impl<DB> Clone for Shared<DB>
where
DB: HasJars,
{
impl<Db: Database> Clone for Shared<Db> {
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<DB> Drop for Storage<DB>
where
DB: HasJars,
{
impl<Db: Database> Drop for Storage<Db> {
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<Self>) -> Box<Self::Jars>;
}
pub trait DbWithJar<J>: HasJar<J> + Database {
fn as_jar_db<'db>(&'db self) -> &'db <J as Jar>::DynDb
where
J: Jar;
}
pub trait JarFromJars<J>: HasJars {
fn jar_from_jars(jars: &Self::Jars) -> &J;
fn jar_from_jars_mut(jars: &mut Self::Jars) -> &mut J;
}
pub trait HasJar<J> {
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<IngredientIndex>;
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<I>
pub trait StorageForView<DbView: ?Sized> {
fn nonce(&self) -> Nonce<StorageNonce>;
/// 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<DbView = DbView>) -> Option<IngredientIndex>;
/// 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<DbView = DbView>) -> IngredientIndex;
/// Gets an ingredient by index
fn lookup_ingredient(&self, index: IngredientIndex) -> &dyn RawIngredient;
}
impl<DbView, Db> StorageForView<DbView> for Storage<Db>
where
I: IngredientsFor,
Db: DatabaseView<DbView>,
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<DbView = DbView>) -> IngredientIndex {
self.add_or_lookup_adapted_jar_by_type(jar)
}
pub trait IngredientsFor {
type Jar;
type Ingredients;
fn nonce(&self) -> Nonce<StorageNonce> {
self.shared.nonce
}
fn create_ingredients<DB>(routes: &mut Routes<DB>) -> Self::Ingredients
fn lookup_jar_by_type(&self, jar: &dyn Jar<DbView = DbView>) -> Option<IngredientIndex> {
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<I, DbView>
where
DB: DbWithJar<Self::Jar> + JarFromJars<Self::Jar>;
I: Ingredient<DbView = DbView>,
DbView: ?Sized,
{
cached_data: std::sync::OnceLock<(Nonce<StorageNonce>, *const I)>,
}
impl<I, DbView> IngredientCache<I, DbView>
where
I: Ingredient<DbView = DbView>,
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<DbView>,
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<DbView>,
create_index: &impl Fn() -> IngredientIndex,
) -> &'s I {
let index = create_index();
storage.lookup_ingredient(index).assert_type::<I>()
}
}

View file

@ -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<C>
where
C: Configuration,
{
phantom: PhantomData<C>,
}
impl<C: Configuration> Default for TrackedStructJar<C> {
fn default() -> Self {
Self {
phantom: Default::default(),
}
}
}
impl<C: Configuration> Jar for TrackedStructJar<C> {
type DbView = dyn Database;
fn create_ingredients(
&self,
struct_index: crate::storage::IngredientIndex,
) -> Vec<Box<dyn Ingredient<DbView = Self::DbView>>> {
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::<C>::new(
struct_index,
field_index,
struct_map,
)) as _
}),
)
.collect()
}
}
pub trait TrackedStructInDb<DB: ?Sized + Database>: SalsaStructInDb<DB> {
/// 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<C> {
assert_eq!(
field_ingredient_index.as_u32() - self.ingredient_index.as_u32() - 1,
field_index,
);
TrackedFieldIngredient::<C> {
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<DB: ?Sized, C> Ingredient<DB> for TrackedStructIngredient<C>
impl<C> Ingredient for TrackedStructIngredient<C>
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<crate::Id>,
) {
@ -435,7 +461,7 @@ where
fn remove_stale_output(
&self,
db: &DB,
db: &Self::DbView,
_executor: DatabaseKeyIndex,
stale_output_key: Option<crate::Id>,
) {
@ -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<crate::Id>, 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<C> IngredientRequiresReset for TrackedStructIngredient<C>

View file

@ -35,6 +35,14 @@ where
map: Arc<FxDashMap<Id, Alloc<ValueStruct<C>>>>,
}
impl<C: Configuration> Clone for StructMapView<C> {
fn clone(&self) -> Self {
Self {
map: self.map.clone(),
}
}
}
/// Return value for [`StructMap`][]'s `update` method.
pub(crate) enum Update<'db, C>
where

View file

@ -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<C>,
pub(super) field_debug_name: &'static str,
ingredient_index: IngredientIndex,
field_index: u32,
struct_map: StructMapView<C>,
}
impl<C> TrackedFieldIngredient<C>
where
C: Configuration,
{
pub(super) fn new(
struct_index: IngredientIndex,
field_index: u32,
struct_map: &StructMapView<C>,
) -> 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<DB: ?Sized, C> Ingredient<DB> for TrackedFieldIngredient<C>
impl<C> Ingredient for TrackedFieldIngredient<C>
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<crate::Id>,
) {
@ -98,14 +111,14 @@ where
fn remove_stale_output(
&self,
_db: &DB,
_db: &Self::DbView,
_executor: crate::DatabaseKeyIndex,
_stale_output_key: Option<crate::Id>,
) {
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<C> IngredientRequiresReset for TrackedFieldIngredient<C>