mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-08-09 05:19:03 +00:00
WIP-- checkpoint, dynamic jars
This commit is contained in:
parent
380b19cc39
commit
fe1b06a48a
27 changed files with 888 additions and 487 deletions
|
@ -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" }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
123
src/function.rs
123
src/function.rs
|
@ -5,16 +5,16 @@ use crossbeam::atomic::AtomicCell;
|
|||
use crate::{
|
||||
cycle::CycleRecoveryStrategy,
|
||||
ingredient::{fmt_index, IngredientRequiresReset},
|
||||
jar::Jar,
|
||||
key::{DatabaseKeyIndex, DependencyIndex},
|
||||
runtime::local_state::QueryOrigin,
|
||||
salsa_struct::SalsaStructInDb,
|
||||
Cycle, DbWithJar, Event, EventKind, Id, Revision,
|
||||
storage::{HasJarsDyn, IngredientIndex},
|
||||
Cycle, Database, Event, EventKind, Id, Revision,
|
||||
};
|
||||
|
||||
use self::delete::DeletedEntries;
|
||||
|
||||
use super::{ingredient::Ingredient, routes::IngredientIndex};
|
||||
use super::ingredient::Ingredient;
|
||||
|
||||
mod accumulated;
|
||||
mod backdate;
|
||||
|
@ -29,6 +29,47 @@ mod memo;
|
|||
mod specify;
|
||||
mod store;
|
||||
mod sync;
|
||||
pub trait Configuration: 'static {
|
||||
const DEBUG_NAME: &'static str;
|
||||
|
||||
/// The database that this function is associated with.
|
||||
type DbView: ?Sized + crate::Database;
|
||||
|
||||
/// The "salsa struct type" that this function is associated with.
|
||||
/// This can be just `salsa::Id` for functions that intern their arguments
|
||||
/// and are not clearly associated with any one salsa struct.
|
||||
type SalsaStruct<'db>: SalsaStructInDb<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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>> {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
@ -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
226
src/ingredient/adaptor.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
28
src/input.rs
28
src/input.rs
|
@ -9,7 +9,8 @@ use crate::{
|
|||
ingredient::{fmt_index, Ingredient, IngredientRequiresReset},
|
||||
key::{DatabaseKeyIndex, DependencyIndex},
|
||||
runtime::{local_state::QueryOrigin, Runtime},
|
||||
IngredientIndex, Revision,
|
||||
storage::IngredientIndex,
|
||||
Database, Revision,
|
||||
};
|
||||
|
||||
pub trait InputId: FromId + 'static {}
|
||||
|
@ -65,15 +66,22 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
33
src/nonce.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
130
src/routes.rs
130
src/routes.rs
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
336
src/storage.rs
336
src/storage.rs
|
@ -1,32 +1,65 @@
|
|||
use std::any::{Any, TypeId};
|
||||
use std::ptr::NonNull;
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
use parking_lot::Condvar;
|
||||
use append_only_vec::AppendOnlyVec;
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
use parking_lot::{Condvar, Mutex};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::cycle::CycleRecoveryStrategy;
|
||||
use crate::ingredient::Ingredient;
|
||||
use crate::jar::Jar;
|
||||
use crate::ingredient::adaptor::AdaptedIngredient;
|
||||
use crate::ingredient::{Ingredient, Jar, RawIngredient};
|
||||
use crate::key::DependencyIndex;
|
||||
use crate::nonce::{Nonce, NonceGenerator};
|
||||
use crate::runtime::local_state::QueryOrigin;
|
||||
use crate::runtime::Runtime;
|
||||
use crate::{Database, DatabaseKeyIndex, Id, IngredientIndex};
|
||||
use crate::{Database, DatabaseKeyIndex, DatabaseView, Id};
|
||||
|
||||
use super::routes::Routes;
|
||||
use super::{ParallelDatabase, Revision};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct StorageNonce;
|
||||
static NONCE: NonceGenerator<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>()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue