From 60cdce22e9ee5fd4195fc371ebb317e1d5237e6a Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 13 Aug 2022 02:14:14 -0400 Subject: [PATCH] track "dependent functions" --- components/salsa-2022-macros/src/input.rs | 4 + components/salsa-2022-macros/src/interned.rs | 4 + .../salsa-2022-macros/src/tracked_struct.rs | 6 ++ components/salsa-2022/src/function.rs | 24 +++++- components/salsa-2022/src/function/execute.rs | 6 +- components/salsa-2022/src/function/specify.rs | 2 +- components/salsa-2022/src/ingredient_list.rs | 83 +++++++++++++++++++ components/salsa-2022/src/lib.rs | 1 + components/salsa-2022/src/salsa_struct.rs | 6 +- components/salsa-2022/src/tracked_struct.rs | 22 +++++ 10 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 components/salsa-2022/src/ingredient_list.rs diff --git a/components/salsa-2022-macros/src/input.rs b/components/salsa-2022-macros/src/input.rs index fa8d4ec7..fbcf1f30 100644 --- a/components/salsa-2022-macros/src/input.rs +++ b/components/salsa-2022-macros/src/input.rs @@ -205,6 +205,10 @@ impl InputStruct { where DB: ?Sized + salsa::DbWithJar<#jar_ty>, { + fn register_dependent_fn(_db: &DB, _index: salsa::routes::IngredientIndex) { + // Do nothing here, at least for now. + // If/when we add ability to delete inputs, this would become relevant. + } } } } diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 9d8667ab..3ca076f7 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -152,6 +152,10 @@ impl InternedStruct { where DB: ?Sized + salsa::DbWithJar<#jar_ty>, { + fn register_dependent_fn(_db: &DB, _index: salsa::routes::IngredientIndex) { + // Do nothing here, at least for now. + // If/when we add ability to delete inputs, this would become relevant. + } } } } diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index b2dcc11f..7d87abc5 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -211,11 +211,17 @@ impl TrackedStruct { fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { let ident = self.id_ident(); let jar_ty = self.jar_ty(); + let tracked_struct_index: Literal = self.tracked_struct_index(); parse_quote! { impl salsa::salsa_struct::SalsaStructInDb for #ident where DB: ?Sized + salsa::DbWithJar<#jar_ty>, { + fn register_dependent_fn(db: &DB, index: salsa::routes::IngredientIndex) { + let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident>>::ingredient(jar); + ingredients.#tracked_struct_index.register_dependent_fn(index) + } } } } diff --git a/components/salsa-2022/src/function.rs b/components/salsa-2022/src/function.rs index 914d98d3..d889bb2e 100644 --- a/components/salsa-2022/src/function.rs +++ b/components/salsa-2022/src/function.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use arc_swap::ArcSwap; -use crossbeam::queue::SegQueue; +use crossbeam::{atomic::AtomicCell, epoch::Atomic, queue::SegQueue}; use crate::{ cycle::CycleRecoveryStrategy, @@ -67,6 +67,10 @@ pub struct FunctionIngredient { /// we don't know that we can trust the database to give us the same runtime /// everytime and so forth. deleted_entries: SegQueue>>, + + /// Set to true once we invoke `register_dependent_fn` for `C::SalsaStruct`. + /// Prevents us from registering more than once. + registered: AtomicCell, } pub trait Configuration { @@ -140,6 +144,7 @@ where lru: Default::default(), sync_map: Default::default(), deleted_entries: Default::default(), + registered: Default::default(), } } @@ -169,7 +174,13 @@ where std::mem::transmute(memo_value) } - fn insert_memo(&self, key: C::Key, memo: memo::Memo) -> Option<&C::Value> { + fn insert_memo( + &self, + db: &DynDb<'_, C>, + key: C::Key, + memo: memo::Memo, + ) -> Option<&C::Value> { + self.register(db); let memo = Arc::new(memo); let value = unsafe { // Unsafety conditions: memo must be in the map (it's not yet, but it will be by the time this @@ -183,6 +194,15 @@ where } value } + + /// 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(&self, db: &DynDb<'_, C>) { + if !self.registered.fetch_or(true) { + >::register_dependent_fn(db, self.index) + } + } } impl Ingredient for FunctionIngredient diff --git a/components/salsa-2022/src/function/execute.rs b/components/salsa-2022/src/function/execute.rs index 51e3867e..a70b7d65 100644 --- a/components/salsa-2022/src/function/execute.rs +++ b/components/salsa-2022/src/function/execute.rs @@ -91,7 +91,11 @@ where } let value = self - .insert_memo(key, Memo::new(Some(value), revision_now, revisions.clone())) + .insert_memo( + db, + key, + Memo::new(Some(value), revision_now, revisions.clone()), + ) .unwrap(); let stamped_value = revisions.stamped_value(value); diff --git a/components/salsa-2022/src/function/specify.rs b/components/salsa-2022/src/function/specify.rs index 25a4d45a..e511eadd 100644 --- a/components/salsa-2022/src/function/specify.rs +++ b/components/salsa-2022/src/function/specify.rs @@ -75,7 +75,7 @@ where }; log::debug!("specify: about to add memo {:#?} for key {:?}", memo, key); - self.insert_memo(key, memo); + self.insert_memo(db, key, memo); } /// Specify the value for `key` but do not record it is an output. diff --git a/components/salsa-2022/src/ingredient_list.rs b/components/salsa-2022/src/ingredient_list.rs new file mode 100644 index 00000000..f80c41ad --- /dev/null +++ b/components/salsa-2022/src/ingredient_list.rs @@ -0,0 +1,83 @@ +use std::sync::Arc; + +use arc_swap::{ArcSwapOption, AsRaw}; + +use crate::IngredientIndex; + +/// A list of ingredients that can be added to in parallel. +pub(crate) struct IngredientList { + /// A list of each tracked functions. + /// tracked struct. + /// + /// Whenever an instance `i` of this struct is deleted, + /// each of these functions will be notified + /// so they can remove any data tied to that instance. + list: ArcSwapOption>, +} + +impl IngredientList { + pub fn new() -> Self { + Self { + list: ArcSwapOption::new(None), + } + } + + /// Returns an iterator over the items in the list. + /// This is a snapshot of the list as it was when this function is called. + /// Items could still be added in parallel via `add_ingredient` + /// that will not be returned by this iterator. + pub(crate) fn iter(&self) -> impl Iterator { + let guard = self.list.load(); + let mut index = 0; + std::iter::from_fn(move || match &*guard { + Some(list) if index < list.len() => { + let r = list[index]; + index += 1; + Some(r) + } + _ => None, + }) + } + + /// Adds an ingredient to the list (if not already present). + pub(crate) fn push(&self, index: IngredientIndex) { + // This function is called whenever a value is stored, + // so other tracked functions and things may be executing, + // and there could even be two calls to this function in parallel. + // + // We use a "compare-and-swap" strategy of reading the old vector, creating a new vector, + // and then installing it, hoping that nobody has conflicted with us. + // If that fails, we start over. + + loop { + let guard = self.list.load(); + let empty_vec = vec![]; + let old_vec = match &*guard { + Some(v) => v, + None => &empty_vec, + }; + + // First check whether the index is already present. + if old_vec.contains(&index) { + return; + } + + // If not, construct a new vector that has all the old values, followed by `index`. + let vec: Arc> = Arc::new( + old_vec + .iter() + .copied() + .chain(std::iter::once(index)) + .collect(), + ); + + // Try to replace the old vector with the new one. If we fail, loop around again. + assert_eq!(vec.len(), vec.capacity()); + let previous = self.list.compare_and_swap(&guard, Some(vec)); + if guard.as_raw() == previous.as_raw() { + // swap was successful + break; + } + } + } +} diff --git a/components/salsa-2022/src/lib.rs b/components/salsa-2022/src/lib.rs index 1558cbbb..6a0e4ad8 100644 --- a/components/salsa-2022/src/lib.rs +++ b/components/salsa-2022/src/lib.rs @@ -9,6 +9,7 @@ pub mod function; pub mod hash; pub mod id; pub mod ingredient; +pub mod ingredient_list; pub mod input; pub mod input_field; pub mod interned; diff --git a/components/salsa-2022/src/salsa_struct.rs b/components/salsa-2022/src/salsa_struct.rs index 62a7a84b..febdae4a 100644 --- a/components/salsa-2022/src/salsa_struct.rs +++ b/components/salsa-2022/src/salsa_struct.rs @@ -1,3 +1,5 @@ -use crate::Database; +use crate::{Database, IngredientIndex}; -pub trait SalsaStructInDb {} +pub trait SalsaStructInDb { + fn register_dependent_fn(db: &DB, index: IngredientIndex); +} diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 7d7741b4..025c11e5 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -1,6 +1,12 @@ +use std::sync::Arc; + +use arc_swap::{ArcSwap, ArcSwapOption, AsRaw}; + use crate::{ cycle::CycleRecoveryStrategy, ingredient::{Ingredient, MutIngredient}, + ingredient_list::IngredientList, + input, interned::{InternedData, InternedId, InternedIngredient}, key::{DatabaseKeyIndex, DependencyIndex}, runtime::{local_state::QueryOrigin, Runtime}, @@ -33,6 +39,14 @@ where Data: TrackedStructData, { interned: InternedIngredient>, + + /// A list of each tracked function `f` whose key is this + /// tracked struct. + /// + /// Whenever an instance `i` of this struct is deleted, + /// each of these functions will be notified + /// so they can remove any data tied to that instance. + dependent_fns: IngredientList, } #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] @@ -53,6 +67,7 @@ where pub fn new(index: IngredientIndex) -> Self { Self { interned: InternedIngredient::new(index), + dependent_fns: IngredientList::new(), } } @@ -99,6 +114,13 @@ where self.interned.delete_index(id); } } + + /// Adds a dependent function (one keyed by this tracked struct) to our list. + /// When instances of this struct are deleted, these dependent functions + /// will be notified. + pub fn register_dependent_fn(&self, index: IngredientIndex) { + self.dependent_fns.push(index); + } } impl Ingredient for TrackedStructIngredient