mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
Auto merge of #17639 - Veykril:salsa-perf, r=Veykril
Some more small salsa memory improvements This does limit our lru limits to 2^16 but if you want to set them higher than that you might as well not set them at all. Also makes `LRU` opt-in per query now, allowing us to drop all the unnecessary LRU stuff for most queries
This commit is contained in:
commit
b333f85a9d
18 changed files with 1240 additions and 402 deletions
|
@ -42,9 +42,9 @@ pub trait Upcast<T: ?Sized> {
|
|||
fn upcast(&self) -> &T;
|
||||
}
|
||||
|
||||
pub const DEFAULT_FILE_TEXT_LRU_CAP: usize = 16;
|
||||
pub const DEFAULT_PARSE_LRU_CAP: usize = 128;
|
||||
pub const DEFAULT_BORROWCK_LRU_CAP: usize = 2024;
|
||||
pub const DEFAULT_FILE_TEXT_LRU_CAP: u16 = 16;
|
||||
pub const DEFAULT_PARSE_LRU_CAP: u16 = 128;
|
||||
pub const DEFAULT_BORROWCK_LRU_CAP: u16 = 2024;
|
||||
|
||||
pub trait FileLoader {
|
||||
/// Text of the file.
|
||||
|
@ -59,6 +59,7 @@ pub trait FileLoader {
|
|||
#[salsa::query_group(SourceDatabaseStorage)]
|
||||
pub trait SourceDatabase: FileLoader + std::fmt::Debug {
|
||||
/// Parses the file into the syntax tree.
|
||||
#[salsa::lru]
|
||||
fn parse(&self, file_id: EditionedFileId) -> Parse<ast::SourceFile>;
|
||||
|
||||
/// Returns the set of errors obtained from parsing the file including validation errors.
|
||||
|
@ -105,6 +106,7 @@ pub trait SourceDatabaseExt: SourceDatabase {
|
|||
#[salsa::input]
|
||||
fn compressed_file_text(&self, file_id: FileId) -> Arc<[u8]>;
|
||||
|
||||
#[salsa::lru]
|
||||
fn file_text(&self, file_id: FileId) -> Arc<str>;
|
||||
|
||||
/// Path to a file, relative to the root of its source root.
|
||||
|
|
|
@ -65,7 +65,7 @@ pub trait ExpandDatabase: SourceDatabase {
|
|||
#[salsa::transparent]
|
||||
fn parse_or_expand_with_err(&self, file_id: HirFileId) -> ExpandResult<Parse<SyntaxNode>>;
|
||||
/// Implementation for the macro case.
|
||||
// This query is LRU cached
|
||||
#[salsa::lru]
|
||||
fn parse_macro_expansion(
|
||||
&self,
|
||||
macro_file: MacroFileId,
|
||||
|
|
|
@ -61,6 +61,7 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
|||
) -> Result<Arc<MirBody>, MirLowerError>;
|
||||
|
||||
#[salsa::invoke(crate::mir::borrowck_query)]
|
||||
#[salsa::lru]
|
||||
fn borrowck(&self, def: DefWithBodyId) -> Result<Arc<[BorrowckResult]>, MirLowerError>;
|
||||
|
||||
#[salsa::invoke(crate::consteval::const_eval_query)]
|
||||
|
|
|
@ -145,7 +145,7 @@ impl Default for RootDatabase {
|
|||
}
|
||||
|
||||
impl RootDatabase {
|
||||
pub fn new(lru_capacity: Option<usize>) -> RootDatabase {
|
||||
pub fn new(lru_capacity: Option<u16>) -> RootDatabase {
|
||||
let mut db = RootDatabase { storage: ManuallyDrop::new(salsa::Storage::default()) };
|
||||
db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
|
||||
db.set_proc_macros_with_durability(Default::default(), Durability::HIGH);
|
||||
|
@ -161,7 +161,7 @@ impl RootDatabase {
|
|||
self.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH);
|
||||
}
|
||||
|
||||
pub fn update_base_query_lru_capacities(&mut self, lru_capacity: Option<usize>) {
|
||||
pub fn update_base_query_lru_capacities(&mut self, lru_capacity: Option<u16>) {
|
||||
let lru_capacity = lru_capacity.unwrap_or(base_db::DEFAULT_PARSE_LRU_CAP);
|
||||
base_db::FileTextQuery.in_db_mut(self).set_lru_capacity(DEFAULT_FILE_TEXT_LRU_CAP);
|
||||
base_db::ParseQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
|
||||
|
@ -170,7 +170,7 @@ impl RootDatabase {
|
|||
hir::db::BorrowckQuery.in_db_mut(self).set_lru_capacity(base_db::DEFAULT_BORROWCK_LRU_CAP);
|
||||
}
|
||||
|
||||
pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap<Box<str>, usize>) {
|
||||
pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap<Box<str>, u16>) {
|
||||
use hir::db as hir_db;
|
||||
|
||||
base_db::FileTextQuery.in_db_mut(self).set_lru_capacity(DEFAULT_FILE_TEXT_LRU_CAP);
|
||||
|
@ -192,135 +192,6 @@ impl RootDatabase {
|
|||
.copied()
|
||||
.unwrap_or(base_db::DEFAULT_BORROWCK_LRU_CAP),
|
||||
);
|
||||
|
||||
macro_rules! update_lru_capacity_per_query {
|
||||
($( $module:ident :: $query:ident )*) => {$(
|
||||
if let Some(&cap) = lru_capacities.get(stringify!($query)) {
|
||||
$module::$query.in_db_mut(self).set_lru_capacity(cap);
|
||||
}
|
||||
)*}
|
||||
}
|
||||
update_lru_capacity_per_query![
|
||||
// SourceDatabase
|
||||
// base_db::ParseQuery
|
||||
// base_db::CrateGraphQuery
|
||||
// base_db::ProcMacrosQuery
|
||||
|
||||
// SourceDatabaseExt
|
||||
base_db::FileTextQuery
|
||||
// base_db::FileSourceRootQuery
|
||||
// base_db::SourceRootQuery
|
||||
base_db::SourceRootCratesQuery
|
||||
|
||||
// ExpandDatabase
|
||||
hir_db::AstIdMapQuery
|
||||
// hir_db::ParseMacroExpansionQuery
|
||||
// hir_db::InternMacroCallQuery
|
||||
hir_db::MacroArgQuery
|
||||
hir_db::DeclMacroExpanderQuery
|
||||
// hir_db::MacroExpandQuery
|
||||
hir_db::ExpandProcMacroQuery
|
||||
hir_db::ParseMacroExpansionErrorQuery
|
||||
|
||||
// DefDatabase
|
||||
hir_db::FileItemTreeQuery
|
||||
hir_db::BlockDefMapQuery
|
||||
hir_db::StructDataWithDiagnosticsQuery
|
||||
hir_db::UnionDataWithDiagnosticsQuery
|
||||
hir_db::EnumDataQuery
|
||||
hir_db::EnumVariantDataWithDiagnosticsQuery
|
||||
hir_db::ImplDataWithDiagnosticsQuery
|
||||
hir_db::TraitDataWithDiagnosticsQuery
|
||||
hir_db::TraitAliasDataQuery
|
||||
hir_db::TypeAliasDataQuery
|
||||
hir_db::FunctionDataQuery
|
||||
hir_db::ConstDataQuery
|
||||
hir_db::StaticDataQuery
|
||||
hir_db::Macro2DataQuery
|
||||
hir_db::MacroRulesDataQuery
|
||||
hir_db::ProcMacroDataQuery
|
||||
hir_db::BodyWithSourceMapQuery
|
||||
hir_db::BodyQuery
|
||||
hir_db::ExprScopesQuery
|
||||
hir_db::GenericParamsQuery
|
||||
hir_db::FieldsAttrsQuery
|
||||
hir_db::FieldsAttrsSourceMapQuery
|
||||
hir_db::AttrsQuery
|
||||
hir_db::CrateLangItemsQuery
|
||||
hir_db::LangItemQuery
|
||||
hir_db::ImportMapQuery
|
||||
hir_db::FieldVisibilitiesQuery
|
||||
hir_db::FunctionVisibilityQuery
|
||||
hir_db::ConstVisibilityQuery
|
||||
hir_db::CrateSupportsNoStdQuery
|
||||
|
||||
// HirDatabase
|
||||
hir_db::MirBodyQuery
|
||||
hir_db::BorrowckQuery
|
||||
hir_db::TyQuery
|
||||
hir_db::ValueTyQuery
|
||||
hir_db::ImplSelfTyQuery
|
||||
hir_db::ConstParamTyQuery
|
||||
hir_db::ConstEvalQuery
|
||||
hir_db::ConstEvalDiscriminantQuery
|
||||
hir_db::ImplTraitQuery
|
||||
hir_db::FieldTypesQuery
|
||||
hir_db::LayoutOfAdtQuery
|
||||
hir_db::TargetDataLayoutQuery
|
||||
hir_db::CallableItemSignatureQuery
|
||||
hir_db::ReturnTypeImplTraitsQuery
|
||||
hir_db::GenericPredicatesForParamQuery
|
||||
hir_db::GenericPredicatesQuery
|
||||
hir_db::TraitEnvironmentQuery
|
||||
hir_db::GenericDefaultsQuery
|
||||
hir_db::InherentImplsInCrateQuery
|
||||
hir_db::InherentImplsInBlockQuery
|
||||
hir_db::IncoherentInherentImplCratesQuery
|
||||
hir_db::TraitImplsInCrateQuery
|
||||
hir_db::TraitImplsInBlockQuery
|
||||
hir_db::TraitImplsInDepsQuery
|
||||
// hir_db::InternCallableDefQuery
|
||||
// hir_db::InternLifetimeParamIdQuery
|
||||
// hir_db::InternImplTraitIdQuery
|
||||
// hir_db::InternTypeOrConstParamIdQuery
|
||||
// hir_db::InternClosureQuery
|
||||
// hir_db::InternCoroutineQuery
|
||||
hir_db::AssociatedTyDataQuery
|
||||
hir_db::TraitDatumQuery
|
||||
hir_db::AdtDatumQuery
|
||||
hir_db::ImplDatumQuery
|
||||
hir_db::FnDefDatumQuery
|
||||
hir_db::FnDefVarianceQuery
|
||||
hir_db::AdtVarianceQuery
|
||||
hir_db::AssociatedTyValueQuery
|
||||
hir_db::ProgramClausesForChalkEnvQuery
|
||||
|
||||
// SymbolsDatabase
|
||||
symbol_index::ModuleSymbolsQuery
|
||||
symbol_index::LibrarySymbolsQuery
|
||||
// symbol_index::LocalRootsQuery
|
||||
// symbol_index::LibraryRootsQuery
|
||||
|
||||
// LineIndexDatabase
|
||||
crate::LineIndexQuery
|
||||
|
||||
// InternDatabase
|
||||
// hir_db::InternFunctionQuery
|
||||
// hir_db::InternStructQuery
|
||||
// hir_db::InternUnionQuery
|
||||
// hir_db::InternEnumQuery
|
||||
// hir_db::InternConstQuery
|
||||
// hir_db::InternStaticQuery
|
||||
// hir_db::InternTraitQuery
|
||||
// hir_db::InternTraitAliasQuery
|
||||
// hir_db::InternTypeAliasQuery
|
||||
// hir_db::InternImplQuery
|
||||
// hir_db::InternExternBlockQuery
|
||||
// hir_db::InternBlockQuery
|
||||
// hir_db::InternMacro2Query
|
||||
// hir_db::InternProcMacroQuery
|
||||
// hir_db::InternMacroRulesQuery
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ pub struct AnalysisHost {
|
|||
}
|
||||
|
||||
impl AnalysisHost {
|
||||
pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
|
||||
pub fn new(lru_capacity: Option<u16>) -> AnalysisHost {
|
||||
AnalysisHost { db: RootDatabase::new(lru_capacity) }
|
||||
}
|
||||
|
||||
|
@ -169,11 +169,11 @@ impl AnalysisHost {
|
|||
AnalysisHost { db }
|
||||
}
|
||||
|
||||
pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
|
||||
pub fn update_lru_capacity(&mut self, lru_capacity: Option<u16>) {
|
||||
self.db.update_base_query_lru_capacities(lru_capacity);
|
||||
}
|
||||
|
||||
pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap<Box<str>, usize>) {
|
||||
pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap<Box<str>, u16>) {
|
||||
self.db.update_lru_capacities(lru_capacities);
|
||||
}
|
||||
|
||||
|
|
|
@ -377,7 +377,7 @@ fn load_crate_graph(
|
|||
) -> RootDatabase {
|
||||
let ProjectWorkspace { toolchain, target_layout, .. } = ws;
|
||||
|
||||
let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
|
||||
let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<u16>().ok());
|
||||
let mut db = RootDatabase::new(lru_cap);
|
||||
let mut analysis_change = ChangeWithProcMacros::new();
|
||||
|
||||
|
|
|
@ -283,9 +283,9 @@ config_data! {
|
|||
linkedProjects: Vec<ManifestOrProjectJson> = vec![],
|
||||
|
||||
/// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
|
||||
lru_capacity: Option<usize> = None,
|
||||
lru_capacity: Option<u16> = None,
|
||||
/// Sets the LRU capacity of the specified queries.
|
||||
lru_query_capacities: FxHashMap<Box<str>, usize> = FxHashMap::default(),
|
||||
lru_query_capacities: FxHashMap<Box<str>, u16> = FxHashMap::default(),
|
||||
|
||||
/// These proc-macros will be ignored when trying to expand them.
|
||||
///
|
||||
|
@ -1743,11 +1743,11 @@ impl Config {
|
|||
extra_env
|
||||
}
|
||||
|
||||
pub fn lru_parse_query_capacity(&self) -> Option<usize> {
|
||||
pub fn lru_parse_query_capacity(&self) -> Option<u16> {
|
||||
self.lru_capacity().to_owned()
|
||||
}
|
||||
|
||||
pub fn lru_query_capacities_config(&self) -> Option<&FxHashMap<Box<str>, usize>> {
|
||||
pub fn lru_query_capacities_config(&self) -> Option<&FxHashMap<Box<str>, u16>> {
|
||||
self.lru_query_capacities().is_empty().not().then(|| self.lru_query_capacities())
|
||||
}
|
||||
|
||||
|
@ -2926,7 +2926,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
|
|||
"FxHashMap<String, String>" => set! {
|
||||
"type": "object",
|
||||
},
|
||||
"FxHashMap<Box<str>, usize>" => set! {
|
||||
"FxHashMap<Box<str>, u16>" => set! {
|
||||
"type": "object",
|
||||
},
|
||||
"FxHashMap<String, Option<String>>" => set! {
|
||||
|
@ -2936,6 +2936,11 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
|
|||
"type": ["null", "integer"],
|
||||
"minimum": 0,
|
||||
},
|
||||
"Option<u16>" => set! {
|
||||
"type": ["null", "integer"],
|
||||
"minimum": 0,
|
||||
"maximum": 65535,
|
||||
},
|
||||
"Option<String>" => set! {
|
||||
"type": ["null", "string"],
|
||||
},
|
||||
|
|
|
@ -53,7 +53,11 @@ pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream
|
|||
num_storages += 1;
|
||||
}
|
||||
"dependencies" => {
|
||||
storage = QueryStorage::Dependencies;
|
||||
storage = QueryStorage::LruDependencies;
|
||||
num_storages += 1;
|
||||
}
|
||||
"lru" => {
|
||||
storage = QueryStorage::LruMemoized;
|
||||
num_storages += 1;
|
||||
}
|
||||
"input" => {
|
||||
|
@ -235,7 +239,7 @@ pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream
|
|||
|
||||
queries_with_storage.push(fn_name);
|
||||
|
||||
let tracing = if let QueryStorage::Memoized = query.storage {
|
||||
let tracing = if let QueryStorage::Memoized | QueryStorage::LruMemoized = query.storage {
|
||||
let s = format!("{trait_name}::{fn_name}");
|
||||
Some(quote! {
|
||||
let _p = tracing::debug_span!(#s, #(#key_names = tracing::field::debug(&#key_names)),*).entered();
|
||||
|
@ -376,8 +380,9 @@ pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream
|
|||
|
||||
let storage = match &query.storage {
|
||||
QueryStorage::Memoized => quote!(salsa::plumbing::MemoizedStorage<Self>),
|
||||
QueryStorage::Dependencies => {
|
||||
quote!(salsa::plumbing::DependencyStorage<Self>)
|
||||
QueryStorage::LruMemoized => quote!(salsa::plumbing::LruMemoizedStorage<Self>),
|
||||
QueryStorage::LruDependencies => {
|
||||
quote!(salsa::plumbing::LruDependencyStorage<Self>)
|
||||
}
|
||||
QueryStorage::Input if query.keys.is_empty() => {
|
||||
quote!(salsa::plumbing::UnitInputStorage<Self>)
|
||||
|
@ -724,7 +729,8 @@ impl Query {
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum QueryStorage {
|
||||
Memoized,
|
||||
Dependencies,
|
||||
LruDependencies,
|
||||
LruMemoized,
|
||||
Input,
|
||||
Interned,
|
||||
InternedLookup { intern_query_type: Ident },
|
||||
|
@ -739,7 +745,9 @@ impl QueryStorage {
|
|||
| QueryStorage::Interned
|
||||
| QueryStorage::InternedLookup { .. }
|
||||
| QueryStorage::Transparent => false,
|
||||
QueryStorage::Memoized | QueryStorage::Dependencies => true,
|
||||
QueryStorage::Memoized | QueryStorage::LruMemoized | QueryStorage::LruDependencies => {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use crate::debug::TableEntry;
|
||||
use crate::durability::Durability;
|
||||
use crate::hash::FxIndexMap;
|
||||
use crate::lru::Lru;
|
||||
use crate::plumbing::DerivedQueryStorageOps;
|
||||
use crate::plumbing::LruQueryStorageOps;
|
||||
use crate::plumbing::QueryFunction;
|
||||
use crate::plumbing::QueryStorageMassOps;
|
||||
use crate::plumbing::QueryStorageOps;
|
||||
|
@ -13,7 +11,6 @@ use crate::{Database, DatabaseKeyIndex, QueryDb, Revision};
|
|||
use parking_lot::RwLock;
|
||||
use std::borrow::Borrow;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use triomphe::Arc;
|
||||
|
||||
mod slot;
|
||||
|
@ -22,79 +19,33 @@ use slot::Slot;
|
|||
/// Memoized queries store the result plus a list of the other queries
|
||||
/// that they invoked. This means we can avoid recomputing them when
|
||||
/// none of those inputs have changed.
|
||||
pub type MemoizedStorage<Q> = DerivedStorage<Q, AlwaysMemoizeValue>;
|
||||
|
||||
/// "Dependency" queries just track their dependencies and not the
|
||||
/// actual value (which they produce on demand). This lessens the
|
||||
/// storage requirements.
|
||||
pub type DependencyStorage<Q> = DerivedStorage<Q, NeverMemoizeValue>;
|
||||
pub type MemoizedStorage<Q> = DerivedStorage<Q>;
|
||||
|
||||
/// Handles storage where the value is 'derived' by executing a
|
||||
/// function (in contrast to "inputs").
|
||||
pub struct DerivedStorage<Q, MP>
|
||||
pub struct DerivedStorage<Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
group_index: u16,
|
||||
lru_list: Lru<Slot<Q, MP>>,
|
||||
slot_map: RwLock<FxIndexMap<Q::Key, Arc<Slot<Q, MP>>>>,
|
||||
policy: PhantomData<MP>,
|
||||
slot_map: RwLock<FxIndexMap<Q::Key, Arc<Slot<Q>>>>,
|
||||
}
|
||||
|
||||
impl<Q, MP> std::panic::RefUnwindSafe for DerivedStorage<Q, MP>
|
||||
impl<Q> std::panic::RefUnwindSafe for DerivedStorage<Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
|
||||
Q::Key: std::panic::RefUnwindSafe,
|
||||
Q::Value: std::panic::RefUnwindSafe,
|
||||
{
|
||||
}
|
||||
|
||||
pub trait MemoizationPolicy<Q>: Send + Sync
|
||||
where
|
||||
Q: QueryFunction,
|
||||
{
|
||||
fn should_memoize_value(key: &Q::Key) -> bool;
|
||||
|
||||
fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool;
|
||||
}
|
||||
|
||||
pub enum AlwaysMemoizeValue {}
|
||||
impl<Q> MemoizationPolicy<Q> for AlwaysMemoizeValue
|
||||
impl<Q> DerivedStorage<Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
Q::Value: Eq,
|
||||
{
|
||||
fn should_memoize_value(_key: &Q::Key) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool {
|
||||
old_value == new_value
|
||||
}
|
||||
}
|
||||
|
||||
pub enum NeverMemoizeValue {}
|
||||
impl<Q> MemoizationPolicy<Q> for NeverMemoizeValue
|
||||
where
|
||||
Q: QueryFunction,
|
||||
{
|
||||
fn should_memoize_value(_key: &Q::Key) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn memoized_value_eq(_old_value: &Q::Value, _new_value: &Q::Value) -> bool {
|
||||
panic!("cannot reach since we never memoize")
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> DerivedStorage<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn slot(&self, key: &Q::Key) -> Arc<Slot<Q, MP>> {
|
||||
fn slot(&self, key: &Q::Key) -> Arc<Slot<Q>> {
|
||||
if let Some(v) = self.slot_map.read().get(key) {
|
||||
return v.clone();
|
||||
}
|
||||
|
@ -111,20 +62,15 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> QueryStorageOps<Q> for DerivedStorage<Q, MP>
|
||||
impl<Q> QueryStorageOps<Q> for DerivedStorage<Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
Q::Value: Eq,
|
||||
{
|
||||
const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = Q::CYCLE_STRATEGY;
|
||||
|
||||
fn new(group_index: u16) -> Self {
|
||||
DerivedStorage {
|
||||
group_index,
|
||||
slot_map: RwLock::new(FxIndexMap::default()),
|
||||
lru_list: Default::default(),
|
||||
policy: PhantomData,
|
||||
}
|
||||
DerivedStorage { group_index, slot_map: RwLock::new(FxIndexMap::default()) }
|
||||
}
|
||||
|
||||
fn fmt_index(
|
||||
|
@ -161,10 +107,6 @@ where
|
|||
let slot = self.slot(key);
|
||||
let StampedValue { value, durability, changed_at } = slot.read(db, key);
|
||||
|
||||
if let Some(evicted) = self.lru_list.record_use(&slot) {
|
||||
evicted.evict();
|
||||
}
|
||||
|
||||
db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
|
||||
slot.database_key_index(),
|
||||
durability,
|
||||
|
@ -175,7 +117,7 @@ where
|
|||
}
|
||||
|
||||
fn durability(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Durability {
|
||||
self.slot(key).durability(db)
|
||||
self.slot_map.read().get(key).map_or(Durability::LOW, |slot| slot.durability(db))
|
||||
}
|
||||
|
||||
fn entries<C>(&self, _db: &<Q as QueryDb<'_>>::DynDb) -> C
|
||||
|
@ -187,31 +129,19 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> QueryStorageMassOps for DerivedStorage<Q, MP>
|
||||
impl<Q> QueryStorageMassOps for DerivedStorage<Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn purge(&self) {
|
||||
self.lru_list.purge();
|
||||
*self.slot_map.write() = Default::default();
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> LruQueryStorageOps for DerivedStorage<Q, MP>
|
||||
impl<Q> DerivedQueryStorageOps<Q> for DerivedStorage<Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn set_lru_capacity(&self, new_capacity: usize) {
|
||||
self.lru_list.set_lru_capacity(new_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> DerivedQueryStorageOps<Q> for DerivedStorage<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
Q::Value: Eq,
|
||||
{
|
||||
fn invalidate<S>(&self, runtime: &mut Runtime, key: &S)
|
||||
where
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
use crate::debug::TableEntry;
|
||||
use crate::derived::MemoizationPolicy;
|
||||
use crate::durability::Durability;
|
||||
use crate::lru::LruIndex;
|
||||
use crate::lru::LruNode;
|
||||
use crate::plumbing::{DatabaseOps, QueryFunction};
|
||||
use crate::revision::Revision;
|
||||
use crate::runtime::local_state::ActiveQueryGuard;
|
||||
|
@ -14,21 +11,18 @@ use crate::runtime::WaitResult;
|
|||
use crate::Cycle;
|
||||
use crate::{Database, DatabaseKeyIndex, Event, EventKind, QueryDb};
|
||||
use parking_lot::{RawRwLock, RwLock};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tracing::{debug, info};
|
||||
|
||||
pub(super) struct Slot<Q, MP>
|
||||
pub(super) struct Slot<Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
key_index: u32,
|
||||
// FIXME: Yeet this
|
||||
group_index: u16,
|
||||
state: RwLock<QueryState<Q>>,
|
||||
lru_index: LruIndex,
|
||||
policy: PhantomData<MP>,
|
||||
}
|
||||
|
||||
/// Defines the "current state" of query's memoized results.
|
||||
|
@ -54,7 +48,7 @@ where
|
|||
|
||||
struct Memo<V> {
|
||||
/// The result of the query, if we decide to memoize it.
|
||||
value: Option<V>,
|
||||
value: V,
|
||||
|
||||
/// Last revision when this memo was verified; this begins
|
||||
/// as the current revision.
|
||||
|
@ -77,12 +71,6 @@ enum ProbeState<V, G> {
|
|||
/// verified in this revision.
|
||||
Stale(G),
|
||||
|
||||
/// There is an entry, and it has been verified
|
||||
/// in this revision, but it has no cached
|
||||
/// value. The `Revision` is the revision where the
|
||||
/// value last changed (if we were to recompute it).
|
||||
NoValue(G, Revision),
|
||||
|
||||
/// There is an entry which has been verified,
|
||||
/// and it has the following value-- or, we blocked
|
||||
/// on another thread, and that resulted in a cycle.
|
||||
|
@ -103,18 +91,16 @@ enum MaybeChangedSinceProbeState<G> {
|
|||
Stale(G),
|
||||
}
|
||||
|
||||
impl<Q, MP> Slot<Q, MP>
|
||||
impl<Q> Slot<Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
Q::Value: Eq,
|
||||
{
|
||||
pub(super) fn new(database_key_index: DatabaseKeyIndex) -> Self {
|
||||
Self {
|
||||
key_index: database_key_index.key_index,
|
||||
group_index: database_key_index.group_index,
|
||||
state: RwLock::new(QueryState::NotComputed),
|
||||
lru_index: LruIndex::default(),
|
||||
policy: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,9 +132,7 @@ where
|
|||
loop {
|
||||
match self.probe(db, self.state.read(), runtime, revision_now) {
|
||||
ProbeState::UpToDate(v) => return v,
|
||||
ProbeState::Stale(..) | ProbeState::NoValue(..) | ProbeState::NotComputed(..) => {
|
||||
break
|
||||
}
|
||||
ProbeState::Stale(..) | ProbeState::NotComputed(..) => break,
|
||||
ProbeState::Retry => continue,
|
||||
}
|
||||
}
|
||||
|
@ -176,9 +160,7 @@ where
|
|||
let mut old_memo = loop {
|
||||
match self.probe(db, self.state.upgradable_read(), runtime, revision_now) {
|
||||
ProbeState::UpToDate(v) => return v,
|
||||
ProbeState::Stale(state)
|
||||
| ProbeState::NotComputed(state)
|
||||
| ProbeState::NoValue(state, _) => {
|
||||
ProbeState::Stale(state) | ProbeState::NotComputed(state) => {
|
||||
type RwLockUpgradableReadGuard<'a, T> =
|
||||
lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>;
|
||||
|
||||
|
@ -226,7 +208,7 @@ where
|
|||
runtime: &Runtime,
|
||||
revision_now: Revision,
|
||||
active_query: ActiveQueryGuard<'_>,
|
||||
panic_guard: PanicGuard<'_, Q, MP>,
|
||||
panic_guard: PanicGuard<'_, Q>,
|
||||
old_memo: Option<Memo<Q::Value>>,
|
||||
key: &Q::Key,
|
||||
) -> StampedValue<Q::Value> {
|
||||
|
@ -285,22 +267,18 @@ where
|
|||
// "backdate" its `changed_at` revision to be the same as the
|
||||
// old value.
|
||||
if let Some(old_memo) = &old_memo {
|
||||
if let Some(old_value) = &old_memo.value {
|
||||
// Careful: if the value became less durable than it
|
||||
// used to be, that is a "breaking change" that our
|
||||
// consumers must be aware of. Becoming *more* durable
|
||||
// is not. See the test `constant_to_non_constant`.
|
||||
if revisions.durability >= old_memo.revisions.durability
|
||||
&& MP::memoized_value_eq(old_value, &value)
|
||||
{
|
||||
debug!(
|
||||
"read_upgrade({:?}): value is equal, back-dating to {:?}",
|
||||
self, old_memo.revisions.changed_at,
|
||||
);
|
||||
// Careful: if the value became less durable than it
|
||||
// used to be, that is a "breaking change" that our
|
||||
// consumers must be aware of. Becoming *more* durable
|
||||
// is not. See the test `constant_to_non_constant`.
|
||||
if revisions.durability >= old_memo.revisions.durability && old_memo.value == value {
|
||||
debug!(
|
||||
"read_upgrade({:?}): value is equal, back-dating to {:?}",
|
||||
self, old_memo.revisions.changed_at,
|
||||
);
|
||||
|
||||
assert!(old_memo.revisions.changed_at <= revisions.changed_at);
|
||||
revisions.changed_at = old_memo.revisions.changed_at;
|
||||
}
|
||||
assert!(old_memo.revisions.changed_at <= revisions.changed_at);
|
||||
revisions.changed_at = old_memo.revisions.changed_at;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,8 +288,7 @@ where
|
|||
changed_at: revisions.changed_at,
|
||||
};
|
||||
|
||||
let memo_value =
|
||||
if self.should_memoize_value(key) { Some(new_value.value.clone()) } else { None };
|
||||
let memo_value = new_value.value.clone();
|
||||
|
||||
debug!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,);
|
||||
|
||||
|
@ -371,20 +348,16 @@ where
|
|||
return ProbeState::Stale(state);
|
||||
}
|
||||
|
||||
if let Some(value) = &memo.value {
|
||||
let value = StampedValue {
|
||||
durability: memo.revisions.durability,
|
||||
changed_at: memo.revisions.changed_at,
|
||||
value: value.clone(),
|
||||
};
|
||||
let value = &memo.value;
|
||||
let value = StampedValue {
|
||||
durability: memo.revisions.durability,
|
||||
changed_at: memo.revisions.changed_at,
|
||||
value: value.clone(),
|
||||
};
|
||||
|
||||
info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at);
|
||||
info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at);
|
||||
|
||||
ProbeState::UpToDate(value)
|
||||
} else {
|
||||
let changed_at = memo.revisions.changed_at;
|
||||
ProbeState::NoValue(state, changed_at)
|
||||
}
|
||||
ProbeState::UpToDate(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -407,21 +380,9 @@ where
|
|||
match &*self.state.read() {
|
||||
QueryState::NotComputed => None,
|
||||
QueryState::InProgress { .. } => Some(TableEntry::new(key.clone(), None)),
|
||||
QueryState::Memoized(memo) => Some(TableEntry::new(key.clone(), memo.value.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn evict(&self) {
|
||||
let mut state = self.state.write();
|
||||
if let QueryState::Memoized(memo) = &mut *state {
|
||||
// Evicting a value with an untracked input could
|
||||
// lead to inconsistencies. Note that we can't check
|
||||
// `has_untracked_input` when we add the value to the cache,
|
||||
// because inputs can become untracked in the next revision.
|
||||
if memo.has_untracked_input() {
|
||||
return;
|
||||
QueryState::Memoized(memo) => {
|
||||
Some(TableEntry::new(key.clone(), Some(memo.value.clone())))
|
||||
}
|
||||
memo.value = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -489,8 +450,7 @@ where
|
|||
|
||||
// If we know when value last changed, we can return right away.
|
||||
// Note that we don't need the actual value to be available.
|
||||
ProbeState::NoValue(_, changed_at)
|
||||
| ProbeState::UpToDate(StampedValue { value: _, durability: _, changed_at }) => {
|
||||
ProbeState::UpToDate(StampedValue { value: _, durability: _, changed_at }) => {
|
||||
MaybeChangedSinceProbeState::ChangedAt(changed_at)
|
||||
}
|
||||
|
||||
|
@ -545,7 +505,7 @@ where
|
|||
let maybe_changed = old_memo.revisions.changed_at > revision;
|
||||
panic_guard.proceed(Some(old_memo));
|
||||
maybe_changed
|
||||
} else if old_memo.value.is_some() {
|
||||
} else {
|
||||
// We found that this memoized value may have changed
|
||||
// but we have an old value. We can re-run the code and
|
||||
// actually *check* if it has changed.
|
||||
|
@ -559,12 +519,6 @@ where
|
|||
key,
|
||||
);
|
||||
changed_at > revision
|
||||
} else {
|
||||
// We found that inputs to this memoized value may have chanced
|
||||
// but we don't have an old value to compare against or re-use.
|
||||
// No choice but to drop the memo and say that its value may have changed.
|
||||
panic_guard.proceed(None);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -583,10 +537,6 @@ where
|
|||
mutex_guard,
|
||||
)
|
||||
}
|
||||
|
||||
fn should_memoize_value(&self, key: &Q::Key) -> bool {
|
||||
MP::should_memoize_value(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q> QueryState<Q>
|
||||
|
@ -598,21 +548,21 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
struct PanicGuard<'me, Q, MP>
|
||||
struct PanicGuard<'me, Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
Q::Value: Eq,
|
||||
{
|
||||
slot: &'me Slot<Q, MP>,
|
||||
slot: &'me Slot<Q>,
|
||||
runtime: &'me Runtime,
|
||||
}
|
||||
|
||||
impl<'me, Q, MP> PanicGuard<'me, Q, MP>
|
||||
impl<'me, Q> PanicGuard<'me, Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
Q::Value: Eq,
|
||||
{
|
||||
fn new(slot: &'me Slot<Q, MP>, runtime: &'me Runtime) -> Self {
|
||||
fn new(slot: &'me Slot<Q>, runtime: &'me Runtime) -> Self {
|
||||
Self { slot, runtime }
|
||||
}
|
||||
|
||||
|
@ -666,10 +616,10 @@ Please report this bug to https://github.com/salsa-rs/salsa/issues."
|
|||
}
|
||||
}
|
||||
|
||||
impl<'me, Q, MP> Drop for PanicGuard<'me, Q, MP>
|
||||
impl<'me, Q> Drop for PanicGuard<'me, Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
Q::Value: Eq,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if std::thread::panicking() {
|
||||
|
@ -702,15 +652,11 @@ where
|
|||
revision_now: Revision,
|
||||
active_query: &ActiveQueryGuard<'_>,
|
||||
) -> Option<StampedValue<V>> {
|
||||
// If we don't have a memoized value, nothing to validate.
|
||||
if self.value.is_none() {
|
||||
return None;
|
||||
}
|
||||
if self.verify_revisions(db, revision_now, active_query) {
|
||||
self.value.clone().map(|value| StampedValue {
|
||||
Some(StampedValue {
|
||||
durability: self.revisions.durability,
|
||||
changed_at: self.revisions.changed_at,
|
||||
value,
|
||||
value: self.value.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
@ -788,58 +734,42 @@ where
|
|||
self.verified_at = revision_now;
|
||||
true
|
||||
}
|
||||
|
||||
fn has_untracked_input(&self) -> bool {
|
||||
self.revisions.untracked
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> std::fmt::Debug for Slot<Q, MP>
|
||||
impl<Q> std::fmt::Debug for Slot<Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(fmt, "{:?}", Q::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> LruNode for Slot<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn lru_index(&self) -> &LruIndex {
|
||||
&self.lru_index
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that `Slot<Q, MP>: Send + Sync` as long as
|
||||
/// Check that `Slot<Q, >: Send + Sync` as long as
|
||||
/// `DB::DatabaseData: Send + Sync`, which in turn implies that
|
||||
/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`.
|
||||
#[allow(dead_code)]
|
||||
fn check_send_sync<Q, MP>()
|
||||
fn check_send_sync<Q>()
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
|
||||
Q::Key: Send + Sync,
|
||||
Q::Value: Send + Sync,
|
||||
{
|
||||
fn is_send_sync<T: Send + Sync>() {}
|
||||
is_send_sync::<Slot<Q, MP>>();
|
||||
is_send_sync::<Slot<Q>>();
|
||||
}
|
||||
|
||||
/// Check that `Slot<Q, MP>: 'static` as long as
|
||||
/// Check that `Slot<Q, >: 'static` as long as
|
||||
/// `DB::DatabaseData: 'static`, which in turn implies that
|
||||
/// `Q::Key: 'static`, `Q::Value: 'static`.
|
||||
#[allow(dead_code)]
|
||||
fn check_static<Q, MP>()
|
||||
fn check_static<Q>()
|
||||
where
|
||||
Q: QueryFunction + 'static,
|
||||
MP: MemoizationPolicy<Q> + 'static,
|
||||
Q::Key: 'static,
|
||||
Q::Value: 'static,
|
||||
{
|
||||
fn is_static<T: 'static>() {}
|
||||
is_static::<Slot<Q, MP>>();
|
||||
is_static::<Slot<Q>>();
|
||||
}
|
||||
|
|
233
crates/salsa/src/derived_lru.rs
Normal file
233
crates/salsa/src/derived_lru.rs
Normal file
|
@ -0,0 +1,233 @@
|
|||
use crate::debug::TableEntry;
|
||||
use crate::durability::Durability;
|
||||
use crate::hash::FxIndexMap;
|
||||
use crate::lru::Lru;
|
||||
use crate::plumbing::DerivedQueryStorageOps;
|
||||
use crate::plumbing::LruQueryStorageOps;
|
||||
use crate::plumbing::QueryFunction;
|
||||
use crate::plumbing::QueryStorageMassOps;
|
||||
use crate::plumbing::QueryStorageOps;
|
||||
use crate::runtime::StampedValue;
|
||||
use crate::Runtime;
|
||||
use crate::{Database, DatabaseKeyIndex, QueryDb, Revision};
|
||||
use parking_lot::RwLock;
|
||||
use std::borrow::Borrow;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use triomphe::Arc;
|
||||
|
||||
mod slot;
|
||||
use slot::Slot;
|
||||
|
||||
/// Memoized queries store the result plus a list of the other queries
|
||||
/// that they invoked. This means we can avoid recomputing them when
|
||||
/// none of those inputs have changed.
|
||||
pub type MemoizedStorage<Q> = DerivedStorage<Q, AlwaysMemoizeValue>;
|
||||
|
||||
/// "Dependency" queries just track their dependencies and not the
|
||||
/// actual value (which they produce on demand). This lessens the
|
||||
/// storage requirements.
|
||||
pub type DependencyStorage<Q> = DerivedStorage<Q, NeverMemoizeValue>;
|
||||
|
||||
/// Handles storage where the value is 'derived' by executing a
|
||||
/// function (in contrast to "inputs").
|
||||
pub struct DerivedStorage<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
group_index: u16,
|
||||
lru_list: Lru<Slot<Q, MP>>,
|
||||
slot_map: RwLock<FxIndexMap<Q::Key, Arc<Slot<Q, MP>>>>,
|
||||
policy: PhantomData<MP>,
|
||||
}
|
||||
|
||||
impl<Q, MP> std::panic::RefUnwindSafe for DerivedStorage<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
Q::Key: std::panic::RefUnwindSafe,
|
||||
Q::Value: std::panic::RefUnwindSafe,
|
||||
{
|
||||
}
|
||||
|
||||
pub trait MemoizationPolicy<Q>: Send + Sync
|
||||
where
|
||||
Q: QueryFunction,
|
||||
{
|
||||
fn should_memoize_value(key: &Q::Key) -> bool;
|
||||
|
||||
fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool;
|
||||
}
|
||||
|
||||
pub enum AlwaysMemoizeValue {}
|
||||
impl<Q> MemoizationPolicy<Q> for AlwaysMemoizeValue
|
||||
where
|
||||
Q: QueryFunction,
|
||||
Q::Value: Eq,
|
||||
{
|
||||
fn should_memoize_value(_key: &Q::Key) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool {
|
||||
old_value == new_value
|
||||
}
|
||||
}
|
||||
|
||||
pub enum NeverMemoizeValue {}
|
||||
impl<Q> MemoizationPolicy<Q> for NeverMemoizeValue
|
||||
where
|
||||
Q: QueryFunction,
|
||||
{
|
||||
fn should_memoize_value(_key: &Q::Key) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn memoized_value_eq(_old_value: &Q::Value, _new_value: &Q::Value) -> bool {
|
||||
panic!("cannot reach since we never memoize")
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> DerivedStorage<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn slot(&self, key: &Q::Key) -> Arc<Slot<Q, MP>> {
|
||||
if let Some(v) = self.slot_map.read().get(key) {
|
||||
return v.clone();
|
||||
}
|
||||
|
||||
let mut write = self.slot_map.write();
|
||||
let entry = write.entry(key.clone());
|
||||
let key_index = entry.index() as u32;
|
||||
let database_key_index = DatabaseKeyIndex {
|
||||
group_index: self.group_index,
|
||||
query_index: Q::QUERY_INDEX,
|
||||
key_index,
|
||||
};
|
||||
entry.or_insert_with(|| Arc::new(Slot::new(database_key_index))).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> QueryStorageOps<Q> for DerivedStorage<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = Q::CYCLE_STRATEGY;
|
||||
|
||||
fn new(group_index: u16) -> Self {
|
||||
DerivedStorage {
|
||||
group_index,
|
||||
slot_map: RwLock::new(FxIndexMap::default()),
|
||||
lru_list: Default::default(),
|
||||
policy: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_index(
|
||||
&self,
|
||||
_db: &<Q as QueryDb<'_>>::DynDb,
|
||||
index: u32,
|
||||
fmt: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result {
|
||||
let slot_map = self.slot_map.read();
|
||||
let key = slot_map.get_index(index as usize).unwrap().0;
|
||||
write!(fmt, "{}::{}({:?})", std::any::type_name::<Q>(), Q::QUERY_NAME, key)
|
||||
}
|
||||
|
||||
fn maybe_changed_after(
|
||||
&self,
|
||||
db: &<Q as QueryDb<'_>>::DynDb,
|
||||
index: u32,
|
||||
revision: Revision,
|
||||
) -> bool {
|
||||
debug_assert!(revision < db.salsa_runtime().current_revision());
|
||||
let (key, slot) = {
|
||||
let read = self.slot_map.read();
|
||||
let Some((key, slot)) = read.get_index(index as usize) else {
|
||||
return false;
|
||||
};
|
||||
(key.clone(), slot.clone())
|
||||
};
|
||||
slot.maybe_changed_after(db, revision, &key)
|
||||
}
|
||||
|
||||
fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Q::Value {
|
||||
db.unwind_if_cancelled();
|
||||
|
||||
let slot = self.slot(key);
|
||||
let StampedValue { value, durability, changed_at } = slot.read(db, key);
|
||||
|
||||
if let Some(evicted) = self.lru_list.record_use(&slot) {
|
||||
evicted.evict();
|
||||
}
|
||||
|
||||
db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
|
||||
slot.database_key_index(),
|
||||
durability,
|
||||
changed_at,
|
||||
);
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
fn durability(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Durability {
|
||||
self.slot(key).durability(db)
|
||||
}
|
||||
|
||||
fn entries<C>(&self, _db: &<Q as QueryDb<'_>>::DynDb) -> C
|
||||
where
|
||||
C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
|
||||
{
|
||||
let slot_map = self.slot_map.read();
|
||||
slot_map.iter().filter_map(|(key, slot)| slot.as_table_entry(key)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> QueryStorageMassOps for DerivedStorage<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn purge(&self) {
|
||||
self.lru_list.purge();
|
||||
*self.slot_map.write() = Default::default();
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> LruQueryStorageOps for DerivedStorage<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn set_lru_capacity(&self, new_capacity: u16) {
|
||||
self.lru_list.set_lru_capacity(new_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> DerivedQueryStorageOps<Q> for DerivedStorage<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn invalidate<S>(&self, runtime: &mut Runtime, key: &S)
|
||||
where
|
||||
S: Eq + Hash,
|
||||
Q::Key: Borrow<S>,
|
||||
{
|
||||
runtime.with_incremented_revision(|new_revision| {
|
||||
let map_read = self.slot_map.read();
|
||||
|
||||
if let Some(slot) = map_read.get(key) {
|
||||
if let Some(durability) = slot.invalidate(new_revision) {
|
||||
return Some(durability);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
845
crates/salsa/src/derived_lru/slot.rs
Normal file
845
crates/salsa/src/derived_lru/slot.rs
Normal file
|
@ -0,0 +1,845 @@
|
|||
use crate::debug::TableEntry;
|
||||
use crate::derived_lru::MemoizationPolicy;
|
||||
use crate::durability::Durability;
|
||||
use crate::lru::LruIndex;
|
||||
use crate::lru::LruNode;
|
||||
use crate::plumbing::{DatabaseOps, QueryFunction};
|
||||
use crate::revision::Revision;
|
||||
use crate::runtime::local_state::ActiveQueryGuard;
|
||||
use crate::runtime::local_state::QueryRevisions;
|
||||
use crate::runtime::Runtime;
|
||||
use crate::runtime::RuntimeId;
|
||||
use crate::runtime::StampedValue;
|
||||
use crate::runtime::WaitResult;
|
||||
use crate::Cycle;
|
||||
use crate::{Database, DatabaseKeyIndex, Event, EventKind, QueryDb};
|
||||
use parking_lot::{RawRwLock, RwLock};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tracing::{debug, info};
|
||||
|
||||
pub(super) struct Slot<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
key_index: u32,
|
||||
group_index: u16,
|
||||
state: RwLock<QueryState<Q>>,
|
||||
lru_index: LruIndex,
|
||||
policy: PhantomData<MP>,
|
||||
}
|
||||
|
||||
/// Defines the "current state" of query's memoized results.
|
||||
enum QueryState<Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
{
|
||||
NotComputed,
|
||||
|
||||
/// The runtime with the given id is currently computing the
|
||||
/// result of this query.
|
||||
InProgress {
|
||||
id: RuntimeId,
|
||||
|
||||
/// Set to true if any other queries are blocked,
|
||||
/// waiting for this query to complete.
|
||||
anyone_waiting: AtomicBool,
|
||||
},
|
||||
|
||||
/// We have computed the query already, and here is the result.
|
||||
Memoized(Memo<Q::Value>),
|
||||
}
|
||||
|
||||
struct Memo<V> {
|
||||
/// The result of the query, if we decide to memoize it.
|
||||
value: Option<V>,
|
||||
|
||||
/// Last revision when this memo was verified; this begins
|
||||
/// as the current revision.
|
||||
pub(crate) verified_at: Revision,
|
||||
|
||||
/// Revision information
|
||||
revisions: QueryRevisions,
|
||||
}
|
||||
|
||||
/// Return value of `probe` helper.
|
||||
enum ProbeState<V, G> {
|
||||
/// Another thread was active but has completed.
|
||||
/// Try again!
|
||||
Retry,
|
||||
|
||||
/// No entry for this key at all.
|
||||
NotComputed(G),
|
||||
|
||||
/// There is an entry, but its contents have not been
|
||||
/// verified in this revision.
|
||||
Stale(G),
|
||||
|
||||
/// There is an entry, and it has been verified
|
||||
/// in this revision, but it has no cached
|
||||
/// value. The `Revision` is the revision where the
|
||||
/// value last changed (if we were to recompute it).
|
||||
NoValue(G, Revision),
|
||||
|
||||
/// There is an entry which has been verified,
|
||||
/// and it has the following value-- or, we blocked
|
||||
/// on another thread, and that resulted in a cycle.
|
||||
UpToDate(V),
|
||||
}
|
||||
|
||||
/// Return value of `maybe_changed_after_probe` helper.
|
||||
enum MaybeChangedSinceProbeState<G> {
|
||||
/// Another thread was active but has completed.
|
||||
/// Try again!
|
||||
Retry,
|
||||
|
||||
/// Value may have changed in the given revision.
|
||||
ChangedAt(Revision),
|
||||
|
||||
/// There is a stale cache entry that has not been
|
||||
/// verified in this revision, so we can't say.
|
||||
Stale(G),
|
||||
}
|
||||
|
||||
impl<Q, MP> Slot<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
pub(super) fn new(database_key_index: DatabaseKeyIndex) -> Self {
|
||||
Self {
|
||||
key_index: database_key_index.key_index,
|
||||
group_index: database_key_index.group_index,
|
||||
state: RwLock::new(QueryState::NotComputed),
|
||||
lru_index: LruIndex::default(),
|
||||
policy: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn database_key_index(&self) -> DatabaseKeyIndex {
|
||||
DatabaseKeyIndex {
|
||||
group_index: self.group_index,
|
||||
query_index: Q::QUERY_INDEX,
|
||||
key_index: self.key_index,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn read(
|
||||
&self,
|
||||
db: &<Q as QueryDb<'_>>::DynDb,
|
||||
key: &Q::Key,
|
||||
) -> StampedValue<Q::Value> {
|
||||
let runtime = db.salsa_runtime();
|
||||
|
||||
// NB: We don't need to worry about people modifying the
|
||||
// revision out from under our feet. Either `db` is a frozen
|
||||
// database, in which case there is a lock, or the mutator
|
||||
// thread is the current thread, and it will be prevented from
|
||||
// doing any `set` invocations while the query function runs.
|
||||
let revision_now = runtime.current_revision();
|
||||
|
||||
info!("{:?}: invoked at {:?}", self, revision_now,);
|
||||
|
||||
// First, do a check with a read-lock.
|
||||
loop {
|
||||
match self.probe(db, self.state.read(), runtime, revision_now) {
|
||||
ProbeState::UpToDate(v) => return v,
|
||||
ProbeState::Stale(..) | ProbeState::NoValue(..) | ProbeState::NotComputed(..) => {
|
||||
break
|
||||
}
|
||||
ProbeState::Retry => continue,
|
||||
}
|
||||
}
|
||||
|
||||
self.read_upgrade(db, key, revision_now)
|
||||
}
|
||||
|
||||
/// Second phase of a read operation: acquires an upgradable-read
|
||||
/// and -- if needed -- validates whether inputs have changed,
|
||||
/// recomputes value, etc. This is invoked after our initial probe
|
||||
/// shows a potentially out of date value.
|
||||
fn read_upgrade(
|
||||
&self,
|
||||
db: &<Q as QueryDb<'_>>::DynDb,
|
||||
key: &Q::Key,
|
||||
revision_now: Revision,
|
||||
) -> StampedValue<Q::Value> {
|
||||
let runtime = db.salsa_runtime();
|
||||
|
||||
debug!("{:?}: read_upgrade(revision_now={:?})", self, revision_now,);
|
||||
|
||||
// Check with an upgradable read to see if there is a value
|
||||
// already. (This permits other readers but prevents anyone
|
||||
// else from running `read_upgrade` at the same time.)
|
||||
let mut old_memo = loop {
|
||||
match self.probe(db, self.state.upgradable_read(), runtime, revision_now) {
|
||||
ProbeState::UpToDate(v) => return v,
|
||||
ProbeState::Stale(state)
|
||||
| ProbeState::NotComputed(state)
|
||||
| ProbeState::NoValue(state, _) => {
|
||||
type RwLockUpgradableReadGuard<'a, T> =
|
||||
lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>;
|
||||
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) {
|
||||
QueryState::Memoized(old_memo) => break Some(old_memo),
|
||||
QueryState::InProgress { .. } => unreachable!(),
|
||||
QueryState::NotComputed => break None,
|
||||
}
|
||||
}
|
||||
ProbeState::Retry => continue,
|
||||
}
|
||||
};
|
||||
|
||||
let panic_guard = PanicGuard::new(self, runtime);
|
||||
let active_query = runtime.push_query(self.database_key_index());
|
||||
|
||||
// If we have an old-value, it *may* now be stale, since there
|
||||
// has been a new revision since the last time we checked. So,
|
||||
// first things first, let's walk over each of our previous
|
||||
// inputs and check whether they are out of date.
|
||||
if let Some(memo) = &mut old_memo {
|
||||
if let Some(value) = memo.verify_value(db.ops_database(), revision_now, &active_query) {
|
||||
info!("{:?}: validated old memoized value", self,);
|
||||
|
||||
db.salsa_event(Event {
|
||||
runtime_id: runtime.id(),
|
||||
kind: EventKind::DidValidateMemoizedValue {
|
||||
database_key: self.database_key_index(),
|
||||
},
|
||||
});
|
||||
|
||||
panic_guard.proceed(old_memo);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
self.execute(db, runtime, revision_now, active_query, panic_guard, old_memo, key)
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
db: &<Q as QueryDb<'_>>::DynDb,
|
||||
runtime: &Runtime,
|
||||
revision_now: Revision,
|
||||
active_query: ActiveQueryGuard<'_>,
|
||||
panic_guard: PanicGuard<'_, Q, MP>,
|
||||
old_memo: Option<Memo<Q::Value>>,
|
||||
key: &Q::Key,
|
||||
) -> StampedValue<Q::Value> {
|
||||
tracing::info!("{:?}: executing query", self.database_key_index().debug(db));
|
||||
|
||||
db.salsa_event(Event {
|
||||
runtime_id: db.salsa_runtime().id(),
|
||||
kind: EventKind::WillExecute { database_key: self.database_key_index() },
|
||||
});
|
||||
|
||||
// Query was not previously executed, or value is potentially
|
||||
// stale, or value is absent. Let's execute!
|
||||
let value = match Cycle::catch(|| Q::execute(db, key.clone())) {
|
||||
Ok(v) => v,
|
||||
Err(cycle) => {
|
||||
tracing::debug!(
|
||||
"{:?}: caught cycle {:?}, have strategy {:?}",
|
||||
self.database_key_index().debug(db),
|
||||
cycle,
|
||||
Q::CYCLE_STRATEGY,
|
||||
);
|
||||
match Q::CYCLE_STRATEGY {
|
||||
crate::plumbing::CycleRecoveryStrategy::Panic => {
|
||||
panic_guard.proceed(None);
|
||||
cycle.throw()
|
||||
}
|
||||
crate::plumbing::CycleRecoveryStrategy::Fallback => {
|
||||
if let Some(c) = active_query.take_cycle() {
|
||||
assert!(c.is(&cycle));
|
||||
Q::cycle_fallback(db, &cycle, key)
|
||||
} else {
|
||||
// we are not a participant in this cycle
|
||||
debug_assert!(!cycle
|
||||
.participant_keys()
|
||||
.any(|k| k == self.database_key_index()));
|
||||
cycle.throw()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut revisions = active_query.pop();
|
||||
|
||||
// We assume that query is side-effect free -- that is, does
|
||||
// not mutate the "inputs" to the query system. Sanity check
|
||||
// that assumption here, at least to the best of our ability.
|
||||
assert_eq!(
|
||||
runtime.current_revision(),
|
||||
revision_now,
|
||||
"revision altered during query execution",
|
||||
);
|
||||
|
||||
// If the new value is equal to the old one, then it didn't
|
||||
// really change, even if some of its inputs have. So we can
|
||||
// "backdate" its `changed_at` revision to be the same as the
|
||||
// old value.
|
||||
if let Some(old_memo) = &old_memo {
|
||||
if let Some(old_value) = &old_memo.value {
|
||||
// Careful: if the value became less durable than it
|
||||
// used to be, that is a "breaking change" that our
|
||||
// consumers must be aware of. Becoming *more* durable
|
||||
// is not. See the test `constant_to_non_constant`.
|
||||
if revisions.durability >= old_memo.revisions.durability
|
||||
&& MP::memoized_value_eq(old_value, &value)
|
||||
{
|
||||
debug!(
|
||||
"read_upgrade({:?}): value is equal, back-dating to {:?}",
|
||||
self, old_memo.revisions.changed_at,
|
||||
);
|
||||
|
||||
assert!(old_memo.revisions.changed_at <= revisions.changed_at);
|
||||
revisions.changed_at = old_memo.revisions.changed_at;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let new_value = StampedValue {
|
||||
value,
|
||||
durability: revisions.durability,
|
||||
changed_at: revisions.changed_at,
|
||||
};
|
||||
|
||||
let memo_value =
|
||||
if self.should_memoize_value(key) { Some(new_value.value.clone()) } else { None };
|
||||
|
||||
debug!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,);
|
||||
|
||||
panic_guard.proceed(Some(Memo { value: memo_value, verified_at: revision_now, revisions }));
|
||||
|
||||
new_value
|
||||
}
|
||||
|
||||
/// Helper for `read` that does a shallow check (not recursive) if we have an up-to-date value.
|
||||
///
|
||||
/// Invoked with the guard `state` corresponding to the `QueryState` of some `Slot` (the guard
|
||||
/// can be either read or write). Returns a suitable `ProbeState`:
|
||||
///
|
||||
/// - `ProbeState::UpToDate(r)` if the table has an up-to-date value (or we blocked on another
|
||||
/// thread that produced such a value).
|
||||
/// - `ProbeState::StaleOrAbsent(g)` if either (a) there is no memo for this key, (b) the memo
|
||||
/// has no value; or (c) the memo has not been verified at the current revision.
|
||||
///
|
||||
/// Note that in case `ProbeState::UpToDate`, the lock will have been released.
|
||||
fn probe<StateGuard>(
|
||||
&self,
|
||||
db: &<Q as QueryDb<'_>>::DynDb,
|
||||
state: StateGuard,
|
||||
runtime: &Runtime,
|
||||
revision_now: Revision,
|
||||
) -> ProbeState<StampedValue<Q::Value>, StateGuard>
|
||||
where
|
||||
StateGuard: Deref<Target = QueryState<Q>>,
|
||||
{
|
||||
match &*state {
|
||||
QueryState::NotComputed => ProbeState::NotComputed(state),
|
||||
|
||||
QueryState::InProgress { id, anyone_waiting } => {
|
||||
let other_id = *id;
|
||||
|
||||
// NB: `Ordering::Relaxed` is sufficient here,
|
||||
// as there are no loads that are "gated" on this
|
||||
// value. Everything that is written is also protected
|
||||
// by a lock that must be acquired. The role of this
|
||||
// boolean is to decide *whether* to acquire the lock,
|
||||
// not to gate future atomic reads.
|
||||
anyone_waiting.store(true, Ordering::Relaxed);
|
||||
|
||||
self.block_on_or_unwind(db, runtime, other_id, state);
|
||||
|
||||
// Other thread completely normally, so our value may be available now.
|
||||
ProbeState::Retry
|
||||
}
|
||||
|
||||
QueryState::Memoized(memo) => {
|
||||
debug!(
|
||||
"{:?}: found memoized value, verified_at={:?}, changed_at={:?}",
|
||||
self, memo.verified_at, memo.revisions.changed_at,
|
||||
);
|
||||
|
||||
if memo.verified_at < revision_now {
|
||||
return ProbeState::Stale(state);
|
||||
}
|
||||
|
||||
if let Some(value) = &memo.value {
|
||||
let value = StampedValue {
|
||||
durability: memo.revisions.durability,
|
||||
changed_at: memo.revisions.changed_at,
|
||||
value: value.clone(),
|
||||
};
|
||||
|
||||
info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at);
|
||||
|
||||
ProbeState::UpToDate(value)
|
||||
} else {
|
||||
let changed_at = memo.revisions.changed_at;
|
||||
ProbeState::NoValue(state, changed_at)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn durability(&self, db: &<Q as QueryDb<'_>>::DynDb) -> Durability {
|
||||
match &*self.state.read() {
|
||||
QueryState::NotComputed => Durability::LOW,
|
||||
QueryState::InProgress { .. } => panic!("query in progress"),
|
||||
QueryState::Memoized(memo) => {
|
||||
if memo.check_durability(db.salsa_runtime()) {
|
||||
memo.revisions.durability
|
||||
} else {
|
||||
Durability::LOW
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn as_table_entry(&self, key: &Q::Key) -> Option<TableEntry<Q::Key, Q::Value>> {
|
||||
match &*self.state.read() {
|
||||
QueryState::NotComputed => None,
|
||||
QueryState::InProgress { .. } => Some(TableEntry::new(key.clone(), None)),
|
||||
QueryState::Memoized(memo) => Some(TableEntry::new(key.clone(), memo.value.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn evict(&self) {
|
||||
let mut state = self.state.write();
|
||||
if let QueryState::Memoized(memo) = &mut *state {
|
||||
// Evicting a value with an untracked input could
|
||||
// lead to inconsistencies. Note that we can't check
|
||||
// `has_untracked_input` when we add the value to the cache,
|
||||
// because inputs can become untracked in the next revision.
|
||||
if memo.has_untracked_input() {
|
||||
return;
|
||||
}
|
||||
memo.value = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn invalidate(&self, new_revision: Revision) -> Option<Durability> {
|
||||
tracing::debug!("Slot::invalidate(new_revision = {:?})", new_revision);
|
||||
match &mut *self.state.write() {
|
||||
QueryState::Memoized(memo) => {
|
||||
memo.revisions.untracked = true;
|
||||
memo.revisions.inputs = None;
|
||||
memo.revisions.changed_at = new_revision;
|
||||
Some(memo.revisions.durability)
|
||||
}
|
||||
QueryState::NotComputed => None,
|
||||
QueryState::InProgress { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn maybe_changed_after(
|
||||
&self,
|
||||
db: &<Q as QueryDb<'_>>::DynDb,
|
||||
revision: Revision,
|
||||
key: &Q::Key,
|
||||
) -> bool {
|
||||
let runtime = db.salsa_runtime();
|
||||
let revision_now = runtime.current_revision();
|
||||
|
||||
db.unwind_if_cancelled();
|
||||
|
||||
debug!(
|
||||
"maybe_changed_after({:?}) called with revision={:?}, revision_now={:?}",
|
||||
self, revision, revision_now,
|
||||
);
|
||||
|
||||
// Do an initial probe with just the read-lock.
|
||||
//
|
||||
// If we find that a cache entry for the value is present
|
||||
// but hasn't been verified in this revision, we'll have to
|
||||
// do more.
|
||||
loop {
|
||||
match self.maybe_changed_after_probe(db, self.state.read(), runtime, revision_now) {
|
||||
MaybeChangedSinceProbeState::Retry => continue,
|
||||
MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision,
|
||||
MaybeChangedSinceProbeState::Stale(state) => {
|
||||
drop(state);
|
||||
return self.maybe_changed_after_upgrade(db, revision, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_changed_after_probe<StateGuard>(
|
||||
&self,
|
||||
db: &<Q as QueryDb<'_>>::DynDb,
|
||||
state: StateGuard,
|
||||
runtime: &Runtime,
|
||||
revision_now: Revision,
|
||||
) -> MaybeChangedSinceProbeState<StateGuard>
|
||||
where
|
||||
StateGuard: Deref<Target = QueryState<Q>>,
|
||||
{
|
||||
match self.probe(db, state, runtime, revision_now) {
|
||||
ProbeState::Retry => MaybeChangedSinceProbeState::Retry,
|
||||
|
||||
ProbeState::Stale(state) => MaybeChangedSinceProbeState::Stale(state),
|
||||
|
||||
// If we know when value last changed, we can return right away.
|
||||
// Note that we don't need the actual value to be available.
|
||||
ProbeState::NoValue(_, changed_at)
|
||||
| ProbeState::UpToDate(StampedValue { value: _, durability: _, changed_at }) => {
|
||||
MaybeChangedSinceProbeState::ChangedAt(changed_at)
|
||||
}
|
||||
|
||||
// If we have nothing cached, then value may have changed.
|
||||
ProbeState::NotComputed(_) => MaybeChangedSinceProbeState::ChangedAt(revision_now),
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_changed_after_upgrade(
|
||||
&self,
|
||||
db: &<Q as QueryDb<'_>>::DynDb,
|
||||
revision: Revision,
|
||||
key: &Q::Key,
|
||||
) -> bool {
|
||||
let runtime = db.salsa_runtime();
|
||||
let revision_now = runtime.current_revision();
|
||||
|
||||
// Get an upgradable read lock, which permits other reads but no writers.
|
||||
// Probe again. If the value is stale (needs to be verified), then upgrade
|
||||
// to a write lock and swap it with InProgress while we work.
|
||||
let mut old_memo = match self.maybe_changed_after_probe(
|
||||
db,
|
||||
self.state.upgradable_read(),
|
||||
runtime,
|
||||
revision_now,
|
||||
) {
|
||||
MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision,
|
||||
|
||||
// If another thread was active, then the cache line is going to be
|
||||
// either verified or cleared out. Just recurse to figure out which.
|
||||
// Note that we don't need an upgradable read.
|
||||
MaybeChangedSinceProbeState::Retry => {
|
||||
return self.maybe_changed_after(db, revision, key)
|
||||
}
|
||||
|
||||
MaybeChangedSinceProbeState::Stale(state) => {
|
||||
type RwLockUpgradableReadGuard<'a, T> =
|
||||
lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>;
|
||||
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) {
|
||||
QueryState::Memoized(old_memo) => old_memo,
|
||||
QueryState::NotComputed | QueryState::InProgress { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let panic_guard = PanicGuard::new(self, runtime);
|
||||
let active_query = runtime.push_query(self.database_key_index());
|
||||
|
||||
if old_memo.verify_revisions(db.ops_database(), revision_now, &active_query) {
|
||||
let maybe_changed = old_memo.revisions.changed_at > revision;
|
||||
panic_guard.proceed(Some(old_memo));
|
||||
maybe_changed
|
||||
} else if old_memo.value.is_some() {
|
||||
// We found that this memoized value may have changed
|
||||
// but we have an old value. We can re-run the code and
|
||||
// actually *check* if it has changed.
|
||||
let StampedValue { changed_at, .. } = self.execute(
|
||||
db,
|
||||
runtime,
|
||||
revision_now,
|
||||
active_query,
|
||||
panic_guard,
|
||||
Some(old_memo),
|
||||
key,
|
||||
);
|
||||
changed_at > revision
|
||||
} else {
|
||||
// We found that inputs to this memoized value may have chanced
|
||||
// but we don't have an old value to compare against or re-use.
|
||||
// No choice but to drop the memo and say that its value may have changed.
|
||||
panic_guard.proceed(None);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper: see [`Runtime::try_block_on_or_unwind`].
|
||||
fn block_on_or_unwind<MutexGuard>(
|
||||
&self,
|
||||
db: &<Q as QueryDb<'_>>::DynDb,
|
||||
runtime: &Runtime,
|
||||
other_id: RuntimeId,
|
||||
mutex_guard: MutexGuard,
|
||||
) {
|
||||
runtime.block_on_or_unwind(
|
||||
db.ops_database(),
|
||||
self.database_key_index(),
|
||||
other_id,
|
||||
mutex_guard,
|
||||
)
|
||||
}
|
||||
|
||||
fn should_memoize_value(&self, key: &Q::Key) -> bool {
|
||||
MP::should_memoize_value(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q> QueryState<Q>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
{
|
||||
fn in_progress(id: RuntimeId) -> Self {
|
||||
QueryState::InProgress { id, anyone_waiting: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
struct PanicGuard<'me, Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
slot: &'me Slot<Q, MP>,
|
||||
runtime: &'me Runtime,
|
||||
}
|
||||
|
||||
impl<'me, Q, MP> PanicGuard<'me, Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn new(slot: &'me Slot<Q, MP>, runtime: &'me Runtime) -> Self {
|
||||
Self { slot, runtime }
|
||||
}
|
||||
|
||||
/// Indicates that we have concluded normally (without panicking).
|
||||
/// If `opt_memo` is some, then this memo is installed as the new
|
||||
/// memoized value. If `opt_memo` is `None`, then the slot is cleared
|
||||
/// and has no value.
|
||||
fn proceed(mut self, opt_memo: Option<Memo<Q::Value>>) {
|
||||
self.overwrite_placeholder(WaitResult::Completed, opt_memo);
|
||||
std::mem::forget(self)
|
||||
}
|
||||
|
||||
/// Overwrites the `InProgress` placeholder for `key` that we
|
||||
/// inserted; if others were blocked, waiting for us to finish,
|
||||
/// then notify them.
|
||||
fn overwrite_placeholder(&mut self, wait_result: WaitResult, opt_memo: Option<Memo<Q::Value>>) {
|
||||
let old_value = {
|
||||
let mut write = self.slot.state.write();
|
||||
match opt_memo {
|
||||
// Replace the `InProgress` marker that we installed with the new
|
||||
// memo, thus releasing our unique access to this key.
|
||||
Some(memo) => std::mem::replace(&mut *write, QueryState::Memoized(memo)),
|
||||
|
||||
// We had installed an `InProgress` marker, but we panicked before
|
||||
// it could be removed. At this point, we therefore "own" unique
|
||||
// access to our slot, so we can just remove the key.
|
||||
None => std::mem::replace(&mut *write, QueryState::NotComputed),
|
||||
}
|
||||
};
|
||||
|
||||
match old_value {
|
||||
QueryState::InProgress { id, anyone_waiting } => {
|
||||
assert_eq!(id, self.runtime.id());
|
||||
|
||||
// NB: As noted on the `store`, `Ordering::Relaxed` is
|
||||
// sufficient here. This boolean signals us on whether to
|
||||
// acquire a mutex; the mutex will guarantee that all writes
|
||||
// we are interested in are visible.
|
||||
if anyone_waiting.load(Ordering::Relaxed) {
|
||||
self.runtime
|
||||
.unblock_queries_blocked_on(self.slot.database_key_index(), wait_result);
|
||||
}
|
||||
}
|
||||
_ => panic!(
|
||||
"\
|
||||
Unexpected panic during query evaluation, aborting the process.
|
||||
|
||||
Please report this bug to https://github.com/salsa-rs/salsa/issues."
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'me, Q, MP> Drop for PanicGuard<'me, Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if std::thread::panicking() {
|
||||
// We panicked before we could proceed and need to remove `key`.
|
||||
self.overwrite_placeholder(WaitResult::Panicked, None)
|
||||
} else {
|
||||
// If no panic occurred, then panic guard ought to be
|
||||
// "forgotten" and so this Drop code should never run.
|
||||
panic!(".forget() was not called")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Memo<V>
|
||||
where
|
||||
V: Clone,
|
||||
{
|
||||
/// Determines whether the value stored in this memo (if any) is still
|
||||
/// valid in the current revision. If so, returns a stamped value.
|
||||
///
|
||||
/// If needed, this will walk each dependency and
|
||||
/// recursively invoke `maybe_changed_after`, which may in turn
|
||||
/// re-execute the dependency. This can cause cycles to occur,
|
||||
/// so the current query must be pushed onto the
|
||||
/// stack to permit cycle detection and recovery: therefore,
|
||||
/// takes the `active_query` argument as evidence.
|
||||
fn verify_value(
|
||||
&mut self,
|
||||
db: &dyn Database,
|
||||
revision_now: Revision,
|
||||
active_query: &ActiveQueryGuard<'_>,
|
||||
) -> Option<StampedValue<V>> {
|
||||
// If we don't have a memoized value, nothing to validate.
|
||||
if self.value.is_none() {
|
||||
return None;
|
||||
}
|
||||
if self.verify_revisions(db, revision_now, active_query) {
|
||||
self.value.clone().map(|value| StampedValue {
|
||||
durability: self.revisions.durability,
|
||||
changed_at: self.revisions.changed_at,
|
||||
value,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines whether the value represented by this memo is still
|
||||
/// valid in the current revision; note that the value itself is
|
||||
/// not needed for this check. If needed, this will walk each
|
||||
/// dependency and recursively invoke `maybe_changed_after`, which
|
||||
/// may in turn re-execute the dependency. This can cause cycles to occur,
|
||||
/// so the current query must be pushed onto the
|
||||
/// stack to permit cycle detection and recovery: therefore,
|
||||
/// takes the `active_query` argument as evidence.
|
||||
fn verify_revisions(
|
||||
&mut self,
|
||||
db: &dyn Database,
|
||||
revision_now: Revision,
|
||||
_active_query: &ActiveQueryGuard<'_>,
|
||||
) -> bool {
|
||||
assert!(self.verified_at != revision_now);
|
||||
let verified_at = self.verified_at;
|
||||
|
||||
debug!(
|
||||
"verify_revisions: verified_at={:?}, revision_now={:?}, inputs={:#?}",
|
||||
verified_at, revision_now, self.revisions.inputs
|
||||
);
|
||||
|
||||
if self.check_durability(db.salsa_runtime()) {
|
||||
return self.mark_value_as_verified(revision_now);
|
||||
}
|
||||
|
||||
match &self.revisions.inputs {
|
||||
// We can't validate values that had untracked inputs; just have to
|
||||
// re-execute.
|
||||
None if self.revisions.untracked => return false,
|
||||
None => {}
|
||||
|
||||
// Check whether any of our inputs changed since the
|
||||
// **last point where we were verified** (not since we
|
||||
// last changed). This is important: if we have
|
||||
// memoized values, then an input may have changed in
|
||||
// revision R2, but we found that *our* value was the
|
||||
// same regardless, so our change date is still
|
||||
// R1. But our *verification* date will be R2, and we
|
||||
// are only interested in finding out whether the
|
||||
// input changed *again*.
|
||||
Some(inputs) => {
|
||||
let changed_input =
|
||||
inputs.slice.iter().find(|&&input| db.maybe_changed_after(input, verified_at));
|
||||
if let Some(input) = changed_input {
|
||||
debug!("validate_memoized_value: `{:?}` may have changed", input);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.mark_value_as_verified(revision_now)
|
||||
}
|
||||
|
||||
/// True if this memo is known not to have changed based on its durability.
|
||||
fn check_durability(&self, runtime: &Runtime) -> bool {
|
||||
let last_changed = runtime.last_changed_revision(self.revisions.durability);
|
||||
debug!(
|
||||
"check_durability(last_changed={:?} <= verified_at={:?}) = {:?}",
|
||||
last_changed,
|
||||
self.verified_at,
|
||||
last_changed <= self.verified_at,
|
||||
);
|
||||
last_changed <= self.verified_at
|
||||
}
|
||||
|
||||
fn mark_value_as_verified(&mut self, revision_now: Revision) -> bool {
|
||||
self.verified_at = revision_now;
|
||||
true
|
||||
}
|
||||
|
||||
fn has_untracked_input(&self) -> bool {
|
||||
self.revisions.untracked
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> std::fmt::Debug for Slot<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(fmt, "{:?}", Q::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q, MP> LruNode for Slot<Q, MP>
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
{
|
||||
fn lru_index(&self) -> &LruIndex {
|
||||
&self.lru_index
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that `Slot<Q, MP>: Send + Sync` as long as
|
||||
/// `DB::DatabaseData: Send + Sync`, which in turn implies that
|
||||
/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`.
|
||||
#[allow(dead_code)]
|
||||
fn check_send_sync<Q, MP>()
|
||||
where
|
||||
Q: QueryFunction,
|
||||
MP: MemoizationPolicy<Q>,
|
||||
Q::Key: Send + Sync,
|
||||
Q::Value: Send + Sync,
|
||||
{
|
||||
fn is_send_sync<T: Send + Sync>() {}
|
||||
is_send_sync::<Slot<Q, MP>>();
|
||||
}
|
||||
|
||||
/// Check that `Slot<Q, MP>: 'static` as long as
|
||||
/// `DB::DatabaseData: 'static`, which in turn implies that
|
||||
/// `Q::Key: 'static`, `Q::Value: 'static`.
|
||||
#[allow(dead_code)]
|
||||
fn check_static<Q, MP>()
|
||||
where
|
||||
Q: QueryFunction + 'static,
|
||||
MP: MemoizationPolicy<Q> + 'static,
|
||||
Q::Key: 'static,
|
||||
Q::Value: 'static,
|
||||
{
|
||||
fn is_static<T: 'static>() {}
|
||||
is_static::<Slot<Q, MP>>();
|
||||
}
|
|
@ -121,8 +121,8 @@ impl InternValueTrivial for String {}
|
|||
|
||||
#[derive(Debug)]
|
||||
struct Slot<V> {
|
||||
/// DatabaseKeyIndex for this slot.
|
||||
database_key_index: DatabaseKeyIndex,
|
||||
/// key index for this slot.
|
||||
key_index: u32,
|
||||
|
||||
/// Value that was interned.
|
||||
value: V,
|
||||
|
@ -199,13 +199,8 @@ where
|
|||
};
|
||||
|
||||
let create_slot = |index: InternId| {
|
||||
let database_key_index = DatabaseKeyIndex {
|
||||
group_index: self.group_index,
|
||||
query_index: Q::QUERY_INDEX,
|
||||
key_index: index.as_u32(),
|
||||
};
|
||||
Arc::new(Slot {
|
||||
database_key_index,
|
||||
key_index: index.as_u32(),
|
||||
value: insert(Q::Value::from_intern_id(index)),
|
||||
interned_at: revision_now,
|
||||
})
|
||||
|
@ -242,7 +237,11 @@ where
|
|||
};
|
||||
let changed_at = slot.interned_at;
|
||||
db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
|
||||
slot.database_key_index,
|
||||
DatabaseKeyIndex {
|
||||
group_index: self.group_index,
|
||||
query_index: Q::QUERY_INDEX,
|
||||
key_index: slot.key_index,
|
||||
},
|
||||
INTERN_DURABILITY,
|
||||
changed_at,
|
||||
);
|
||||
|
@ -294,7 +293,11 @@ where
|
|||
};
|
||||
let changed_at = slot.interned_at;
|
||||
db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
|
||||
slot.database_key_index,
|
||||
DatabaseKeyIndex {
|
||||
group_index: self.group_index,
|
||||
query_index: Q::QUERY_INDEX,
|
||||
key_index: slot.key_index,
|
||||
},
|
||||
INTERN_DURABILITY,
|
||||
changed_at,
|
||||
);
|
||||
|
@ -414,7 +417,11 @@ where
|
|||
let value = slot.value.clone();
|
||||
let interned_at = slot.interned_at;
|
||||
db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
|
||||
slot.database_key_index,
|
||||
DatabaseKeyIndex {
|
||||
group_index: interned_storage.group_index,
|
||||
query_index: Q::QUERY_INDEX,
|
||||
key_index: slot.key_index,
|
||||
},
|
||||
INTERN_DURABILITY,
|
||||
interned_at,
|
||||
);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//! from previous invocations as appropriate.
|
||||
|
||||
mod derived;
|
||||
mod derived_lru;
|
||||
mod durability;
|
||||
mod hash;
|
||||
mod input;
|
||||
|
@ -577,7 +578,7 @@ where
|
|||
/// cost of potential extra recalculations of evicted values.
|
||||
///
|
||||
/// If `cap` is zero, all values are preserved, this is the default.
|
||||
pub fn set_lru_capacity(&self, cap: usize)
|
||||
pub fn set_lru_capacity(&self, cap: u16)
|
||||
where
|
||||
Q::Storage: plumbing::LruQueryStorageOps,
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use oorandom::Rand64;
|
||||
use parking_lot::Mutex;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::AtomicU16;
|
||||
use std::sync::atomic::Ordering;
|
||||
use triomphe::Arc;
|
||||
|
||||
|
@ -20,15 +20,15 @@ pub(crate) struct Lru<Node>
|
|||
where
|
||||
Node: LruNode,
|
||||
{
|
||||
green_zone: AtomicUsize,
|
||||
green_zone: AtomicU16,
|
||||
data: Mutex<LruData<Node>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LruData<Node> {
|
||||
end_red_zone: usize,
|
||||
end_yellow_zone: usize,
|
||||
end_green_zone: usize,
|
||||
end_red_zone: u16,
|
||||
end_yellow_zone: u16,
|
||||
end_green_zone: u16,
|
||||
rng: Rand64,
|
||||
entries: Vec<Arc<Node>>,
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ pub(crate) trait LruNode: Sized + Debug {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LruIndex {
|
||||
/// Index in the appropriate LRU list, or std::usize::MAX if not a
|
||||
/// Index in the appropriate LRU list, or std::u16::MAX if not a
|
||||
/// member.
|
||||
index: AtomicUsize,
|
||||
index: AtomicU16,
|
||||
}
|
||||
|
||||
impl<Node> Default for Lru<Node>
|
||||
|
@ -68,12 +68,12 @@ where
|
|||
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
fn with_seed(seed: &str) -> Self {
|
||||
Lru { green_zone: AtomicUsize::new(0), data: Mutex::new(LruData::with_seed(seed)) }
|
||||
Lru { green_zone: AtomicU16::new(0), data: Mutex::new(LruData::with_seed(seed)) }
|
||||
}
|
||||
|
||||
/// Adjust the total number of nodes permitted to have a value at
|
||||
/// once. If `len` is zero, this disables LRU caching completely.
|
||||
pub(crate) fn set_lru_capacity(&self, len: usize) {
|
||||
pub(crate) fn set_lru_capacity(&self, len: u16) {
|
||||
let mut data = self.data.lock();
|
||||
|
||||
// We require each zone to have at least 1 slot. Therefore,
|
||||
|
@ -143,23 +143,24 @@ where
|
|||
LruData { end_yellow_zone: 0, end_green_zone: 0, end_red_zone: 0, entries: Vec::new(), rng }
|
||||
}
|
||||
|
||||
fn green_zone(&self) -> std::ops::Range<usize> {
|
||||
fn green_zone(&self) -> std::ops::Range<u16> {
|
||||
0..self.end_green_zone
|
||||
}
|
||||
|
||||
fn yellow_zone(&self) -> std::ops::Range<usize> {
|
||||
fn yellow_zone(&self) -> std::ops::Range<u16> {
|
||||
self.end_green_zone..self.end_yellow_zone
|
||||
}
|
||||
|
||||
fn red_zone(&self) -> std::ops::Range<usize> {
|
||||
fn red_zone(&self) -> std::ops::Range<u16> {
|
||||
self.end_yellow_zone..self.end_red_zone
|
||||
}
|
||||
|
||||
fn resize(&mut self, len_green_zone: usize, len_yellow_zone: usize, len_red_zone: usize) {
|
||||
fn resize(&mut self, len_green_zone: u16, len_yellow_zone: u16, len_red_zone: u16) {
|
||||
self.end_green_zone = len_green_zone;
|
||||
self.end_yellow_zone = self.end_green_zone + len_yellow_zone;
|
||||
self.end_red_zone = self.end_yellow_zone + len_red_zone;
|
||||
let entries = std::mem::replace(&mut self.entries, Vec::with_capacity(self.end_red_zone));
|
||||
let entries =
|
||||
std::mem::replace(&mut self.entries, Vec::with_capacity(self.end_red_zone as usize));
|
||||
|
||||
tracing::debug!("green_zone = {:?}", self.green_zone());
|
||||
tracing::debug!("yellow_zone = {:?}", self.yellow_zone());
|
||||
|
@ -207,7 +208,7 @@ where
|
|||
|
||||
// Easy case: we still have capacity. Push it, and then promote
|
||||
// it up to the appropriate zone.
|
||||
let len = self.entries.len();
|
||||
let len = self.entries.len() as u16;
|
||||
if len < self.end_red_zone {
|
||||
self.entries.push(node.clone());
|
||||
node.lru_index().store(len);
|
||||
|
@ -218,7 +219,7 @@ where
|
|||
// Harder case: no capacity. Create some by evicting somebody from red
|
||||
// zone and then promoting.
|
||||
let victim_index = self.pick_index(self.red_zone());
|
||||
let victim_node = std::mem::replace(&mut self.entries[victim_index], node.clone());
|
||||
let victim_node = std::mem::replace(&mut self.entries[victim_index as usize], node.clone());
|
||||
tracing::debug!("evicting red node {:?} from {}", victim_node, victim_index);
|
||||
victim_node.lru_index().clear();
|
||||
self.promote_red_to_green(node, victim_index);
|
||||
|
@ -231,7 +232,7 @@ where
|
|||
///
|
||||
/// NB: It is not required that `node.lru_index()` is up-to-date
|
||||
/// when entering this method.
|
||||
fn promote_red_to_green(&mut self, node: &Arc<Node>, red_index: usize) {
|
||||
fn promote_red_to_green(&mut self, node: &Arc<Node>, red_index: u16) {
|
||||
debug_assert!(self.red_zone().contains(&red_index));
|
||||
|
||||
// Pick a yellow at random and switch places with it.
|
||||
|
@ -242,12 +243,12 @@ where
|
|||
let yellow_index = self.pick_index(self.yellow_zone());
|
||||
tracing::debug!(
|
||||
"demoting yellow node {:?} from {} to red at {}",
|
||||
self.entries[yellow_index],
|
||||
self.entries[yellow_index as usize],
|
||||
yellow_index,
|
||||
red_index,
|
||||
);
|
||||
self.entries.swap(yellow_index, red_index);
|
||||
self.entries[red_index].lru_index().store(red_index);
|
||||
self.entries.swap(yellow_index as usize, red_index as usize);
|
||||
self.entries[red_index as usize].lru_index().store(red_index);
|
||||
|
||||
// Now move ourselves up into the green zone.
|
||||
self.promote_yellow_to_green(node, yellow_index);
|
||||
|
@ -259,51 +260,51 @@ where
|
|||
///
|
||||
/// NB: It is not required that `node.lru_index()` is up-to-date
|
||||
/// when entering this method.
|
||||
fn promote_yellow_to_green(&mut self, node: &Arc<Node>, yellow_index: usize) {
|
||||
fn promote_yellow_to_green(&mut self, node: &Arc<Node>, yellow_index: u16) {
|
||||
debug_assert!(self.yellow_zone().contains(&yellow_index));
|
||||
|
||||
// Pick a yellow at random and switch places with it.
|
||||
let green_index = self.pick_index(self.green_zone());
|
||||
tracing::debug!(
|
||||
"demoting green node {:?} from {} to yellow at {}",
|
||||
self.entries[green_index],
|
||||
self.entries[green_index as usize],
|
||||
green_index,
|
||||
yellow_index
|
||||
);
|
||||
self.entries.swap(green_index, yellow_index);
|
||||
self.entries[yellow_index].lru_index().store(yellow_index);
|
||||
self.entries.swap(green_index as usize, yellow_index as usize);
|
||||
self.entries[yellow_index as usize].lru_index().store(yellow_index);
|
||||
node.lru_index().store(green_index);
|
||||
|
||||
tracing::debug!("promoted {:?} to green index {}", node, green_index);
|
||||
}
|
||||
|
||||
fn pick_index(&mut self, zone: std::ops::Range<usize>) -> usize {
|
||||
let end_index = std::cmp::min(zone.end, self.entries.len());
|
||||
self.rng.rand_range(zone.start as u64..end_index as u64) as usize
|
||||
fn pick_index(&mut self, zone: std::ops::Range<u16>) -> u16 {
|
||||
let end_index = std::cmp::min(zone.end, self.entries.len() as u16);
|
||||
self.rng.rand_range(zone.start as u64..end_index as u64) as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LruIndex {
|
||||
fn default() -> Self {
|
||||
Self { index: AtomicUsize::new(usize::MAX) }
|
||||
Self { index: AtomicU16::new(u16::MAX) }
|
||||
}
|
||||
}
|
||||
|
||||
impl LruIndex {
|
||||
fn load(&self) -> usize {
|
||||
fn load(&self) -> u16 {
|
||||
self.index.load(Ordering::Acquire) // see note on ordering below
|
||||
}
|
||||
|
||||
fn store(&self, value: usize) {
|
||||
fn store(&self, value: u16) {
|
||||
self.index.store(value, Ordering::Release) // see note on ordering below
|
||||
}
|
||||
|
||||
fn clear(&self) {
|
||||
self.store(usize::MAX);
|
||||
self.store(u16::MAX);
|
||||
}
|
||||
|
||||
fn is_in_lru(&self) -> bool {
|
||||
self.load() != usize::MAX
|
||||
self.load() != u16::MAX
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,9 @@ use std::fmt::Debug;
|
|||
use std::hash::Hash;
|
||||
use triomphe::Arc;
|
||||
|
||||
pub use crate::derived::DependencyStorage;
|
||||
pub use crate::derived::MemoizedStorage;
|
||||
pub use crate::derived_lru::DependencyStorage as LruDependencyStorage;
|
||||
pub use crate::derived_lru::MemoizedStorage as LruMemoizedStorage;
|
||||
pub use crate::input::{InputStorage, UnitInputStorage};
|
||||
pub use crate::interned::InternedStorage;
|
||||
pub use crate::interned::LookupInternedStorage;
|
||||
|
@ -228,7 +229,7 @@ where
|
|||
/// that is, storage whose value is not derived from other storage but
|
||||
/// is set independently.
|
||||
pub trait LruQueryStorageOps {
|
||||
fn set_lru_capacity(&self, new_capacity: usize);
|
||||
fn set_lru_capacity(&self, new_capacity: u16);
|
||||
}
|
||||
|
||||
pub trait DerivedQueryStorageOps<Q>
|
||||
|
|
|
@ -24,7 +24,9 @@ impl Drop for HotPotato {
|
|||
|
||||
#[salsa::query_group(QueryGroupStorage)]
|
||||
trait QueryGroup: salsa::Database {
|
||||
#[salsa::lru]
|
||||
fn get(&self, x: u32) -> Arc<HotPotato>;
|
||||
#[salsa::lru]
|
||||
fn get_volatile(&self, x: u32) -> usize;
|
||||
}
|
||||
|
||||
|
|
|
@ -2252,7 +2252,8 @@
|
|||
"null",
|
||||
"integer"
|
||||
],
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"maximum": 65535
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue