Vendor things
This commit is contained in:
parent
5deceec006
commit
977e3c17e5
19434 changed files with 10682014 additions and 0 deletions
172
third-party/vendor/salsa-2022/src/accumulator.rs
vendored
Normal file
172
third-party/vendor/salsa-2022/src/accumulator.rs
vendored
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
//! Basic test of accumulator functionality.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use crate::{
|
||||
cycle::CycleRecoveryStrategy,
|
||||
hash::FxDashMap,
|
||||
ingredient::{fmt_index, Ingredient, IngredientRequiresReset},
|
||||
key::DependencyIndex,
|
||||
runtime::local_state::QueryOrigin,
|
||||
storage::HasJar,
|
||||
DatabaseKeyIndex, Event, EventKind, IngredientIndex, Revision, Runtime,
|
||||
};
|
||||
|
||||
pub trait Accumulator {
|
||||
type Data: Clone;
|
||||
type Jar;
|
||||
|
||||
fn accumulator_ingredient<Db>(db: &Db) -> &AccumulatorIngredient<Self::Data>
|
||||
where
|
||||
Db: ?Sized + HasJar<Self::Jar>;
|
||||
}
|
||||
pub struct AccumulatorIngredient<Data: Clone> {
|
||||
index: IngredientIndex,
|
||||
map: FxDashMap<DatabaseKeyIndex, AccumulatedValues<Data>>,
|
||||
debug_name: &'static str,
|
||||
}
|
||||
|
||||
struct AccumulatedValues<Data> {
|
||||
produced_at: Revision,
|
||||
values: Vec<Data>,
|
||||
}
|
||||
|
||||
impl<Data: Clone> AccumulatorIngredient<Data> {
|
||||
pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self {
|
||||
Self {
|
||||
map: FxDashMap::default(),
|
||||
index,
|
||||
debug_name,
|
||||
}
|
||||
}
|
||||
|
||||
fn dependency_index(&self) -> DependencyIndex {
|
||||
DependencyIndex {
|
||||
ingredient_index: self.index,
|
||||
key_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&self, runtime: &Runtime, value: Data) {
|
||||
let current_revision = runtime.current_revision();
|
||||
let (active_query, _) = match runtime.active_query() {
|
||||
Some(pair) => pair,
|
||||
None => {
|
||||
panic!("cannot accumulate values outside of an active query")
|
||||
}
|
||||
};
|
||||
|
||||
let mut accumulated_values = self.map.entry(active_query).or_insert(AccumulatedValues {
|
||||
values: vec![],
|
||||
produced_at: current_revision,
|
||||
});
|
||||
|
||||
// When we call `push' in a query, we will add the accumulator to the output of the query.
|
||||
// If we find here that this accumulator is not the output of the query,
|
||||
// we can say that the accumulated values we stored for this query is out of date.
|
||||
if !runtime.is_output_of_active_query(self.dependency_index()) {
|
||||
accumulated_values.values.truncate(0);
|
||||
accumulated_values.produced_at = current_revision;
|
||||
}
|
||||
|
||||
runtime.add_output(self.dependency_index());
|
||||
accumulated_values.values.push(value);
|
||||
}
|
||||
|
||||
pub(crate) fn produced_by(
|
||||
&self,
|
||||
runtime: &Runtime,
|
||||
query: DatabaseKeyIndex,
|
||||
output: &mut Vec<Data>,
|
||||
) {
|
||||
let current_revision = runtime.current_revision();
|
||||
if let Some(v) = self.map.get(&query) {
|
||||
// FIXME: We don't currently have a good way to identify the value that was read.
|
||||
// You can't report is as a tracked read of `query`, because the return value of query is not being read here --
|
||||
// instead it is the set of values accumuated by `query`.
|
||||
runtime.report_untracked_read();
|
||||
|
||||
let AccumulatedValues {
|
||||
values,
|
||||
produced_at,
|
||||
} = v.value();
|
||||
|
||||
if *produced_at == current_revision {
|
||||
output.extend(values.iter().cloned());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB: ?Sized, Data> Ingredient<DB> for AccumulatorIngredient<Data>
|
||||
where
|
||||
DB: crate::Database,
|
||||
Data: Clone,
|
||||
{
|
||||
fn ingredient_index(&self) -> IngredientIndex {
|
||||
self.index
|
||||
}
|
||||
|
||||
fn maybe_changed_after(&self, _db: &DB, _input: DependencyIndex, _revision: Revision) -> bool {
|
||||
panic!("nothing should ever depend on an accumulator directly")
|
||||
}
|
||||
|
||||
fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy {
|
||||
CycleRecoveryStrategy::Panic
|
||||
}
|
||||
|
||||
fn origin(&self, _key_index: crate::Id) -> Option<QueryOrigin> {
|
||||
None
|
||||
}
|
||||
|
||||
fn mark_validated_output(
|
||||
&self,
|
||||
db: &DB,
|
||||
executor: DatabaseKeyIndex,
|
||||
output_key: Option<crate::Id>,
|
||||
) {
|
||||
assert!(output_key.is_none());
|
||||
let current_revision = db.runtime().current_revision();
|
||||
if let Some(mut v) = self.map.get_mut(&executor) {
|
||||
// The value is still valid in the new revision.
|
||||
v.produced_at = current_revision;
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_stale_output(
|
||||
&self,
|
||||
db: &DB,
|
||||
executor: DatabaseKeyIndex,
|
||||
stale_output_key: Option<crate::Id>,
|
||||
) {
|
||||
assert!(stale_output_key.is_none());
|
||||
if self.map.remove(&executor).is_some() {
|
||||
db.salsa_event(Event {
|
||||
runtime_id: db.runtime().id(),
|
||||
kind: EventKind::DidDiscardAccumulated {
|
||||
executor_key: executor,
|
||||
accumulator: self.dependency_index(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
panic!("unexpected reset on accumulator")
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, _db: &DB, _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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Data> IngredientRequiresReset for AccumulatorIngredient<Data>
|
||||
where
|
||||
Data: Clone,
|
||||
{
|
||||
const RESET_ON_NEW_REVISION: bool = false;
|
||||
}
|
||||
57
third-party/vendor/salsa-2022/src/cancelled.rs
vendored
Normal file
57
third-party/vendor/salsa-2022/src/cancelled.rs
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use std::{
|
||||
fmt,
|
||||
panic::{self, UnwindSafe},
|
||||
};
|
||||
|
||||
/// A panic payload indicating that execution of a salsa query was cancelled.
|
||||
///
|
||||
/// This can occur for a few reasons:
|
||||
/// *
|
||||
/// *
|
||||
/// *
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Cancelled {
|
||||
/// The query was operating on revision R, but there is a pending write to move to revision R+1.
|
||||
#[non_exhaustive]
|
||||
PendingWrite,
|
||||
|
||||
/// The query was blocked on another thread, and that thread panicked.
|
||||
#[non_exhaustive]
|
||||
PropagatedPanic,
|
||||
}
|
||||
|
||||
impl Cancelled {
|
||||
pub(crate) fn throw(self) -> ! {
|
||||
// We use resume and not panic here to avoid running the panic
|
||||
// hook (that is, to avoid collecting and printing backtrace).
|
||||
std::panic::resume_unwind(Box::new(self));
|
||||
}
|
||||
|
||||
/// Runs `f`, and catches any salsa cancellation.
|
||||
pub fn catch<F, T>(f: F) -> Result<T, Cancelled>
|
||||
where
|
||||
F: FnOnce() -> T + UnwindSafe,
|
||||
{
|
||||
match panic::catch_unwind(f) {
|
||||
Ok(t) => Ok(t),
|
||||
Err(payload) => match payload.downcast() {
|
||||
Ok(cancelled) => Err(*cancelled),
|
||||
Err(payload) => panic::resume_unwind(payload),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Cancelled {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let why = match self {
|
||||
Cancelled::PendingWrite => "pending write",
|
||||
Cancelled::PropagatedPanic => "propagated panic",
|
||||
};
|
||||
f.write_str("cancelled because of ")?;
|
||||
f.write_str(why)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Cancelled {}
|
||||
125
third-party/vendor/salsa-2022/src/cycle.rs
vendored
Normal file
125
third-party/vendor/salsa-2022/src/cycle.rs
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
use crate::debug::DebugWithDb;
|
||||
use crate::{key::DatabaseKeyIndex, Database};
|
||||
use std::{panic::AssertUnwindSafe, sync::Arc};
|
||||
|
||||
/// Captures the participants of a cycle that occurred when executing a query.
|
||||
///
|
||||
/// This type is meant to be used to help give meaningful error messages to the
|
||||
/// user or to help salsa developers figure out why their program is resulting
|
||||
/// in a computation cycle.
|
||||
///
|
||||
/// It is used in a few ways:
|
||||
///
|
||||
/// * During [cycle recovery](https://https://salsa-rs.github.io/salsa/cycles/fallback.html),
|
||||
/// where it is given to the fallback function.
|
||||
/// * As the panic value when an unexpected cycle (i.e., a cycle where one or more participants
|
||||
/// lacks cycle recovery information) occurs.
|
||||
///
|
||||
/// You can read more about cycle handling in
|
||||
/// the [salsa book](https://https://salsa-rs.github.io/salsa/cycles.html).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Cycle {
|
||||
participants: CycleParticipants,
|
||||
}
|
||||
|
||||
pub(crate) type CycleParticipants = Arc<Vec<DatabaseKeyIndex>>;
|
||||
|
||||
impl Cycle {
|
||||
pub(crate) fn new(participants: CycleParticipants) -> Self {
|
||||
Self { participants }
|
||||
}
|
||||
|
||||
/// True if two `Cycle` values represent the same cycle.
|
||||
pub(crate) fn is(&self, cycle: &Cycle) -> bool {
|
||||
Arc::ptr_eq(&self.participants, &cycle.participants)
|
||||
}
|
||||
|
||||
pub(crate) fn throw(self) -> ! {
|
||||
log::debug!("throwing cycle {:?}", self);
|
||||
std::panic::resume_unwind(Box::new(self))
|
||||
}
|
||||
|
||||
pub(crate) fn catch<T>(execute: impl FnOnce() -> T) -> Result<T, Cycle> {
|
||||
match std::panic::catch_unwind(AssertUnwindSafe(execute)) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(err) => match err.downcast::<Cycle>() {
|
||||
Ok(cycle) => Err(*cycle),
|
||||
Err(other) => std::panic::resume_unwind(other),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over the [`DatabaseKeyIndex`] for each query participating
|
||||
/// in the cycle. The start point of this iteration within the cycle
|
||||
/// is arbitrary but deterministic, but the ordering is otherwise determined
|
||||
/// by the execution.
|
||||
pub fn participant_keys(&self) -> impl Iterator<Item = DatabaseKeyIndex> + '_ {
|
||||
self.participants.iter().copied()
|
||||
}
|
||||
|
||||
/// Returns a vector with the debug information for
|
||||
/// all the participants in the cycle.
|
||||
pub fn all_participants<DB: ?Sized + Database>(&self, db: &DB) -> Vec<String> {
|
||||
self.participant_keys()
|
||||
.map(|d| format!("{:?}", d.debug(db)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns a vector with the debug information for
|
||||
/// those participants in the cycle that lacked recovery
|
||||
/// information.
|
||||
pub fn unexpected_participants<DB: ?Sized + Database>(&self, db: &DB) -> Vec<String> {
|
||||
self.participant_keys()
|
||||
.filter(|&d| {
|
||||
db.cycle_recovery_strategy(d.ingredient_index) == CycleRecoveryStrategy::Panic
|
||||
})
|
||||
.map(|d| format!("{:?}", d.debug(db)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns a "debug" view onto this strict that can be used to print out information.
|
||||
pub fn debug<'me, DB: ?Sized + Database>(&'me self, db: &'me DB) -> impl std::fmt::Debug + 'me {
|
||||
struct UnexpectedCycleDebug<'me> {
|
||||
c: &'me Cycle,
|
||||
db: &'me dyn Database,
|
||||
}
|
||||
|
||||
impl<'me> std::fmt::Debug for UnexpectedCycleDebug<'me> {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fmt.debug_struct("UnexpectedCycle")
|
||||
.field("all_participants", &self.c.all_participants(self.db))
|
||||
.field(
|
||||
"unexpected_participants",
|
||||
&self.c.unexpected_participants(self.db),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
UnexpectedCycleDebug {
|
||||
c: self,
|
||||
db: db.as_salsa_database(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cycle recovery strategy: Is this query capable of recovering from
|
||||
/// a cycle that results from executing the function? If so, how?
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum CycleRecoveryStrategy {
|
||||
/// Cannot recover from cycles: panic.
|
||||
///
|
||||
/// This is the default. It is also what happens if a cycle
|
||||
/// occurs and the queries involved have different recovery
|
||||
/// strategies.
|
||||
///
|
||||
/// In the case of a failure due to a cycle, the panic
|
||||
/// value will be XXX (FIXME).
|
||||
Panic,
|
||||
|
||||
/// Recovers from cycles by storing a sentinel value.
|
||||
///
|
||||
/// This value is computed by the `QueryFunction::cycle_fallback`
|
||||
/// function.
|
||||
Fallback,
|
||||
}
|
||||
138
third-party/vendor/salsa-2022/src/database.rs
vendored
Normal file
138
third-party/vendor/salsa-2022/src/database.rs
vendored
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
use crate::{storage::HasJarsDyn, DebugWithDb, Durability, Event};
|
||||
|
||||
pub trait Database: HasJarsDyn + AsSalsaDatabase {
|
||||
/// This function is invoked at key points in the salsa
|
||||
/// runtime. It permits the database to be customized and to
|
||||
/// inject logging or other custom behavior.
|
||||
///
|
||||
/// By default, the event is logged at level debug using
|
||||
/// the standard `log` facade.
|
||||
fn salsa_event(&self, event: Event) {
|
||||
log::debug!("salsa_event: {:?}", event.debug(self));
|
||||
}
|
||||
|
||||
/// A "synthetic write" causes the system to act *as though* some
|
||||
/// input of durability `durability` has changed. This is mostly
|
||||
/// useful for profiling scenarios.
|
||||
///
|
||||
/// **WARNING:** Just like an ordinary write, this method triggers
|
||||
/// cancellation. If you invoke it while a snapshot exists, it
|
||||
/// will block until that snapshot is dropped -- if that snapshot
|
||||
/// is owned by the current thread, this could trigger deadlock.
|
||||
fn synthetic_write(&mut self, durability: Durability) {
|
||||
self.runtime_mut().report_tracked_write(durability);
|
||||
}
|
||||
|
||||
/// Reports that the query depends on some state unknown to salsa.
|
||||
///
|
||||
/// Queries which report untracked reads will be re-executed in the next
|
||||
/// revision.
|
||||
fn report_untracked_read(&self) {
|
||||
self.runtime().report_untracked_read();
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// must also be `Send`, as must any additional data in your database.
|
||||
pub trait ParallelDatabase: Database + Send {
|
||||
/// Creates a second handle to the database that holds the
|
||||
/// database fixed at a particular revision. So long as this
|
||||
/// "frozen" handle exists, any attempt to [`set`] an input will
|
||||
/// block.
|
||||
///
|
||||
/// [`set`]: struct.QueryTable.html#method.set
|
||||
///
|
||||
/// This is the method you are meant to use most of the time in a
|
||||
/// parallel setting where modifications may arise asynchronously
|
||||
/// (e.g., a language server). In this context, it is common to
|
||||
/// wish to "fork off" a snapshot of the database performing some
|
||||
/// series of queries in parallel and arranging the results. Using
|
||||
/// this method for that purpose ensures that those queries will
|
||||
/// see a consistent view of the database (it is also advisable
|
||||
/// for those queries to use the [`Runtime::unwind_if_cancelled`]
|
||||
/// method to check for cancellation).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// It is not permitted to create a snapshot from inside of a
|
||||
/// query. Attepting to do so will panic.
|
||||
///
|
||||
/// # Deadlock warning
|
||||
///
|
||||
/// The intended pattern for snapshots is that, once created, they
|
||||
/// are sent to another thread and used from there. As such, the
|
||||
/// `snapshot` acquires a "read lock" on the database --
|
||||
/// therefore, so long as the `snapshot` is not dropped, any
|
||||
/// attempt to `set` a value in the database will block. If the
|
||||
/// `snapshot` is owned by the same thread that is attempting to
|
||||
/// `set`, this will cause a problem.
|
||||
///
|
||||
/// # How to implement this
|
||||
///
|
||||
/// Typically, this method will create a second copy of your
|
||||
/// database type (`MyDatabaseType`, in the example below),
|
||||
/// cloning over each of the fields from `self` into this new
|
||||
/// copy. For the field that stores the salsa runtime, you should
|
||||
/// use [the `Runtime::snapshot` method][rfm] to create a snapshot of the
|
||||
/// runtime. Finally, package up the result using `Snapshot::new`,
|
||||
/// which is a simple wrapper type that only gives `&self` access
|
||||
/// to the database within (thus preventing the use of methods
|
||||
/// that may mutate the inputs):
|
||||
///
|
||||
/// [rfm]: struct.Runtime.html#method.snapshot
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// impl ParallelDatabase for MyDatabaseType {
|
||||
/// fn snapshot(&self) -> Snapshot<Self> {
|
||||
/// Snapshot::new(
|
||||
/// MyDatabaseType {
|
||||
/// runtime: self.storage.snapshot(),
|
||||
/// other_field: self.other_field.clone(),
|
||||
/// }
|
||||
/// )
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn snapshot(&self) -> Snapshot<Self>;
|
||||
}
|
||||
pub trait AsSalsaDatabase {
|
||||
fn as_salsa_database(&self) -> &dyn Database;
|
||||
}
|
||||
|
||||
/// Simple wrapper struct that takes ownership of a database `DB` and
|
||||
/// only gives `&self` access to it. See [the `snapshot` method][fm]
|
||||
/// for more details.
|
||||
///
|
||||
/// [fm]: trait.ParallelDatabase.html#method.snapshot
|
||||
#[derive(Debug)]
|
||||
pub struct Snapshot<DB: ?Sized>
|
||||
where
|
||||
DB: ParallelDatabase,
|
||||
{
|
||||
db: DB,
|
||||
}
|
||||
|
||||
impl<DB> Snapshot<DB>
|
||||
where
|
||||
DB: ParallelDatabase,
|
||||
{
|
||||
/// Creates a `Snapshot` that wraps the given database handle
|
||||
/// `db`. From this point forward, only shared references to `db`
|
||||
/// will be possible.
|
||||
pub fn new(db: DB) -> Self {
|
||||
Snapshot { db }
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> std::ops::Deref for Snapshot<DB>
|
||||
where
|
||||
DB: ParallelDatabase,
|
||||
{
|
||||
type Target = DB;
|
||||
|
||||
fn deref(&self) -> &DB {
|
||||
&self.db
|
||||
}
|
||||
}
|
||||
247
third-party/vendor/salsa-2022/src/debug.rs
vendored
Normal file
247
third-party/vendor/salsa-2022/src/debug.rs
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub trait DebugWithDb<Db: ?Sized> {
|
||||
fn debug<'me, 'db>(&'me self, db: &'me Db) -> DebugWith<'me, Db>
|
||||
where
|
||||
Self: Sized + 'me,
|
||||
{
|
||||
DebugWith {
|
||||
value: BoxRef::Ref(self),
|
||||
db,
|
||||
include_all_fields: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_with<'me, 'db>(&'me self, db: &'me Db, include_all_fields: bool) -> DebugWith<'me, Db>
|
||||
where
|
||||
Self: Sized + 'me,
|
||||
{
|
||||
DebugWith {
|
||||
value: BoxRef::Ref(self),
|
||||
db,
|
||||
include_all_fields,
|
||||
}
|
||||
}
|
||||
|
||||
/// Be careful when using this method inside a tracked function,
|
||||
/// because the default macro generated implementation will read all fields,
|
||||
/// maybe introducing undesired dependencies.
|
||||
fn debug_all<'me, 'db>(&'me self, db: &'me Db) -> DebugWith<'me, Db>
|
||||
where
|
||||
Self: Sized + 'me,
|
||||
{
|
||||
DebugWith {
|
||||
value: BoxRef::Ref(self),
|
||||
db,
|
||||
include_all_fields: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_debug<'me, 'db>(self, db: &'me Db) -> DebugWith<'me, Db>
|
||||
where
|
||||
Self: Sized + 'me,
|
||||
{
|
||||
DebugWith {
|
||||
value: BoxRef::Box(Box::new(self)),
|
||||
db,
|
||||
include_all_fields: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Be careful when using this method inside a tracked function,
|
||||
/// because the default macro generated implementation will read all fields,
|
||||
/// maybe introducing undesired dependencies.
|
||||
fn into_debug_all<'me, 'db>(self, db: &'me Db) -> DebugWith<'me, Db>
|
||||
where
|
||||
Self: Sized + 'me,
|
||||
{
|
||||
DebugWith {
|
||||
value: BoxRef::Box(Box::new(self)),
|
||||
db,
|
||||
include_all_fields: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// if `include_all_fields` is `false` only identity fields should be read, which means:
|
||||
/// - for [#\[salsa::input\]](salsa_2022_macros::input) no fields
|
||||
/// - for [#\[salsa::tracked\]](salsa_2022_macros::tracked) only fields with `#[id]` attribute
|
||||
/// - for [#\[salsa::interned\]](salsa_2022_macros::interned) any field
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result;
|
||||
}
|
||||
|
||||
pub struct DebugWith<'me, Db: ?Sized> {
|
||||
value: BoxRef<'me, dyn DebugWithDb<Db> + 'me>,
|
||||
db: &'me Db,
|
||||
include_all_fields: bool,
|
||||
}
|
||||
|
||||
enum BoxRef<'me, T: ?Sized> {
|
||||
Box(Box<T>),
|
||||
Ref(&'me T),
|
||||
}
|
||||
|
||||
impl<T: ?Sized> std::ops::Deref for BoxRef<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
BoxRef::Box(b) => b,
|
||||
BoxRef::Ref(r) => r,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: ?Sized> fmt::Debug for DebugWith<'_, D> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
DebugWithDb::fmt(&*self.value, f, self.db, self.include_all_fields)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for &T
|
||||
where
|
||||
T: DebugWithDb<Db>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
||||
T::fmt(self, f, db, include_all_fields)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for Box<T>
|
||||
where
|
||||
T: DebugWithDb<Db>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
||||
T::fmt(self, f, db, include_all_fields)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db: ?Sized, T> DebugWithDb<Db> for Rc<T>
|
||||
where
|
||||
T: DebugWithDb<Db>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
||||
T::fmt(self, f, db, include_all_fields)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for Arc<T>
|
||||
where
|
||||
T: DebugWithDb<Db>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
||||
T::fmt(self, f, db, include_all_fields)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db: ?Sized, T> DebugWithDb<Db> for Vec<T>
|
||||
where
|
||||
T: DebugWithDb<Db>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
||||
let elements = self.iter().map(|e| e.debug_with(db, include_all_fields));
|
||||
f.debug_list().entries(elements).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db: ?Sized, T> DebugWithDb<Db> for Option<T>
|
||||
where
|
||||
T: DebugWithDb<Db>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
||||
let me = self.as_ref().map(|v| v.debug_with(db, include_all_fields));
|
||||
fmt::Debug::fmt(&me, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db: ?Sized, K, V, S> DebugWithDb<Db> for HashMap<K, V, S>
|
||||
where
|
||||
K: DebugWithDb<Db>,
|
||||
V: DebugWithDb<Db>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
||||
let elements = self.iter().map(|(k, v)| {
|
||||
(
|
||||
k.debug_with(db, include_all_fields),
|
||||
v.debug_with(db, include_all_fields),
|
||||
)
|
||||
});
|
||||
f.debug_map().entries(elements).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db: ?Sized, A, B> DebugWithDb<Db> for (A, B)
|
||||
where
|
||||
A: DebugWithDb<Db>,
|
||||
B: DebugWithDb<Db>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
||||
f.debug_tuple("")
|
||||
.field(&self.0.debug_with(db, include_all_fields))
|
||||
.field(&self.1.debug_with(db, include_all_fields))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db: ?Sized, A, B, C> DebugWithDb<Db> for (A, B, C)
|
||||
where
|
||||
A: DebugWithDb<Db>,
|
||||
B: DebugWithDb<Db>,
|
||||
C: DebugWithDb<Db>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
||||
f.debug_tuple("")
|
||||
.field(&self.0.debug_with(db, include_all_fields))
|
||||
.field(&self.1.debug_with(db, include_all_fields))
|
||||
.field(&self.2.debug_with(db, include_all_fields))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db: ?Sized, V, S> DebugWithDb<Db> for HashSet<V, S>
|
||||
where
|
||||
V: DebugWithDb<Db>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
||||
let elements = self.iter().map(|e| e.debug_with(db, include_all_fields));
|
||||
f.debug_list().entries(elements).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// This is used by the macro generated code.
|
||||
/// If the field type implements `DebugWithDb`, uses that, otherwise, uses `Debug`.
|
||||
/// That's the "has impl" trick (https://github.com/nvzqz/impls#how-it-works)
|
||||
#[doc(hidden)]
|
||||
pub mod helper {
|
||||
use super::{DebugWith, DebugWithDb};
|
||||
use std::{fmt, marker::PhantomData};
|
||||
|
||||
pub trait Fallback<T: fmt::Debug, Db: ?Sized> {
|
||||
fn salsa_debug<'a, 'b>(
|
||||
a: &'a T,
|
||||
_db: &'b Db,
|
||||
_include_all_fields: bool,
|
||||
) -> &'a dyn fmt::Debug {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SalsaDebug<T, Db: ?Sized>(PhantomData<T>, PhantomData<Db>);
|
||||
|
||||
impl<T: DebugWithDb<Db>, Db: ?Sized> SalsaDebug<T, Db> {
|
||||
#[allow(dead_code)]
|
||||
pub fn salsa_debug<'a, 'b: 'a>(
|
||||
a: &'a T,
|
||||
db: &'b Db,
|
||||
include_all_fields: bool,
|
||||
) -> DebugWith<'a, Db> {
|
||||
a.debug_with(db, include_all_fields)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Everything, Db: ?Sized, T: fmt::Debug> Fallback<T, Db> for Everything {}
|
||||
}
|
||||
49
third-party/vendor/salsa-2022/src/durability.rs
vendored
Normal file
49
third-party/vendor/salsa-2022/src/durability.rs
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/// Describes how likely a value is to change -- how "durable" it is.
|
||||
/// By default, inputs have `Durability::LOW` and interned values have
|
||||
/// `Durability::HIGH`. But inputs can be explicitly set with other
|
||||
/// durabilities.
|
||||
///
|
||||
/// We use durabilities to optimize the work of "revalidating" a query
|
||||
/// after some input has changed. Ordinarily, in a new revision,
|
||||
/// queries have to trace all their inputs back to the base inputs to
|
||||
/// determine if any of those inputs have changed. But if we know that
|
||||
/// the only changes were to inputs of low durability (the common
|
||||
/// case), and we know that the query only used inputs of medium
|
||||
/// durability or higher, then we can skip that enumeration.
|
||||
///
|
||||
/// Typically, one assigns low durabilites to inputs that the user is
|
||||
/// frequently editing. Medium or high durabilities are used for
|
||||
/// configuration, the source from library crates, or other things
|
||||
/// that are unlikely to be edited.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Durability(u8);
|
||||
|
||||
impl Durability {
|
||||
/// Low durability: things that change frequently.
|
||||
///
|
||||
/// Example: part of the crate being edited
|
||||
pub const LOW: Durability = Durability(0);
|
||||
|
||||
/// Medium durability: things that change sometimes, but rarely.
|
||||
///
|
||||
/// Example: a Cargo.toml file
|
||||
pub const MEDIUM: Durability = Durability(1);
|
||||
|
||||
/// High durability: things that are not expected to change under
|
||||
/// common usage.
|
||||
///
|
||||
/// Example: the standard library or something from crates.io
|
||||
pub const HIGH: Durability = Durability(2);
|
||||
|
||||
/// The maximum possible durability; equivalent to HIGH but
|
||||
/// "conceptually" distinct (i.e., if we add more durability
|
||||
/// levels, this could change).
|
||||
pub(crate) const MAX: Durability = Self::HIGH;
|
||||
|
||||
/// Number of durability levels.
|
||||
pub(crate) const LEN: usize = 3;
|
||||
|
||||
pub(crate) fn index(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
221
third-party/vendor/salsa-2022/src/event.rs
vendored
Normal file
221
third-party/vendor/salsa-2022/src/event.rs
vendored
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
use crate::{
|
||||
debug::DebugWithDb, key::DatabaseKeyIndex, key::DependencyIndex, runtime::RuntimeId, Database,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
/// The `Event` struct identifies various notable things that can
|
||||
/// occur during salsa execution. Instances of this struct are given
|
||||
/// to `salsa_event`.
|
||||
pub struct Event {
|
||||
/// The id of the snapshot that triggered the event. Usually
|
||||
/// 1-to-1 with a thread, as well.
|
||||
pub runtime_id: RuntimeId,
|
||||
|
||||
/// What sort of event was it.
|
||||
pub kind: EventKind,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Event {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("Event")
|
||||
.field("runtime_id", &self.runtime_id)
|
||||
.field("kind", &self.kind)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db> DebugWithDb<Db> for Event
|
||||
where
|
||||
Db: ?Sized + Database,
|
||||
{
|
||||
fn fmt(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
db: &Db,
|
||||
include_all_fields: bool,
|
||||
) -> std::fmt::Result {
|
||||
f.debug_struct("Event")
|
||||
.field("runtime_id", &self.runtime_id)
|
||||
.field("kind", &self.kind.debug_with(db, include_all_fields))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum identifying the various kinds of events that can occur.
|
||||
pub enum EventKind {
|
||||
/// Occurs when we found that all inputs to a memoized value are
|
||||
/// up-to-date and hence the value can be re-used without
|
||||
/// executing the closure.
|
||||
///
|
||||
/// Executes before the "re-used" value is returned.
|
||||
DidValidateMemoizedValue {
|
||||
/// The database-key for the affected value. Implements `Debug`.
|
||||
database_key: DatabaseKeyIndex,
|
||||
},
|
||||
|
||||
/// Indicates that another thread (with id `other_runtime_id`) is processing the
|
||||
/// given query (`database_key`), so we will block until they
|
||||
/// finish.
|
||||
///
|
||||
/// Executes after we have registered with the other thread but
|
||||
/// before they have answered us.
|
||||
///
|
||||
/// (NB: you can find the `id` of the current thread via the
|
||||
/// `runtime`)
|
||||
WillBlockOn {
|
||||
/// The id of the runtime we will block on.
|
||||
other_runtime_id: RuntimeId,
|
||||
|
||||
/// The database-key for the affected value. Implements `Debug`.
|
||||
database_key: DatabaseKeyIndex,
|
||||
},
|
||||
|
||||
/// Indicates that the function for this query will be executed.
|
||||
/// This is either because it has never executed before or because
|
||||
/// its inputs may be out of date.
|
||||
WillExecute {
|
||||
/// The database-key for the affected value. Implements `Debug`.
|
||||
database_key: DatabaseKeyIndex,
|
||||
},
|
||||
|
||||
/// Indicates that `unwind_if_cancelled` was called and salsa will check if
|
||||
/// the current revision has been cancelled.
|
||||
WillCheckCancellation,
|
||||
|
||||
/// Discovered that a query used to output a given output but no longer does.
|
||||
WillDiscardStaleOutput {
|
||||
/// Key for the query that is executing and which no longer outputs the given value.
|
||||
execute_key: DatabaseKeyIndex,
|
||||
|
||||
/// Key for the query that is no longer output
|
||||
output_key: DependencyIndex,
|
||||
},
|
||||
|
||||
/// Tracked structs or memoized data were discarded (freed).
|
||||
DidDiscard {
|
||||
/// Value being discarded.
|
||||
key: DatabaseKeyIndex,
|
||||
},
|
||||
|
||||
/// Discarded accumulated data from a given fn
|
||||
DidDiscardAccumulated {
|
||||
/// The key of the fn that accumulated results
|
||||
executor_key: DatabaseKeyIndex,
|
||||
|
||||
/// Accumulator that was accumulated into
|
||||
accumulator: DependencyIndex,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Debug for EventKind {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
EventKind::DidValidateMemoizedValue { database_key } => fmt
|
||||
.debug_struct("DidValidateMemoizedValue")
|
||||
.field("database_key", database_key)
|
||||
.finish(),
|
||||
EventKind::WillBlockOn {
|
||||
other_runtime_id,
|
||||
database_key,
|
||||
} => fmt
|
||||
.debug_struct("WillBlockOn")
|
||||
.field("other_runtime_id", other_runtime_id)
|
||||
.field("database_key", database_key)
|
||||
.finish(),
|
||||
EventKind::WillExecute { database_key } => fmt
|
||||
.debug_struct("WillExecute")
|
||||
.field("database_key", database_key)
|
||||
.finish(),
|
||||
EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(),
|
||||
EventKind::WillDiscardStaleOutput {
|
||||
execute_key,
|
||||
output_key,
|
||||
} => fmt
|
||||
.debug_struct("WillDiscardStaleOutput")
|
||||
.field("execute_key", &execute_key)
|
||||
.field("output_key", &output_key)
|
||||
.finish(),
|
||||
EventKind::DidDiscard { key } => {
|
||||
fmt.debug_struct("DidDiscard").field("key", &key).finish()
|
||||
}
|
||||
EventKind::DidDiscardAccumulated {
|
||||
executor_key,
|
||||
accumulator,
|
||||
} => fmt
|
||||
.debug_struct("DidDiscardAccumulated")
|
||||
.field("executor_key", executor_key)
|
||||
.field("accumulator", accumulator)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db> DebugWithDb<Db> for EventKind
|
||||
where
|
||||
Db: ?Sized + Database,
|
||||
{
|
||||
fn fmt(
|
||||
&self,
|
||||
fmt: &mut std::fmt::Formatter<'_>,
|
||||
db: &Db,
|
||||
include_all_fields: bool,
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
EventKind::DidValidateMemoizedValue { database_key } => fmt
|
||||
.debug_struct("DidValidateMemoizedValue")
|
||||
.field(
|
||||
"database_key",
|
||||
&database_key.debug_with(db, include_all_fields),
|
||||
)
|
||||
.finish(),
|
||||
EventKind::WillBlockOn {
|
||||
other_runtime_id,
|
||||
database_key,
|
||||
} => fmt
|
||||
.debug_struct("WillBlockOn")
|
||||
.field("other_runtime_id", other_runtime_id)
|
||||
.field(
|
||||
"database_key",
|
||||
&database_key.debug_with(db, include_all_fields),
|
||||
)
|
||||
.finish(),
|
||||
EventKind::WillExecute { database_key } => fmt
|
||||
.debug_struct("WillExecute")
|
||||
.field(
|
||||
"database_key",
|
||||
&database_key.debug_with(db, include_all_fields),
|
||||
)
|
||||
.finish(),
|
||||
EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(),
|
||||
EventKind::WillDiscardStaleOutput {
|
||||
execute_key,
|
||||
output_key,
|
||||
} => fmt
|
||||
.debug_struct("WillDiscardStaleOutput")
|
||||
.field(
|
||||
"execute_key",
|
||||
&execute_key.debug_with(db, include_all_fields),
|
||||
)
|
||||
.field("output_key", &output_key.debug_with(db, include_all_fields))
|
||||
.finish(),
|
||||
EventKind::DidDiscard { key } => fmt
|
||||
.debug_struct("DidDiscard")
|
||||
.field("key", &key.debug_with(db, include_all_fields))
|
||||
.finish(),
|
||||
EventKind::DidDiscardAccumulated {
|
||||
executor_key,
|
||||
accumulator,
|
||||
} => fmt
|
||||
.debug_struct("DidDiscardAccumulated")
|
||||
.field(
|
||||
"executor_key",
|
||||
&executor_key.debug_with(db, include_all_fields),
|
||||
)
|
||||
.field(
|
||||
"accumulator",
|
||||
&accumulator.debug_with(db, include_all_fields),
|
||||
)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
290
third-party/vendor/salsa-2022/src/function.rs
vendored
Normal file
290
third-party/vendor/salsa-2022/src/function.rs
vendored
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
use std::{fmt, sync::Arc};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use crossbeam::{atomic::AtomicCell, queue::SegQueue};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
use super::{ingredient::Ingredient, routes::IngredientIndex, AsId};
|
||||
|
||||
mod accumulated;
|
||||
mod backdate;
|
||||
mod delete;
|
||||
mod diff_outputs;
|
||||
mod execute;
|
||||
mod fetch;
|
||||
mod inputs;
|
||||
mod lru;
|
||||
mod maybe_changed_after;
|
||||
mod memo;
|
||||
mod specify;
|
||||
mod store;
|
||||
mod sync;
|
||||
|
||||
/// 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.
|
||||
/// The function ingredient is fairly complex and so its code is spread across multiple modules, typically one per method.
|
||||
/// The main entry points are:
|
||||
///
|
||||
/// * the `fetch` method, which is invoked when the function is called by the user's code;
|
||||
/// it will return a memoized value if one exists, or execute the function otherwise.
|
||||
/// * the `specify` method, which can only be used when the key is an entity created by the active query.
|
||||
/// It sets the value of the function imperatively, so that when later fetches occur, they'll return this value.
|
||||
/// * the `store` method, which can only be invoked with an `&mut` reference, and is to set input fields.
|
||||
pub struct FunctionIngredient<C: Configuration> {
|
||||
/// The ingredient index we were assigned in the database.
|
||||
/// Used to construct `DatabaseKeyIndex` values.
|
||||
index: IngredientIndex,
|
||||
|
||||
/// Tracks the keys for which we have memoized values.
|
||||
memo_map: memo::MemoMap<C::Key, C::Value>,
|
||||
|
||||
/// Tracks the keys that are currently being processed; used to coordinate between
|
||||
/// worker threads.
|
||||
sync_map: sync::SyncMap,
|
||||
|
||||
/// Used to find memos to throw out when we have too many memoized values.
|
||||
lru: lru::Lru,
|
||||
|
||||
/// When `fetch` and friends executes, they return a reference to the
|
||||
/// value stored in the memo that is extended to live as long as the `&self`
|
||||
/// reference we start with. This means that whenever we remove something
|
||||
/// from `memo_map` with an `&self` reference, there *could* be references to its
|
||||
/// internals still in use. Therefore we push the memo into this queue and
|
||||
/// only *actually* free up memory when a new revision starts (which means
|
||||
/// we have an `&mut` reference to self).
|
||||
///
|
||||
/// You might think that we could do this only if the memo was verified in the
|
||||
/// current revision: you would be right, but we are being defensive, because
|
||||
/// we don't know that we can trust the database to give us the same runtime
|
||||
/// everytime and so forth.
|
||||
deleted_entries: SegQueue<ArcSwap<memo::Memo<C::Value>>>,
|
||||
|
||||
/// Set to true once we invoke `register_dependent_fn` for `C::SalsaStruct`.
|
||||
/// Prevents us from registering more than once.
|
||||
registered: AtomicCell<bool>,
|
||||
|
||||
debug_name: &'static str,
|
||||
}
|
||||
|
||||
pub trait Configuration {
|
||||
type Jar: for<'db> Jar<'db>;
|
||||
|
||||
/// 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: for<'db> SalsaStructInDb<DynDb<'db, Self>>;
|
||||
|
||||
/// What key is used to index the memo. Typically a salsa struct id,
|
||||
/// but if this memoized function has multiple arguments it will be a `salsa::Id`
|
||||
/// that results from interning those arguments.
|
||||
type Key: AsId;
|
||||
|
||||
/// The value computed by the function.
|
||||
type Value: 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: &DynDb<Self>, key: Self::Key) -> Self::Value;
|
||||
|
||||
/// 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: &DynDb<Self>, cycle: &Cycle, key: Self::Key) -> Self::Value;
|
||||
|
||||
/// Given a salsa Id, returns the key. Convenience function to avoid
|
||||
/// having to type `<C::Key as AsId>::from_id`.
|
||||
fn key_from_id(id: Id) -> Self::Key {
|
||||
AsId::from_id(id)
|
||||
}
|
||||
}
|
||||
|
||||
/// True if `old_value == new_value`. Invoked by the generated
|
||||
/// code for `should_backdate_value` so as to give a better
|
||||
/// error message.
|
||||
pub fn should_backdate_value<V: Eq>(old_value: &V, new_value: &V) -> bool {
|
||||
old_value == new_value
|
||||
}
|
||||
|
||||
pub type DynDb<'bound, C> = <<C as Configuration>::Jar as Jar<'bound>>::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]>);
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self {
|
||||
Self {
|
||||
index,
|
||||
memo_map: memo::MemoMap::default(),
|
||||
lru: Default::default(),
|
||||
sync_map: Default::default(),
|
||||
deleted_entries: Default::default(),
|
||||
registered: Default::default(),
|
||||
debug_name,
|
||||
}
|
||||
}
|
||||
|
||||
fn database_key_index(&self, k: C::Key) -> DatabaseKeyIndex {
|
||||
DatabaseKeyIndex {
|
||||
ingredient_index: self.index,
|
||||
key_index: k.as_id(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_capacity(&self, capacity: usize) {
|
||||
self.lru.set_capacity(capacity);
|
||||
}
|
||||
|
||||
/// Returns a reference to the memo value that lives as long as self.
|
||||
/// This is UNSAFE: the caller is responsible for ensuring that the
|
||||
/// memo will not be released so long as the `&self` is valid.
|
||||
/// This is done by (a) ensuring the memo is present in the memo-map
|
||||
/// when this function is called and (b) ensuring that any entries
|
||||
/// removed from the memo-map are added to `deleted_entries`, which is
|
||||
/// only cleared with `&mut self`.
|
||||
unsafe fn extend_memo_lifetime<'this, 'memo>(
|
||||
&'this self,
|
||||
memo: &'memo memo::Memo<C::Value>,
|
||||
) -> Option<&'this C::Value> {
|
||||
let memo_value: Option<&'memo C::Value> = memo.value.as_ref();
|
||||
std::mem::transmute(memo_value)
|
||||
}
|
||||
|
||||
fn insert_memo(
|
||||
&self,
|
||||
db: &DynDb<'_, C>,
|
||||
key: C::Key,
|
||||
memo: memo::Memo<C::Value>,
|
||||
) -> 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
|
||||
// value is returned) and anything removed from map is added to deleted entries (ensured elsewhere).
|
||||
self.extend_memo_lifetime(&memo)
|
||||
};
|
||||
if let Some(old_value) = self.memo_map.insert(key, memo) {
|
||||
// In case there is a reference to the old memo out there, we have to store it
|
||||
// in the deleted entries. This will get cleared when a new revision starts.
|
||||
self.deleted_entries.push(old_value);
|
||||
}
|
||||
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) {
|
||||
<C::SalsaStruct as SalsaStructInDb<_>>::register_dependent_fn(db, self.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB, C> Ingredient<DB> for FunctionIngredient<C>
|
||||
where
|
||||
DB: ?Sized + DbWithJar<C::Jar>,
|
||||
C: Configuration,
|
||||
{
|
||||
fn ingredient_index(&self) -> IngredientIndex {
|
||||
self.index
|
||||
}
|
||||
|
||||
fn maybe_changed_after(&self, db: &DB, input: DependencyIndex, revision: Revision) -> bool {
|
||||
let key = C::key_from_id(input.key_index.unwrap());
|
||||
let db = db.as_jar_db();
|
||||
self.maybe_changed_after(db, key, revision)
|
||||
}
|
||||
|
||||
fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy {
|
||||
C::CYCLE_STRATEGY
|
||||
}
|
||||
|
||||
fn origin(&self, key_index: Id) -> Option<QueryOrigin> {
|
||||
let key = C::key_from_id(key_index);
|
||||
self.origin(key)
|
||||
}
|
||||
|
||||
fn mark_validated_output(
|
||||
&self,
|
||||
db: &DB,
|
||||
executor: DatabaseKeyIndex,
|
||||
output_key: Option<crate::Id>,
|
||||
) {
|
||||
let output_key = C::key_from_id(output_key.unwrap());
|
||||
self.validate_specified_value(db.as_jar_db(), executor, output_key);
|
||||
}
|
||||
|
||||
fn remove_stale_output(
|
||||
&self,
|
||||
_db: &DB,
|
||||
_executor: DatabaseKeyIndex,
|
||||
_stale_output_key: Option<crate::Id>,
|
||||
) {
|
||||
// This function is invoked when a query Q specifies the value for `stale_output_key` in rev 1,
|
||||
// but not in rev 2. We don't do anything in this case, we just leave the (now stale) memo.
|
||||
// Since its `verified_at` field has not changed, it will be considered dirty if it is invoked.
|
||||
}
|
||||
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
std::mem::take(&mut self.deleted_entries);
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, db: &DB, id: crate::Id) {
|
||||
// Remove any data keyed by `id`, since `id` no longer
|
||||
// exists in this revision.
|
||||
|
||||
let id: C::Key = C::key_from_id(id);
|
||||
if let Some(origin) = self.delete_memo(id) {
|
||||
let key = self.database_key_index(id);
|
||||
db.salsa_event(Event {
|
||||
runtime_id: db.runtime().id(),
|
||||
kind: EventKind::DidDiscard { key },
|
||||
});
|
||||
|
||||
// Anything that was output by this memoized execution
|
||||
// is now itself stale.
|
||||
for stale_output in origin.outputs() {
|
||||
db.remove_stale_output(key, stale_output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_index(&self, index: Option<crate::Id>, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt_index(self.debug_name, index, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> IngredientRequiresReset for FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
const RESET_ON_NEW_REVISION: bool = true;
|
||||
}
|
||||
79
third-party/vendor/salsa-2022/src/function/accumulated.rs
vendored
Normal file
79
third-party/vendor/salsa-2022/src/function/accumulated.rs
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use crate::{
|
||||
hash::FxHashSet,
|
||||
runtime::local_state::QueryOrigin,
|
||||
storage::{HasJar, HasJarsDyn},
|
||||
DatabaseKeyIndex,
|
||||
};
|
||||
|
||||
use super::{Configuration, DynDb, FunctionIngredient};
|
||||
use crate::accumulator::Accumulator;
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
/// Returns all the values accumulated into `accumulator` by this query and its
|
||||
/// transitive inputs.
|
||||
pub fn accumulated<'db, A>(&self, db: &DynDb<'db, C>, key: C::Key) -> Vec<A::Data>
|
||||
where
|
||||
DynDb<'db, C>: HasJar<A::Jar>,
|
||||
A: Accumulator,
|
||||
{
|
||||
// To start, ensure that the value is up to date:
|
||||
self.fetch(db, key);
|
||||
|
||||
// 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);
|
||||
stack.extend(db.origin(input));
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// The stack is used to execute a DFS across all the queries
|
||||
/// that were transitively executed by some given start query.
|
||||
/// When we visit a query Q0, we look at its dependencies Q1...Qn,
|
||||
/// and if they have not already been visited, we push them on the stack.
|
||||
struct Stack {
|
||||
/// Stack of queries left to visit.
|
||||
v: Vec<DatabaseKeyIndex>,
|
||||
|
||||
/// Set of all queries we've seen.
|
||||
s: FxHashSet<DatabaseKeyIndex>,
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
fn new(start: DatabaseKeyIndex) -> Self {
|
||||
Self {
|
||||
v: vec![start],
|
||||
s: FxHashSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn pop(&mut self) -> Option<DatabaseKeyIndex> {
|
||||
self.v.pop()
|
||||
}
|
||||
|
||||
/// Extend the stack of queries with the dependencies from `origin`.
|
||||
fn extend(&mut self, origin: Option<QueryOrigin>) {
|
||||
match origin {
|
||||
None | Some(QueryOrigin::Assigned(_)) | Some(QueryOrigin::BaseInput) => {}
|
||||
Some(QueryOrigin::Derived(edges)) | Some(QueryOrigin::DerivedUntracked(edges)) => {
|
||||
for dependency_index in edges.inputs() {
|
||||
if let Ok(i) = DatabaseKeyIndex::try_from(dependency_index) {
|
||||
if self.s.insert(i) {
|
||||
self.v.push(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
third-party/vendor/salsa-2022/src/function/backdate.rs
vendored
Normal file
36
third-party/vendor/salsa-2022/src/function/backdate.rs
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use crate::runtime::local_state::QueryRevisions;
|
||||
|
||||
use super::{memo::Memo, Configuration, FunctionIngredient};
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
/// If the value/durability of this memo is equal to what is found in `revisions`/`value`,
|
||||
/// then updates `revisions.changed_at` to match `self.revisions.changed_at`. This is invoked
|
||||
/// on an old memo when a new memo has been produced to check whether there have been changed.
|
||||
pub(super) fn backdate_if_appropriate(
|
||||
&self,
|
||||
old_memo: &Memo<C::Value>,
|
||||
revisions: &mut QueryRevisions,
|
||||
value: &C::Value,
|
||||
) {
|
||||
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
|
||||
&& C::should_backdate_value(old_value, value)
|
||||
{
|
||||
log::debug!(
|
||||
"value is equal, back-dating to {:?}",
|
||||
old_memo.revisions.changed_at,
|
||||
);
|
||||
|
||||
assert!(old_memo.revisions.changed_at <= revisions.changed_at);
|
||||
revisions.changed_at = old_memo.revisions.changed_at;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
third-party/vendor/salsa-2022/src/function/delete.rs
vendored
Normal file
20
third-party/vendor/salsa-2022/src/function/delete.rs
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use crate::runtime::local_state::QueryOrigin;
|
||||
|
||||
use super::{Configuration, FunctionIngredient};
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
/// Removes the memoized value for `key` from the memo-map.
|
||||
/// Pushes the memo onto `deleted_entries` to ensure that any references into that memo which were handed out remain valid.
|
||||
pub(super) fn delete_memo(&self, key: C::Key) -> Option<QueryOrigin> {
|
||||
if let Some(memo) = self.memo_map.remove(key) {
|
||||
let origin = memo.load().revisions.origin.clone();
|
||||
self.deleted_entries.push(memo);
|
||||
Some(origin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
52
third-party/vendor/salsa-2022/src/function/diff_outputs.rs
vendored
Normal file
52
third-party/vendor/salsa-2022/src/function/diff_outputs.rs
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use crate::{
|
||||
hash::FxHashSet, key::DependencyIndex, runtime::local_state::QueryRevisions,
|
||||
storage::HasJarsDyn, Database, DatabaseKeyIndex, Event, EventKind,
|
||||
};
|
||||
|
||||
use super::{memo::Memo, Configuration, DynDb, FunctionIngredient};
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
/// Compute the old and new outputs and invoke the `clear_stale_output` callback
|
||||
/// for each output that was generated before but is not generated now.
|
||||
pub(super) fn diff_outputs(
|
||||
&self,
|
||||
db: &DynDb<'_, C>,
|
||||
key: DatabaseKeyIndex,
|
||||
old_memo: &Memo<C::Value>,
|
||||
revisions: &QueryRevisions,
|
||||
) {
|
||||
// Iterate over the outputs of the `old_memo` and put them into a hashset
|
||||
let mut old_outputs = FxHashSet::default();
|
||||
old_memo.revisions.origin.outputs().for_each(|i| {
|
||||
old_outputs.insert(i);
|
||||
});
|
||||
|
||||
// Iterate over the outputs of the current query
|
||||
// and remove elements from `old_outputs` when we find them
|
||||
for new_output in revisions.origin.outputs() {
|
||||
if old_outputs.contains(&new_output) {
|
||||
old_outputs.remove(&new_output);
|
||||
}
|
||||
}
|
||||
|
||||
for old_output in old_outputs {
|
||||
Self::report_stale_output(db, key, old_output);
|
||||
}
|
||||
}
|
||||
|
||||
fn report_stale_output(db: &DynDb<'_, C>, key: DatabaseKeyIndex, output: DependencyIndex) {
|
||||
let runtime_id = db.runtime().id();
|
||||
db.salsa_event(Event {
|
||||
runtime_id,
|
||||
kind: EventKind::WillDiscardStaleOutput {
|
||||
execute_key: key,
|
||||
output_key: output,
|
||||
},
|
||||
});
|
||||
|
||||
db.remove_stale_output(key, output);
|
||||
}
|
||||
}
|
||||
112
third-party/vendor/salsa-2022/src/function/execute.rs
vendored
Normal file
112
third-party/vendor/salsa-2022/src/function/execute.rs
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
debug::DebugWithDb,
|
||||
runtime::{local_state::ActiveQueryGuard, StampedValue},
|
||||
storage::HasJarsDyn,
|
||||
Cycle, Database, Event, EventKind,
|
||||
};
|
||||
|
||||
use super::{memo::Memo, Configuration, DynDb, FunctionIngredient};
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
/// Executes the query function for the given `active_query`. Creates and stores
|
||||
/// a new memo with the result, backdated if possible. Once this completes,
|
||||
/// the query will have been popped off the active query stack.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `db`, the database.
|
||||
/// * `active_query`, the active stack frame for the query to execute.
|
||||
/// * `opt_old_memo`, the older memo, if any existed. Used for backdated.
|
||||
pub(super) fn execute(
|
||||
&self,
|
||||
db: &DynDb<C>,
|
||||
active_query: ActiveQueryGuard<'_>,
|
||||
opt_old_memo: Option<Arc<Memo<C::Value>>>,
|
||||
) -> StampedValue<&C::Value> {
|
||||
let runtime = db.runtime();
|
||||
let revision_now = runtime.current_revision();
|
||||
let database_key_index = active_query.database_key_index;
|
||||
|
||||
log::info!("{:?}: executing query", database_key_index);
|
||||
|
||||
db.salsa_event(Event {
|
||||
runtime_id: runtime.id(),
|
||||
kind: EventKind::WillExecute {
|
||||
database_key: database_key_index,
|
||||
},
|
||||
});
|
||||
|
||||
// Query was not previously executed, or value is potentially
|
||||
// stale, or value is absent. Let's execute!
|
||||
let database_key_index = active_query.database_key_index;
|
||||
let key = C::key_from_id(database_key_index.key_index);
|
||||
let value = match Cycle::catch(|| C::execute(db, key)) {
|
||||
Ok(v) => v,
|
||||
Err(cycle) => {
|
||||
log::debug!(
|
||||
"{:?}: caught cycle {:?}, have strategy {:?}",
|
||||
database_key_index.debug(db),
|
||||
cycle,
|
||||
C::CYCLE_STRATEGY
|
||||
);
|
||||
match C::CYCLE_STRATEGY {
|
||||
crate::cycle::CycleRecoveryStrategy::Panic => cycle.throw(),
|
||||
crate::cycle::CycleRecoveryStrategy::Fallback => {
|
||||
if let Some(c) = active_query.take_cycle() {
|
||||
assert!(c.is(&cycle));
|
||||
C::recover_from_cycle(db, &cycle, key)
|
||||
} else {
|
||||
// we are not a participant in this cycle
|
||||
debug_assert!(!cycle
|
||||
.participant_keys()
|
||||
.any(|k| k == database_key_index));
|
||||
cycle.throw()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut revisions = active_query.pop(runtime);
|
||||
|
||||
// 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) = &opt_old_memo {
|
||||
self.backdate_if_appropriate(old_memo, &mut revisions, &value);
|
||||
self.diff_outputs(db, database_key_index, old_memo, &revisions);
|
||||
}
|
||||
|
||||
let value = self
|
||||
.insert_memo(
|
||||
db,
|
||||
key,
|
||||
Memo::new(Some(value), revision_now, revisions.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let stamped_value = revisions.stamped_value(value);
|
||||
|
||||
log::debug!(
|
||||
"{:?}: read_upgrade: result.revisions = {:#?}",
|
||||
database_key_index.debug(db),
|
||||
revisions
|
||||
);
|
||||
|
||||
stamped_value
|
||||
}
|
||||
}
|
||||
93
third-party/vendor/salsa-2022/src/function/fetch.rs
vendored
Normal file
93
third-party/vendor/salsa-2022/src/function/fetch.rs
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use arc_swap::Guard;
|
||||
|
||||
use crate::{database::AsSalsaDatabase, runtime::StampedValue, storage::HasJarsDyn, AsId};
|
||||
|
||||
use super::{Configuration, DynDb, FunctionIngredient};
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
pub fn fetch(&self, db: &DynDb<C>, key: C::Key) -> &C::Value {
|
||||
let runtime = db.runtime();
|
||||
|
||||
runtime.unwind_if_revision_cancelled(db);
|
||||
|
||||
let StampedValue {
|
||||
value,
|
||||
durability,
|
||||
changed_at,
|
||||
} = self.compute_value(db, key);
|
||||
|
||||
if let Some(evicted) = self.lru.record_use(key.as_id()) {
|
||||
self.evict(AsId::from_id(evicted));
|
||||
}
|
||||
|
||||
db.runtime().report_tracked_read(
|
||||
self.database_key_index(key).into(),
|
||||
durability,
|
||||
changed_at,
|
||||
);
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn compute_value(&self, db: &DynDb<C>, key: C::Key) -> StampedValue<&C::Value> {
|
||||
loop {
|
||||
if let Some(value) = self.fetch_hot(db, key).or_else(|| self.fetch_cold(db, key)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fetch_hot(&self, db: &DynDb<C>, key: C::Key) -> Option<StampedValue<&C::Value>> {
|
||||
let memo_guard = self.memo_map.get(key);
|
||||
if let Some(memo) = &memo_guard {
|
||||
if memo.value.is_some() {
|
||||
let runtime = db.runtime();
|
||||
if self.shallow_verify_memo(db, runtime, self.database_key_index(key), memo) {
|
||||
let value = unsafe {
|
||||
// Unsafety invariant: memo is present in memo_map
|
||||
self.extend_memo_lifetime(memo).unwrap()
|
||||
};
|
||||
return Some(memo.revisions.stamped_value(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn fetch_cold(&self, db: &DynDb<C>, key: C::Key) -> Option<StampedValue<&C::Value>> {
|
||||
let runtime = db.runtime();
|
||||
let database_key_index = self.database_key_index(key);
|
||||
|
||||
// Try to claim this query: if someone else has claimed it already, go back and start again.
|
||||
let _claim_guard = self
|
||||
.sync_map
|
||||
.claim(db.as_salsa_database(), database_key_index)?;
|
||||
|
||||
// Push the query on the stack.
|
||||
let active_query = runtime.push_query(database_key_index);
|
||||
|
||||
// Now that we've claimed the item, check again to see if there's a "hot" value.
|
||||
// This time we can do a *deep* verify. Because this can recurse, don't hold the arcswap guard.
|
||||
let opt_old_memo = self.memo_map.get(key).map(Guard::into_inner);
|
||||
if let Some(old_memo) = &opt_old_memo {
|
||||
if old_memo.value.is_some() && self.deep_verify_memo(db, old_memo, &active_query) {
|
||||
let value = unsafe {
|
||||
// Unsafety invariant: memo is present in memo_map.
|
||||
self.extend_memo_lifetime(old_memo).unwrap()
|
||||
};
|
||||
return Some(old_memo.revisions.stamped_value(value));
|
||||
}
|
||||
}
|
||||
|
||||
Some(self.execute(db, active_query, opt_old_memo))
|
||||
}
|
||||
|
||||
fn evict(&self, key: C::Key) {
|
||||
self.memo_map.evict(key);
|
||||
}
|
||||
}
|
||||
12
third-party/vendor/salsa-2022/src/function/inputs.rs
vendored
Normal file
12
third-party/vendor/salsa-2022/src/function/inputs.rs
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
use crate::runtime::local_state::QueryOrigin;
|
||||
|
||||
use super::{Configuration, FunctionIngredient};
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
pub(super) fn origin(&self, key: C::Key) -> Option<QueryOrigin> {
|
||||
self.memo_map.get(key).map(|m| m.revisions.origin.clone())
|
||||
}
|
||||
}
|
||||
38
third-party/vendor/salsa-2022/src/function/lru.rs
vendored
Normal file
38
third-party/vendor/salsa-2022/src/function/lru.rs
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use crate::{hash::FxLinkedHashSet, Id};
|
||||
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct Lru {
|
||||
capacity: AtomicCell<usize>,
|
||||
set: Mutex<FxLinkedHashSet<Id>>,
|
||||
}
|
||||
|
||||
impl Lru {
|
||||
pub(super) fn record_use(&self, index: Id) -> Option<Id> {
|
||||
let capacity = self.capacity.load();
|
||||
|
||||
if capacity == 0 {
|
||||
// LRU is disabled
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut set = self.set.lock();
|
||||
set.insert(index);
|
||||
if set.len() > capacity {
|
||||
return set.pop_front();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn set_capacity(&self, capacity: usize) {
|
||||
self.capacity.store(capacity);
|
||||
|
||||
if capacity == 0 {
|
||||
let mut set = self.set.lock();
|
||||
*set = FxLinkedHashSet::default();
|
||||
}
|
||||
}
|
||||
}
|
||||
223
third-party/vendor/salsa-2022/src/function/maybe_changed_after.rs
vendored
Normal file
223
third-party/vendor/salsa-2022/src/function/maybe_changed_after.rs
vendored
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
use arc_swap::Guard;
|
||||
|
||||
use crate::{
|
||||
database::AsSalsaDatabase,
|
||||
debug::DebugWithDb,
|
||||
key::DatabaseKeyIndex,
|
||||
runtime::{
|
||||
local_state::{ActiveQueryGuard, EdgeKind, QueryOrigin},
|
||||
StampedValue,
|
||||
},
|
||||
storage::HasJarsDyn,
|
||||
Revision, Runtime,
|
||||
};
|
||||
|
||||
use super::{memo::Memo, Configuration, DynDb, FunctionIngredient};
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
pub(super) fn maybe_changed_after(
|
||||
&self,
|
||||
db: &DynDb<C>,
|
||||
key: C::Key,
|
||||
revision: Revision,
|
||||
) -> bool {
|
||||
let runtime = db.runtime();
|
||||
runtime.unwind_if_revision_cancelled(db);
|
||||
|
||||
loop {
|
||||
let database_key_index = self.database_key_index(key);
|
||||
|
||||
log::debug!(
|
||||
"{:?}: maybe_changed_after(revision = {:?})",
|
||||
database_key_index.debug(db),
|
||||
revision,
|
||||
);
|
||||
|
||||
// Check if we have a verified version: this is the hot path.
|
||||
let memo_guard = self.memo_map.get(key);
|
||||
if let Some(memo) = &memo_guard {
|
||||
if self.shallow_verify_memo(db, runtime, database_key_index, memo) {
|
||||
return memo.revisions.changed_at > revision;
|
||||
}
|
||||
drop(memo_guard); // release the arc-swap guard before cold path
|
||||
if let Some(mcs) = self.maybe_changed_after_cold(db, key, revision) {
|
||||
return mcs;
|
||||
} else {
|
||||
// We failed to claim, have to retry.
|
||||
}
|
||||
} else {
|
||||
// No memo? Assume has changed.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_changed_after_cold(
|
||||
&self,
|
||||
db: &DynDb<C>,
|
||||
key_index: C::Key,
|
||||
revision: Revision,
|
||||
) -> Option<bool> {
|
||||
let runtime = db.runtime();
|
||||
let database_key_index = self.database_key_index(key_index);
|
||||
|
||||
let _claim_guard = self
|
||||
.sync_map
|
||||
.claim(db.as_salsa_database(), database_key_index)?;
|
||||
let active_query = runtime.push_query(database_key_index);
|
||||
|
||||
// Load the current memo, if any. Use a real arc, not an arc-swap guard,
|
||||
// since we may recurse.
|
||||
let old_memo = match self.memo_map.get(key_index) {
|
||||
Some(m) => Guard::into_inner(m),
|
||||
None => return Some(true),
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
"{:?}: maybe_changed_after_cold, successful claim, revision = {:?}, old_memo = {:#?}",
|
||||
database_key_index.debug(db),
|
||||
revision,
|
||||
old_memo
|
||||
);
|
||||
|
||||
// Check if the inputs are still valid and we can just compare `changed_at`.
|
||||
if self.deep_verify_memo(db, &old_memo, &active_query) {
|
||||
return Some(old_memo.revisions.changed_at > revision);
|
||||
}
|
||||
|
||||
// If inputs have changed, but we have an old value, we can re-execute.
|
||||
// It is possible the result will be equal to the old value and hence
|
||||
// backdated. In that case, although we will have computed a new memo,
|
||||
// the value has not logically changed.
|
||||
if old_memo.value.is_some() {
|
||||
let StampedValue { changed_at, .. } = self.execute(db, active_query, Some(old_memo));
|
||||
return Some(changed_at > revision);
|
||||
}
|
||||
|
||||
// Otherwise, nothing for it: have to consider the value to have changed.
|
||||
Some(true)
|
||||
}
|
||||
|
||||
/// True if the memo's value and `changed_at` time is still valid in this revision.
|
||||
/// Does only a shallow O(1) check, doesn't walk the dependencies.
|
||||
#[inline]
|
||||
pub(super) fn shallow_verify_memo(
|
||||
&self,
|
||||
db: &DynDb<C>,
|
||||
runtime: &Runtime,
|
||||
database_key_index: DatabaseKeyIndex,
|
||||
memo: &Memo<C::Value>,
|
||||
) -> bool {
|
||||
let verified_at = memo.verified_at.load();
|
||||
let revision_now = runtime.current_revision();
|
||||
|
||||
log::debug!(
|
||||
"{:?}: shallow_verify_memo(memo = {:#?})",
|
||||
database_key_index.debug(db),
|
||||
memo,
|
||||
);
|
||||
|
||||
if verified_at == revision_now {
|
||||
// Already verified.
|
||||
return true;
|
||||
}
|
||||
|
||||
if memo.check_durability(runtime) {
|
||||
// No input of the suitable durability has changed since last verified.
|
||||
memo.mark_as_verified(db.as_salsa_database(), runtime, database_key_index);
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// True if the memo's value and `changed_at` time is up to date in the current
|
||||
/// revision. When this returns true, it also updates the memo's `verified_at`
|
||||
/// field if needed to make future calls cheaper.
|
||||
///
|
||||
/// Takes an [`ActiveQueryGuard`] argument because this function recursively
|
||||
/// walks dependencies of `old_memo` and may even execute them to see if their
|
||||
/// outputs have changed. As that could lead to cycles, it is important that the
|
||||
/// query is on the stack.
|
||||
pub(super) fn deep_verify_memo(
|
||||
&self,
|
||||
db: &DynDb<C>,
|
||||
old_memo: &Memo<C::Value>,
|
||||
active_query: &ActiveQueryGuard<'_>,
|
||||
) -> bool {
|
||||
let runtime = db.runtime();
|
||||
let database_key_index = active_query.database_key_index;
|
||||
|
||||
log::debug!(
|
||||
"{:?}: deep_verify_memo(old_memo = {:#?})",
|
||||
database_key_index.debug(db),
|
||||
old_memo
|
||||
);
|
||||
|
||||
if self.shallow_verify_memo(db, runtime, database_key_index, old_memo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
match &old_memo.revisions.origin {
|
||||
QueryOrigin::Assigned(_) => {
|
||||
// If the value was assigneed by another query,
|
||||
// and that query were up-to-date,
|
||||
// then we would have updated the `verified_at` field already.
|
||||
// So the fact that we are here means that it was not specified
|
||||
// during this revision or is otherwise stale.
|
||||
return false;
|
||||
}
|
||||
QueryOrigin::BaseInput => {
|
||||
// This value was `set` by the mutator thread -- ie, it's a base input and it cannot be out of date.
|
||||
return true;
|
||||
}
|
||||
QueryOrigin::DerivedUntracked(_) => {
|
||||
// Untracked inputs? Have to assume that it changed.
|
||||
return false;
|
||||
}
|
||||
QueryOrigin::Derived(edges) => {
|
||||
// Fully tracked inputs? Iterate over the inputs and check them, one by one.
|
||||
//
|
||||
// NB: It's important here that we are iterating the inputs in the order that
|
||||
// they executed. It's possible that if the value of some input I0 is no longer
|
||||
// valid, then some later input I1 might never have executed at all, so verifying
|
||||
// it is still up to date is meaningless.
|
||||
let last_verified_at = old_memo.verified_at.load();
|
||||
for &(edge_kind, dependency_index) in edges.input_outputs.iter() {
|
||||
match edge_kind {
|
||||
EdgeKind::Input => {
|
||||
if db.maybe_changed_after(dependency_index, last_verified_at) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
EdgeKind::Output => {
|
||||
// Subtle: Mark outputs as validated now, even though we may
|
||||
// later find an input that requires us to re-execute the function.
|
||||
// Even if it re-execute, the function will wind up writing the same value,
|
||||
// since all prior inputs were green. It's important to do this during
|
||||
// this loop, because it's possible that one of our input queries will
|
||||
// re-execute and may read one of our earlier outputs
|
||||
// (e.g., in a scenario where we do something like
|
||||
// `e = Entity::new(..); query(e);` and `query` reads a field of `e`).
|
||||
//
|
||||
// NB. Accumulators are also outputs, but the above logic doesn't
|
||||
// quite apply to them. Since multiple values are pushed, the first value
|
||||
// may be unchanged, but later values could be different.
|
||||
// In that case, however, the data accumulated
|
||||
// by this function cannot be read until this function is marked green,
|
||||
// so even if we mark them as valid here, the function will re-execute
|
||||
// and overwrite the contents.
|
||||
db.mark_validated_output(database_key_index, dependency_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
old_memo.mark_as_verified(db.as_salsa_database(), runtime, database_key_index);
|
||||
true
|
||||
}
|
||||
}
|
||||
135
third-party/vendor/salsa-2022/src/function/memo.rs
vendored
Normal file
135
third-party/vendor/salsa-2022/src/function/memo.rs
vendored
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use arc_swap::{ArcSwap, Guard};
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
|
||||
use crate::{
|
||||
hash::FxDashMap, key::DatabaseKeyIndex, runtime::local_state::QueryRevisions, AsId, Event,
|
||||
EventKind, Revision, Runtime,
|
||||
};
|
||||
|
||||
/// The memo map maps from a key of type `K` to the memoized value for that `K`.
|
||||
/// The memoized value is a `Memo<V>` which contains, in addition to the value `V`,
|
||||
/// dependency information.
|
||||
pub(super) struct MemoMap<K: AsId, V> {
|
||||
map: FxDashMap<K, ArcSwap<Memo<V>>>,
|
||||
}
|
||||
|
||||
impl<K: AsId, V> Default for MemoMap<K, V> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
map: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: AsId, V> MemoMap<K, V> {
|
||||
/// Inserts the memo for the given key; (atomically) overwrites any previously existing memo.-
|
||||
#[must_use]
|
||||
pub(super) fn insert(&self, key: K, memo: Arc<Memo<V>>) -> Option<ArcSwap<Memo<V>>> {
|
||||
self.map.insert(key, ArcSwap::from(memo))
|
||||
}
|
||||
|
||||
/// Removes any existing memo for the given key.
|
||||
#[must_use]
|
||||
pub(super) fn remove(&self, key: K) -> Option<ArcSwap<Memo<V>>> {
|
||||
self.map.remove(&key).map(|o| o.1)
|
||||
}
|
||||
|
||||
/// Loads the current memo for `key_index`. This does not hold any sort of
|
||||
/// lock on the `memo_map` once it returns, so this memo could immediately
|
||||
/// become outdated if other threads store into the `memo_map`.
|
||||
pub(super) fn get(&self, key: K) -> Option<Guard<Arc<Memo<V>>>> {
|
||||
self.map.get(&key).map(|v| v.load())
|
||||
}
|
||||
|
||||
/// Evicts the existing memo for the given key, replacing it
|
||||
/// with an equivalent memo that has no value. If the memo is untracked, BaseInput,
|
||||
/// or has values assigned as output of another query, this has no effect.
|
||||
pub(super) fn evict(&self, key: K) {
|
||||
use crate::runtime::local_state::QueryOrigin;
|
||||
use dashmap::mapref::entry::Entry::*;
|
||||
|
||||
if let Occupied(entry) = self.map.entry(key) {
|
||||
let memo = entry.get().load();
|
||||
match memo.revisions.origin {
|
||||
QueryOrigin::Assigned(_)
|
||||
| QueryOrigin::DerivedUntracked(_)
|
||||
| QueryOrigin::BaseInput => {
|
||||
// Careful: Cannot evict memos whose values were
|
||||
// assigned as output of another query
|
||||
// or those with untracked inputs
|
||||
// as their values cannot be reconstructed.
|
||||
}
|
||||
|
||||
QueryOrigin::Derived(_) => {
|
||||
let memo_evicted = Arc::new(Memo::new(
|
||||
None::<V>,
|
||||
memo.verified_at.load(),
|
||||
memo.revisions.clone(),
|
||||
));
|
||||
|
||||
entry.get().store(memo_evicted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Memo<V> {
|
||||
/// The result of the query, if we decide to memoize it.
|
||||
pub(super) value: Option<V>,
|
||||
|
||||
/// Last revision when this memo was verified; this begins
|
||||
/// as the current revision.
|
||||
pub(super) verified_at: AtomicCell<Revision>,
|
||||
|
||||
/// Revision information
|
||||
pub(super) revisions: QueryRevisions,
|
||||
}
|
||||
|
||||
impl<V> Memo<V> {
|
||||
pub(super) fn new(value: Option<V>, revision_now: Revision, revisions: QueryRevisions) -> Self {
|
||||
Memo {
|
||||
value,
|
||||
verified_at: AtomicCell::new(revision_now),
|
||||
revisions,
|
||||
}
|
||||
}
|
||||
/// True if this memo is known not to have changed based on its durability.
|
||||
pub(super) fn check_durability(&self, runtime: &Runtime) -> bool {
|
||||
let last_changed = runtime.last_changed_revision(self.revisions.durability);
|
||||
let verified_at = self.verified_at.load();
|
||||
log::debug!(
|
||||
"check_durability(last_changed={:?} <= verified_at={:?}) = {:?}",
|
||||
last_changed,
|
||||
self.verified_at,
|
||||
last_changed <= verified_at,
|
||||
);
|
||||
last_changed <= verified_at
|
||||
}
|
||||
|
||||
/// Mark memo as having been verified in the `revision_now`, which should
|
||||
/// be the current revision.
|
||||
pub(super) fn mark_as_verified(
|
||||
&self,
|
||||
db: &dyn crate::Database,
|
||||
runtime: &crate::Runtime,
|
||||
database_key_index: DatabaseKeyIndex,
|
||||
) {
|
||||
db.salsa_event(Event {
|
||||
runtime_id: runtime.id(),
|
||||
kind: EventKind::DidValidateMemoizedValue {
|
||||
database_key: database_key_index,
|
||||
},
|
||||
});
|
||||
|
||||
self.verified_at.store(runtime.current_revision());
|
||||
|
||||
// Also mark the outputs as verified
|
||||
for output in self.revisions.origin.outputs() {
|
||||
db.mark_validated_output(database_key_index, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
141
third-party/vendor/salsa-2022/src/function/specify.rs
vendored
Normal file
141
third-party/vendor/salsa-2022/src/function/specify.rs
vendored
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
use crossbeam::atomic::AtomicCell;
|
||||
|
||||
use crate::{
|
||||
database::AsSalsaDatabase,
|
||||
runtime::local_state::{QueryOrigin, QueryRevisions},
|
||||
storage::HasJarsDyn,
|
||||
tracked_struct::TrackedStructInDb,
|
||||
DatabaseKeyIndex, DebugWithDb,
|
||||
};
|
||||
|
||||
use super::{memo::Memo, Configuration, DynDb, FunctionIngredient};
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
/// Specifies the value of the function for the given key.
|
||||
/// This is a way to imperatively set the value of a function.
|
||||
/// It only works if the key is a tracked struct created in the current query.
|
||||
pub(crate) fn specify<'db>(
|
||||
&self,
|
||||
db: &'db DynDb<'db, C>,
|
||||
key: C::Key,
|
||||
value: C::Value,
|
||||
origin: impl Fn(DatabaseKeyIndex) -> QueryOrigin,
|
||||
) where
|
||||
C::Key: TrackedStructInDb<DynDb<'db, C>>,
|
||||
{
|
||||
let runtime = db.runtime();
|
||||
|
||||
let (active_query_key, current_deps) = match runtime.active_query() {
|
||||
Some(v) => v,
|
||||
None => panic!("can only use `specify` with an active query"),
|
||||
};
|
||||
|
||||
// `specify` only works if the key is a tracked struct created in the current query.
|
||||
//
|
||||
// The reason is this. We want to ensure that the same result is reached regardless of
|
||||
// the "path" that the user takes through the execution graph.
|
||||
// If you permit values to be specified from other queries, you can have a situation like this:
|
||||
// * Q0 creates the tracked struct T0
|
||||
// * Q1 specifies the value for F(T0)
|
||||
// * Q2 invokes F(T0)
|
||||
// * Q3 invokes Q1 and then Q2
|
||||
// * Q4 invokes Q2 and then Q1
|
||||
//
|
||||
// Now, if We invoke Q3 first, We get one result for Q2, but if We invoke Q4 first, We get a different value. That's no good.
|
||||
let database_key_index = key.database_key_index(db);
|
||||
let dependency_index = database_key_index.into();
|
||||
if !runtime.is_output_of_active_query(dependency_index) {
|
||||
panic!("can only use `specfiy` on entities created during current query");
|
||||
}
|
||||
|
||||
// Subtle: we treat the "input" to a set query as if it were
|
||||
// volatile.
|
||||
//
|
||||
// The idea is this. You have the current query C that
|
||||
// created the entity E, and it is setting the value F(E) of the function F.
|
||||
// When some other query R reads the field F(E), in order to have obtained
|
||||
// the entity E, it has to have executed the query C.
|
||||
//
|
||||
// This will have forced C to either:
|
||||
//
|
||||
// - not create E this time, in which case R shouldn't have it (some kind of leak has occurred)
|
||||
// - assign a value to F(E), in which case `verified_at` will be the current revision and `changed_at` will be updated appropriately
|
||||
// - NOT assign a value to F(E), in which case we need to re-execute the function (which typically panics).
|
||||
//
|
||||
// So, ruling out the case of a leak having occurred, that means that the reader R will either see:
|
||||
//
|
||||
// - a result that is verified in the current revision, because it was set, which will use the set value
|
||||
// - a result that is NOT verified and has untracked inputs, which will re-execute (and likely panic)
|
||||
|
||||
let revision = runtime.current_revision();
|
||||
let mut revisions = QueryRevisions {
|
||||
changed_at: current_deps.changed_at,
|
||||
durability: current_deps.durability,
|
||||
origin: origin(active_query_key),
|
||||
};
|
||||
|
||||
if let Some(old_memo) = self.memo_map.get(key) {
|
||||
self.backdate_if_appropriate(&old_memo, &mut revisions, &value);
|
||||
self.diff_outputs(db, database_key_index, &old_memo, &revisions);
|
||||
}
|
||||
|
||||
let memo = Memo {
|
||||
value: Some(value),
|
||||
verified_at: AtomicCell::new(revision),
|
||||
revisions,
|
||||
};
|
||||
|
||||
log::debug!("specify: about to add memo {:#?} for key {:?}", memo, key);
|
||||
self.insert_memo(db, key, memo);
|
||||
}
|
||||
|
||||
/// 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>(&self, db: &'db DynDb<'db, C>, key: C::Key, value: C::Value)
|
||||
where
|
||||
C::Key: TrackedStructInDb<DynDb<'db, C>>,
|
||||
{
|
||||
self.specify(db, key, value, |database_key_index| {
|
||||
QueryOrigin::Assigned(database_key_index)
|
||||
});
|
||||
|
||||
// Record that the current query *specified* a value for this cell.
|
||||
let database_key_index = self.database_key_index(key);
|
||||
db.runtime().add_output(database_key_index.into());
|
||||
}
|
||||
|
||||
/// Invoked when the query `executor` has been validated as having green inputs
|
||||
/// and `key` is a value that was specified by `executor`.
|
||||
/// Marks `key` as valid in the current revision since if `executor` had re-executed,
|
||||
/// it would have specified `key` again.
|
||||
pub(super) fn validate_specified_value(
|
||||
&self,
|
||||
db: &DynDb<'_, C>,
|
||||
executor: DatabaseKeyIndex,
|
||||
key: C::Key,
|
||||
) {
|
||||
let runtime = db.runtime();
|
||||
|
||||
let memo = match self.memo_map.get(key) {
|
||||
Some(m) => m,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// If we are marking this as validated, it must be a value that was
|
||||
// assigneed by `executor`.
|
||||
match memo.revisions.origin {
|
||||
QueryOrigin::Assigned(by_query) => assert_eq!(by_query, executor),
|
||||
_ => panic!(
|
||||
"expected a query assigned by `{:?}`, not `{:?}`",
|
||||
executor.debug(db),
|
||||
memo.revisions.origin,
|
||||
),
|
||||
}
|
||||
|
||||
let database_key_index = self.database_key_index(key);
|
||||
memo.mark_as_verified(db.as_salsa_database(), runtime, database_key_index);
|
||||
}
|
||||
}
|
||||
41
third-party/vendor/salsa-2022/src/function/store.rs
vendored
Normal file
41
third-party/vendor/salsa-2022/src/function/store.rs
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
|
||||
use crate::{
|
||||
durability::Durability,
|
||||
runtime::local_state::{QueryOrigin, QueryRevisions},
|
||||
Runtime,
|
||||
};
|
||||
|
||||
use super::{memo::Memo, Configuration, FunctionIngredient};
|
||||
|
||||
impl<C> FunctionIngredient<C>
|
||||
where
|
||||
C: Configuration,
|
||||
{
|
||||
pub fn store(
|
||||
&mut self,
|
||||
runtime: &mut Runtime,
|
||||
key: C::Key,
|
||||
value: C::Value,
|
||||
durability: Durability,
|
||||
) {
|
||||
let revision = runtime.current_revision();
|
||||
let memo = Memo {
|
||||
value: Some(value),
|
||||
verified_at: AtomicCell::new(revision),
|
||||
revisions: QueryRevisions {
|
||||
changed_at: revision,
|
||||
durability,
|
||||
origin: QueryOrigin::BaseInput,
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(old_value) = self.memo_map.insert(key, Arc::new(memo)) {
|
||||
// NB: we don't have to store `old_value` into `deleted_entries` because we have `&mut self`.
|
||||
let durability = old_value.load().revisions.durability;
|
||||
runtime.report_tracked_write(durability);
|
||||
}
|
||||
}
|
||||
}
|
||||
90
third-party/vendor/salsa-2022/src/function/sync.rs
vendored
Normal file
90
third-party/vendor/salsa-2022/src/function/sync.rs
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::{
|
||||
hash::FxDashMap,
|
||||
key::DatabaseKeyIndex,
|
||||
runtime::{RuntimeId, WaitResult},
|
||||
Database, Id, Runtime,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct SyncMap {
|
||||
sync_map: FxDashMap<Id, SyncState>,
|
||||
}
|
||||
|
||||
struct SyncState {
|
||||
id: RuntimeId,
|
||||
|
||||
/// Set to true if any other queries are blocked,
|
||||
/// waiting for this query to complete.
|
||||
anyone_waiting: AtomicBool,
|
||||
}
|
||||
|
||||
impl SyncMap {
|
||||
pub(super) fn claim<'me>(
|
||||
&'me self,
|
||||
db: &'me dyn Database,
|
||||
database_key_index: DatabaseKeyIndex,
|
||||
) -> Option<ClaimGuard<'me>> {
|
||||
let runtime = db.runtime();
|
||||
match self.sync_map.entry(database_key_index.key_index) {
|
||||
dashmap::mapref::entry::Entry::Vacant(entry) => {
|
||||
entry.insert(SyncState {
|
||||
id: runtime.id(),
|
||||
anyone_waiting: AtomicBool::new(false),
|
||||
});
|
||||
Some(ClaimGuard {
|
||||
database_key: database_key_index,
|
||||
runtime,
|
||||
sync_map: &self.sync_map,
|
||||
})
|
||||
}
|
||||
dashmap::mapref::entry::Entry::Occupied(entry) => {
|
||||
// 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.
|
||||
entry.get().anyone_waiting.store(true, Ordering::Relaxed);
|
||||
let other_id = entry.get().id;
|
||||
runtime.block_on_or_unwind(db, database_key_index, other_id, entry);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks an active 'claim' in the synchronization map. The claim is
|
||||
/// released when this value is dropped.
|
||||
#[must_use]
|
||||
pub(super) struct ClaimGuard<'me> {
|
||||
database_key: DatabaseKeyIndex,
|
||||
runtime: &'me Runtime,
|
||||
sync_map: &'me FxDashMap<Id, SyncState>,
|
||||
}
|
||||
|
||||
impl<'me> ClaimGuard<'me> {
|
||||
fn remove_from_map_and_unblock_queries(&self, wait_result: WaitResult) {
|
||||
let (_, SyncState { anyone_waiting, .. }) =
|
||||
self.sync_map.remove(&self.database_key.key_index).unwrap();
|
||||
|
||||
// NB: `Ordering::Relaxed` is sufficient here,
|
||||
// see `store` above for explanation.
|
||||
if anyone_waiting.load(Ordering::Relaxed) {
|
||||
self.runtime
|
||||
.unblock_queries_blocked_on(self.database_key, wait_result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'me> Drop for ClaimGuard<'me> {
|
||||
fn drop(&mut self) {
|
||||
let wait_result = if std::thread::panicking() {
|
||||
WaitResult::Panicked
|
||||
} else {
|
||||
WaitResult::Completed
|
||||
};
|
||||
self.remove_from_map_and_unblock_queries(wait_result)
|
||||
}
|
||||
}
|
||||
14
third-party/vendor/salsa-2022/src/hash.rs
vendored
Normal file
14
third-party/vendor/salsa-2022/src/hash.rs
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
|
||||
pub(crate) type FxHasher = std::hash::BuildHasherDefault<rustc_hash::FxHasher>;
|
||||
pub(crate) type FxIndexSet<K> = indexmap::IndexSet<K, FxHasher>;
|
||||
pub(crate) type FxIndexMap<K, V> = indexmap::IndexMap<K, V, FxHasher>;
|
||||
pub(crate) type FxDashMap<K, V> = dashmap::DashMap<K, V, FxHasher>;
|
||||
pub(crate) type FxLinkedHashSet<K> = hashlink::LinkedHashSet<K, FxHasher>;
|
||||
pub(crate) type FxHashSet<K> = std::collections::HashSet<K, FxHasher>;
|
||||
|
||||
pub(crate) fn hash<T: Hash>(t: &T) -> u64 {
|
||||
let mut hasher = FxHasher::default().build_hasher();
|
||||
t.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
94
third-party/vendor/salsa-2022/src/id.rs
vendored
Normal file
94
third-party/vendor/salsa-2022/src/id.rs
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
/// An Id is a newtype'd u32 ranging from `0..Id::MAX_U32`.
|
||||
/// The maximum range is smaller than a standard u32 to leave
|
||||
/// room for niches; currently there is only one niche, so that
|
||||
/// `Option<Id>` is the same size as an `Id`.
|
||||
///
|
||||
/// You will rarely use the `Id` type directly, though you can.
|
||||
/// You are more likely to use types that implement the `AsId` trait,
|
||||
/// such as entity keys.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct Id {
|
||||
value: NonZeroU32,
|
||||
}
|
||||
|
||||
impl Id {
|
||||
pub const MAX_U32: u32 = std::u32::MAX - 0xFF;
|
||||
pub const MAX_USIZE: usize = Self::MAX_U32 as usize;
|
||||
|
||||
/// Create a `salsa::Id` from a u32 value. This value should
|
||||
/// be less than [`Self::MAX_U32`].
|
||||
///
|
||||
/// In general, you should not need to create salsa ids yourself,
|
||||
/// but it can be useful if you are using the type as a general
|
||||
/// purpose "identifier" internally.
|
||||
#[track_caller]
|
||||
pub const fn from_u32(x: u32) -> Self {
|
||||
Id {
|
||||
value: match NonZeroU32::new(x + 1) {
|
||||
Some(v) => v,
|
||||
None => panic!("given value is too large to be a `salsa::Id`"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn as_u32(self) -> u32 {
|
||||
self.value.get() - 1
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Id {
|
||||
fn from(n: u32) -> Self {
|
||||
Id::from_u32(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for Id {
|
||||
fn from(n: usize) -> Self {
|
||||
assert!(n < Id::MAX_USIZE);
|
||||
Id::from_u32(n as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for u32 {
|
||||
fn from(n: Id) -> Self {
|
||||
n.as_u32()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for usize {
|
||||
fn from(n: Id) -> usize {
|
||||
n.as_u32() as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that can be interconverted to a salsa Id;
|
||||
pub trait AsId: Sized + Copy + Eq + Hash + Debug {
|
||||
fn as_id(self) -> Id;
|
||||
fn from_id(id: Id) -> Self;
|
||||
}
|
||||
|
||||
impl AsId for Id {
|
||||
fn as_id(self) -> Id {
|
||||
self
|
||||
}
|
||||
|
||||
fn from_id(id: Id) -> Self {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
/// As a special case, we permit `()` to be converted to an `Id`.
|
||||
/// This is useful for declaring functions with no arguments.
|
||||
impl AsId for () {
|
||||
fn as_id(self) -> Id {
|
||||
Id::from_u32(0)
|
||||
}
|
||||
|
||||
fn from_id(id: Id) -> Self {
|
||||
assert_eq!(0, id.as_u32());
|
||||
}
|
||||
}
|
||||
90
third-party/vendor/salsa-2022/src/ingredient.rs
vendored
Normal file
90
third-party/vendor/salsa-2022/src/ingredient.rs
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::{
|
||||
cycle::CycleRecoveryStrategy, key::DependencyIndex, runtime::local_state::QueryOrigin,
|
||||
DatabaseKeyIndex, Id, IngredientIndex,
|
||||
};
|
||||
|
||||
use super::Revision;
|
||||
|
||||
/// "Ingredients" are the bits of data that are stored within the database to make salsa work.
|
||||
/// Each jar will define some number of ingredients that it requires.
|
||||
/// Each use salsa macro (e.g., `#[salsa::tracked]`, `#[salsa::interned]`) adds one or more
|
||||
/// ingredients to the jar struct that together are used to create the salsa concept.
|
||||
/// For example, a tracked struct defines a [`crate::interned::InternedIngredient`] to store
|
||||
/// its identity plus [`crate::function::FunctionIngredient`] values to store its fields.
|
||||
/// The exact ingredients are determined by
|
||||
/// [`IngredientsFor`](`crate::storage::IngredientsFor`) implementations generated by the
|
||||
/// macro.
|
||||
pub trait Ingredient<DB: ?Sized> {
|
||||
/// Returns the [`IngredientIndex`] of this ingredient.
|
||||
fn ingredient_index(&self) -> IngredientIndex;
|
||||
|
||||
/// If this ingredient is a participant in a cycle, what is its cycle recovery strategy?
|
||||
/// (Really only relevant to [`crate::function::FunctionIngredient`],
|
||||
/// since only function ingredients push themselves onto the active query stack.)
|
||||
fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy;
|
||||
|
||||
/// Has the value for `input` in this ingredient changed after `revision`?
|
||||
fn maybe_changed_after(&self, db: &DB, input: DependencyIndex, revision: Revision) -> bool;
|
||||
|
||||
/// What were the inputs (if any) that were used to create the value at `key_index`.
|
||||
fn origin(&self, key_index: Id) -> Option<QueryOrigin>;
|
||||
|
||||
/// Invoked when the value `output_key` should be marked as valid in the current revision.
|
||||
/// This occurs because the value for `executor`, which generated it, was marked as valid
|
||||
/// in the current revision.
|
||||
fn mark_validated_output(&self, db: &DB, executor: DatabaseKeyIndex, output_key: Option<Id>);
|
||||
|
||||
/// Invoked when the value `stale_output` was output by `executor` in a previous
|
||||
/// revision, but was NOT output in the current revision.
|
||||
///
|
||||
/// This hook is used to clear out the stale value so others cannot read it.
|
||||
fn remove_stale_output(
|
||||
&self,
|
||||
db: &DB,
|
||||
executor: DatabaseKeyIndex,
|
||||
stale_output_key: Option<Id>,
|
||||
);
|
||||
|
||||
/// Informs the ingredient `self` that the salsa struct with id `id` has been deleted.
|
||||
/// 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);
|
||||
|
||||
/// Invoked when a new revision is about to start.
|
||||
/// This moment is important because it means that we have an `&mut`-reference to the
|
||||
/// database, and hence any pre-existing `&`-references must have expired.
|
||||
/// Many ingredients, given an `&'db`-reference to the database,
|
||||
/// use unsafe code to return `&'db`-references to internal values.
|
||||
/// The backing memory for those values can only be freed once an `&mut`-reference to the
|
||||
/// database is created.
|
||||
///
|
||||
/// **Important:** to actually receive resets, the ingredient must set
|
||||
/// [`IngredientRequiresReset::RESET_ON_NEW_REVISION`] to true.
|
||||
fn reset_for_new_revision(&mut self);
|
||||
|
||||
fn fmt_index(&self, index: Option<crate::Id>, fmt: &mut fmt::Formatter<'_>) -> fmt::Result;
|
||||
}
|
||||
|
||||
/// A helper function to show human readable fmt.
|
||||
pub(crate) fn fmt_index(
|
||||
debug_name: &str,
|
||||
id: Option<Id>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> fmt::Result {
|
||||
if let Some(i) = id {
|
||||
write!(fmt, "{}({})", debug_name, u32::from(i))
|
||||
} else {
|
||||
write!(fmt, "{}()", debug_name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a const indicating if an ingredient needs to be reset each round.
|
||||
/// This const probably *should* be a member of `Ingredient` trait but then `Ingredient` would
|
||||
/// not be dyn-safe.
|
||||
pub trait IngredientRequiresReset {
|
||||
/// If this is true, then `reset_for_new_revision` will be called every new revision.
|
||||
const RESET_ON_NEW_REVISION: bool;
|
||||
}
|
||||
83
third-party/vendor/salsa-2022/src/ingredient_list.rs
vendored
Normal file
83
third-party/vendor/salsa-2022/src/ingredient_list.rs
vendored
Normal file
|
|
@ -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<Vec<IngredientIndex>>,
|
||||
}
|
||||
|
||||
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<Item = IngredientIndex> {
|
||||
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<Vec<IngredientIndex>> = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
third-party/vendor/salsa-2022/src/input.rs
vendored
Normal file
133
third-party/vendor/salsa-2022/src/input.rs
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use std::{
|
||||
fmt,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cycle::CycleRecoveryStrategy,
|
||||
ingredient::{fmt_index, Ingredient, IngredientRequiresReset},
|
||||
key::{DatabaseKeyIndex, DependencyIndex},
|
||||
runtime::{local_state::QueryOrigin, Runtime},
|
||||
AsId, IngredientIndex, Revision,
|
||||
};
|
||||
|
||||
pub trait InputId: AsId {}
|
||||
impl<T: AsId> InputId for T {}
|
||||
|
||||
pub struct InputIngredient<Id>
|
||||
where
|
||||
Id: InputId,
|
||||
{
|
||||
ingredient_index: IngredientIndex,
|
||||
counter: AtomicU32,
|
||||
debug_name: &'static str,
|
||||
_phantom: std::marker::PhantomData<Id>,
|
||||
}
|
||||
|
||||
impl<Id> InputIngredient<Id>
|
||||
where
|
||||
Id: InputId,
|
||||
{
|
||||
pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self {
|
||||
Self {
|
||||
ingredient_index: index,
|
||||
counter: Default::default(),
|
||||
debug_name,
|
||||
_phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn database_key_index(&self, id: Id) -> DatabaseKeyIndex {
|
||||
DatabaseKeyIndex {
|
||||
ingredient_index: self.ingredient_index,
|
||||
key_index: id.as_id(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_input(&self, _runtime: &Runtime) -> Id {
|
||||
let next_id = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||
Id::from_id(crate::Id::from_u32(next_id))
|
||||
}
|
||||
|
||||
pub fn new_singleton_input(&self, _runtime: &Runtime) -> Id {
|
||||
// when one exists already, panic
|
||||
if self.counter.load(Ordering::Relaxed) >= 1 {
|
||||
panic!("singleton struct may not be duplicated");
|
||||
}
|
||||
// fresh new ingredient
|
||||
self.counter.store(1, Ordering::Relaxed);
|
||||
Id::from_id(crate::Id::from_u32(0))
|
||||
}
|
||||
|
||||
pub fn get_singleton_input(&self, _runtime: &Runtime) -> Option<Id> {
|
||||
(self.counter.load(Ordering::Relaxed) > 0).then(|| Id::from_id(crate::Id::from_u32(0)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB: ?Sized, Id> Ingredient<DB> for InputIngredient<Id>
|
||||
where
|
||||
Id: InputId,
|
||||
{
|
||||
fn ingredient_index(&self) -> IngredientIndex {
|
||||
self.ingredient_index
|
||||
}
|
||||
|
||||
fn maybe_changed_after(&self, _db: &DB, _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
|
||||
}
|
||||
|
||||
fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy {
|
||||
CycleRecoveryStrategy::Panic
|
||||
}
|
||||
|
||||
fn origin(&self, _key_index: crate::Id) -> Option<QueryOrigin> {
|
||||
None
|
||||
}
|
||||
|
||||
fn mark_validated_output(
|
||||
&self,
|
||||
_db: &DB,
|
||||
executor: DatabaseKeyIndex,
|
||||
output_key: Option<crate::Id>,
|
||||
) {
|
||||
unreachable!(
|
||||
"mark_validated_output({:?}, {:?}): input cannot be the output of a tracked function",
|
||||
executor, output_key
|
||||
);
|
||||
}
|
||||
|
||||
fn remove_stale_output(
|
||||
&self,
|
||||
_db: &DB,
|
||||
executor: DatabaseKeyIndex,
|
||||
stale_output_key: Option<crate::Id>,
|
||||
) {
|
||||
unreachable!(
|
||||
"remove_stale_output({:?}, {:?}): input cannot be the output of a tracked function",
|
||||
executor, stale_output_key
|
||||
);
|
||||
}
|
||||
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
panic!("unexpected call to `reset_for_new_revision`")
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) {
|
||||
panic!(
|
||||
"unexpected call: input 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(self.debug_name, index, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Id> IngredientRequiresReset for InputIngredient<Id>
|
||||
where
|
||||
Id: InputId,
|
||||
{
|
||||
const RESET_ON_NEW_REVISION: bool = false;
|
||||
}
|
||||
170
third-party/vendor/salsa-2022/src/input_field.rs
vendored
Normal file
170
third-party/vendor/salsa-2022/src/input_field.rs
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
use crate::cycle::CycleRecoveryStrategy;
|
||||
use crate::ingredient::{fmt_index, Ingredient, IngredientRequiresReset};
|
||||
use crate::key::DependencyIndex;
|
||||
use crate::runtime::local_state::QueryOrigin;
|
||||
use crate::runtime::StampedValue;
|
||||
use crate::{AsId, DatabaseKeyIndex, Durability, Id, IngredientIndex, Revision, Runtime};
|
||||
use dashmap::mapref::entry::Entry;
|
||||
use dashmap::DashMap;
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Ingredient used to represent the fields of a `#[salsa::input]`.
|
||||
///
|
||||
/// These fields can only be mutated by a call to a setter with an `&mut`
|
||||
/// reference to the database, and therefore cannot be mutated during a tracked
|
||||
/// function or in parallel.
|
||||
/// However for on-demand inputs to work the fields must be able to be set via
|
||||
/// a shared reference, so some locking is required.
|
||||
/// Altogether this makes the implementation somewhat simpler than tracked
|
||||
/// structs.
|
||||
pub struct InputFieldIngredient<K, F> {
|
||||
index: IngredientIndex,
|
||||
map: DashMap<K, Box<StampedValue<F>>>,
|
||||
debug_name: &'static str,
|
||||
}
|
||||
|
||||
impl<K, F> InputFieldIngredient<K, F>
|
||||
where
|
||||
K: Eq + Hash + AsId,
|
||||
{
|
||||
pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self {
|
||||
Self {
|
||||
index,
|
||||
map: Default::default(),
|
||||
debug_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store_mut(
|
||||
&mut self,
|
||||
runtime: &Runtime,
|
||||
key: K,
|
||||
value: F,
|
||||
durability: Durability,
|
||||
) -> Option<F> {
|
||||
let revision = runtime.current_revision();
|
||||
let stamped_value = Box::new(StampedValue {
|
||||
value,
|
||||
durability,
|
||||
changed_at: revision,
|
||||
});
|
||||
|
||||
self.map
|
||||
.insert(key, stamped_value)
|
||||
.map(|old_value| old_value.value)
|
||||
}
|
||||
|
||||
/// Set the field of a new input.
|
||||
///
|
||||
/// This function panics if the field has ever been set before.
|
||||
pub fn store_new(&self, runtime: &Runtime, key: K, value: F, durability: Durability) {
|
||||
let revision = runtime.current_revision();
|
||||
let stamped_value = Box::new(StampedValue {
|
||||
value,
|
||||
durability,
|
||||
changed_at: revision,
|
||||
});
|
||||
|
||||
match self.map.entry(key) {
|
||||
Entry::Occupied(_) => {
|
||||
panic!("attempted to set field of existing input using `store_new`, use `store_mut` instead");
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(stamped_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetch<'db>(&'db self, runtime: &'db Runtime, key: K) -> &F {
|
||||
let StampedValue {
|
||||
value,
|
||||
durability,
|
||||
changed_at,
|
||||
} = &**self.map.get(&key).unwrap();
|
||||
|
||||
runtime.report_tracked_read(
|
||||
self.database_key_index(key).into(),
|
||||
*durability,
|
||||
*changed_at,
|
||||
);
|
||||
|
||||
// SAFETY:
|
||||
// The value is stored in a box so internal moves in the dashmap don't
|
||||
// invalidate the reference to the value inside the box.
|
||||
// Values are only removed or altered when we have `&mut self`.
|
||||
unsafe { transmute_lifetime(self, value) }
|
||||
}
|
||||
|
||||
fn database_key_index(&self, key: K) -> DatabaseKeyIndex {
|
||||
DatabaseKeyIndex {
|
||||
ingredient_index: self.index,
|
||||
key_index: key.as_id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns `u` but with the lifetime of `t`.
|
||||
//
|
||||
// Safe if you know that data at `u` will remain shared
|
||||
// until the reference `t` expires.
|
||||
unsafe fn transmute_lifetime<'t, 'u, T, U>(_t: &'t T, u: &'u U) -> &'t U {
|
||||
std::mem::transmute(u)
|
||||
}
|
||||
|
||||
impl<DB: ?Sized, K, F> Ingredient<DB> for InputFieldIngredient<K, F>
|
||||
where
|
||||
K: AsId,
|
||||
{
|
||||
fn ingredient_index(&self) -> IngredientIndex {
|
||||
self.index
|
||||
}
|
||||
|
||||
fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy {
|
||||
CycleRecoveryStrategy::Panic
|
||||
}
|
||||
|
||||
fn maybe_changed_after(&self, _db: &DB, input: DependencyIndex, revision: Revision) -> bool {
|
||||
let key = K::from_id(input.key_index.unwrap());
|
||||
self.map.get(&key).unwrap().changed_at > revision
|
||||
}
|
||||
|
||||
fn origin(&self, _key_index: Id) -> Option<QueryOrigin> {
|
||||
None
|
||||
}
|
||||
|
||||
fn mark_validated_output(
|
||||
&self,
|
||||
_db: &DB,
|
||||
_executor: DatabaseKeyIndex,
|
||||
_output_key: Option<Id>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn remove_stale_output(
|
||||
&self,
|
||||
_db: &DB,
|
||||
_executor: DatabaseKeyIndex,
|
||||
_stale_output_key: Option<Id>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, _db: &DB, _id: Id) {
|
||||
panic!("unexpected call: input fields are never deleted");
|
||||
}
|
||||
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
panic!("unexpected call: input fields don't register for resets");
|
||||
}
|
||||
|
||||
fn fmt_index(&self, index: Option<crate::Id>, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt_index(self.debug_name, index, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, F> IngredientRequiresReset for InputFieldIngredient<K, F>
|
||||
where
|
||||
K: AsId,
|
||||
{
|
||||
const RESET_ON_NEW_REVISION: bool = false;
|
||||
}
|
||||
278
third-party/vendor/salsa-2022/src/interned.rs
vendored
Normal file
278
third-party/vendor/salsa-2022/src/interned.rs
vendored
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
use crossbeam::atomic::AtomicCell;
|
||||
use crossbeam::queue::SegQueue;
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::durability::Durability;
|
||||
use crate::id::AsId;
|
||||
use crate::ingredient::{fmt_index, IngredientRequiresReset};
|
||||
use crate::key::DependencyIndex;
|
||||
use crate::runtime::local_state::QueryOrigin;
|
||||
use crate::runtime::Runtime;
|
||||
use crate::DatabaseKeyIndex;
|
||||
|
||||
use super::hash::FxDashMap;
|
||||
use super::ingredient::Ingredient;
|
||||
use super::routes::IngredientIndex;
|
||||
use super::Revision;
|
||||
|
||||
pub trait InternedId: AsId {}
|
||||
impl<T: AsId> InternedId for T {}
|
||||
|
||||
pub trait InternedData: Sized + Eq + Hash + Clone {}
|
||||
impl<T: Eq + Hash + Clone> InternedData for T {}
|
||||
|
||||
/// The interned ingredient has the job of hashing values of type `Data` to produce an `Id`.
|
||||
/// It used to store interned structs but also to store the id fields of a tracked struct.
|
||||
/// Interned values endure until they are explicitly removed in some way.
|
||||
pub struct InternedIngredient<Id: InternedId, Data: InternedData> {
|
||||
/// Index of this ingredient in the database (used to construct database-ids, etc).
|
||||
ingredient_index: IngredientIndex,
|
||||
|
||||
/// Maps from data to the existing interned id for that data.
|
||||
///
|
||||
/// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa.
|
||||
key_map: FxDashMap<Data, Id>,
|
||||
|
||||
/// Maps from an interned id to its data.
|
||||
///
|
||||
/// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa.
|
||||
value_map: FxDashMap<Id, Box<Data>>,
|
||||
|
||||
/// counter for the next id.
|
||||
counter: AtomicCell<u32>,
|
||||
|
||||
/// Stores the revision when this interned ingredient was last cleared.
|
||||
/// You can clear an interned table at any point, deleting all its entries,
|
||||
/// but that will make anything dependent on those entries dirty and in need
|
||||
/// of being recomputed.
|
||||
reset_at: Revision,
|
||||
|
||||
/// When specific entries are deleted from the interned table, their data is added
|
||||
/// to this vector rather than being immediately freed. This is because we may` have
|
||||
/// references to that data floating about that are tied to the lifetime of some
|
||||
/// `&db` reference. This queue itself is not freed until we have an `&mut db` reference,
|
||||
/// guaranteeing that there are no more references to it.
|
||||
deleted_entries: SegQueue<Box<Data>>,
|
||||
|
||||
debug_name: &'static str,
|
||||
}
|
||||
|
||||
impl<Id, Data> InternedIngredient<Id, Data>
|
||||
where
|
||||
Id: InternedId,
|
||||
Data: InternedData,
|
||||
{
|
||||
pub fn new(ingredient_index: IngredientIndex, debug_name: &'static str) -> Self {
|
||||
Self {
|
||||
ingredient_index,
|
||||
key_map: Default::default(),
|
||||
value_map: Default::default(),
|
||||
counter: AtomicCell::default(),
|
||||
reset_at: Revision::start(),
|
||||
deleted_entries: Default::default(),
|
||||
debug_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intern(&self, runtime: &Runtime, data: Data) -> Id {
|
||||
runtime.report_tracked_read(
|
||||
DependencyIndex::for_table(self.ingredient_index),
|
||||
Durability::MAX,
|
||||
self.reset_at,
|
||||
);
|
||||
|
||||
// Optimisation to only get read lock on the map if the data has already
|
||||
// been interned.
|
||||
if let Some(id) = self.key_map.get(&data) {
|
||||
return *id;
|
||||
}
|
||||
|
||||
match self.key_map.entry(data.clone()) {
|
||||
// Data has been interned by a racing call, use that ID instead
|
||||
dashmap::mapref::entry::Entry::Occupied(entry) => *entry.get(),
|
||||
// We won any races so should intern the data
|
||||
dashmap::mapref::entry::Entry::Vacant(entry) => {
|
||||
let next_id = self.counter.fetch_add(1);
|
||||
let next_id = Id::from_id(crate::id::Id::from_u32(next_id));
|
||||
let old_value = self.value_map.insert(next_id, Box::new(data));
|
||||
assert!(
|
||||
old_value.is_none(),
|
||||
"next_id is guaranteed to be unique, bar overflow"
|
||||
);
|
||||
entry.insert(next_id);
|
||||
next_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn reset_at(&self) -> Revision {
|
||||
self.reset_at
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, revision: Revision) {
|
||||
assert!(revision > self.reset_at);
|
||||
self.reset_at = revision;
|
||||
self.key_map.clear();
|
||||
self.value_map.clear();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn data<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db Data {
|
||||
runtime.report_tracked_read(
|
||||
DependencyIndex::for_table(self.ingredient_index),
|
||||
Durability::MAX,
|
||||
self.reset_at,
|
||||
);
|
||||
|
||||
let data = match self.value_map.get(&id) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
panic!("no data found for id `{:?}`", id)
|
||||
}
|
||||
};
|
||||
|
||||
// Unsafety clause:
|
||||
//
|
||||
// * Values are only removed or altered when we have `&mut self`
|
||||
unsafe { transmute_lifetime(self, &**data) }
|
||||
}
|
||||
|
||||
/// Get the ingredient index for this table.
|
||||
pub(super) fn ingredient_index(&self) -> IngredientIndex {
|
||||
self.ingredient_index
|
||||
}
|
||||
|
||||
/// Deletes an index from the interning table, making it available for re-use.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This should only be used when you are certain that:
|
||||
/// 1. The given `id` has not (and will not) be used in the current revision.
|
||||
/// 2. The interned data corresponding to `id` will not be interned in this revision.
|
||||
///
|
||||
/// More specifically, this is used when a query `Q` executes and we can compare the
|
||||
/// entities `E_now` that it produced in this revision vs the entities `E_prev` it
|
||||
/// produced in the last revision. Any missing entities `E_prev - E_new` can be deleted.
|
||||
///
|
||||
/// If you are wrong about this, it should not be unsafe, but unpredictable results may occur.
|
||||
pub(crate) fn delete_index(&self, id: Id) {
|
||||
let (_, key) = self
|
||||
.value_map
|
||||
.remove(&id)
|
||||
.unwrap_or_else(|| panic!("No entry for id `{:?}`", id));
|
||||
|
||||
self.key_map.remove(&key);
|
||||
// Careful: even though `id` ought not to have been used in this revision,
|
||||
// we don't know that for sure since users could have leaked things. If they did,
|
||||
// they may have stray references into `data`. So push the box onto the
|
||||
// "to be deleted" queue.
|
||||
//
|
||||
// To avoid this, we could include some kind of atomic counter in the `Box` that
|
||||
// gets set whenever `data` executes, so we can track if the data was accessed since
|
||||
// the last time an `&mut self` method was called. But that'd take extra storage
|
||||
// and doesn't obviously seem worth it.
|
||||
self.deleted_entries.push(key);
|
||||
}
|
||||
|
||||
pub(crate) fn clear_deleted_indices(&mut self) {
|
||||
std::mem::take(&mut self.deleted_entries);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns `u` but with the lifetime of `t`.
|
||||
//
|
||||
// Safe if you know that data at `u` will remain shared
|
||||
// until the reference `t` expires.
|
||||
unsafe fn transmute_lifetime<'t, 'u, T, U>(_t: &'t T, u: &'u U) -> &'t U {
|
||||
std::mem::transmute(u)
|
||||
}
|
||||
|
||||
impl<DB: ?Sized, Id, Data> Ingredient<DB> for InternedIngredient<Id, Data>
|
||||
where
|
||||
Id: InternedId,
|
||||
Data: InternedData,
|
||||
{
|
||||
fn ingredient_index(&self) -> IngredientIndex {
|
||||
self.ingredient_index
|
||||
}
|
||||
|
||||
fn maybe_changed_after(&self, _db: &DB, _input: DependencyIndex, revision: Revision) -> bool {
|
||||
revision < self.reset_at
|
||||
}
|
||||
|
||||
fn cycle_recovery_strategy(&self) -> crate::cycle::CycleRecoveryStrategy {
|
||||
crate::cycle::CycleRecoveryStrategy::Panic
|
||||
}
|
||||
|
||||
fn origin(&self, _key_index: crate::Id) -> Option<QueryOrigin> {
|
||||
None
|
||||
}
|
||||
|
||||
fn mark_validated_output(
|
||||
&self,
|
||||
_db: &DB,
|
||||
executor: DatabaseKeyIndex,
|
||||
output_key: Option<crate::Id>,
|
||||
) {
|
||||
unreachable!(
|
||||
"mark_validated_output({:?}, {:?}): input cannot be the output of a tracked function",
|
||||
executor, output_key
|
||||
);
|
||||
}
|
||||
|
||||
fn remove_stale_output(
|
||||
&self,
|
||||
_db: &DB,
|
||||
executor: DatabaseKeyIndex,
|
||||
stale_output_key: Option<crate::Id>,
|
||||
) {
|
||||
unreachable!(
|
||||
"remove_stale_output({:?}, {:?}): interned ids are not outputs",
|
||||
executor, stale_output_key
|
||||
);
|
||||
}
|
||||
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
// Interned ingredients do not, normally, get deleted except when they are "reset" en masse.
|
||||
// There ARE methods (e.g., `clear_deleted_entries` and `remove`) for deleting individual
|
||||
// items, but those are only used for tracked struct ingredients.
|
||||
panic!("unexpected call to `reset_for_new_revision`")
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, _db: &DB, _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(self.debug_name, index, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Id, Data> IngredientRequiresReset for InternedIngredient<Id, Data>
|
||||
where
|
||||
Id: InternedId,
|
||||
Data: InternedData,
|
||||
{
|
||||
const RESET_ON_NEW_REVISION: bool = false;
|
||||
}
|
||||
|
||||
pub struct IdentityInterner<Id: AsId> {
|
||||
data: PhantomData<Id>,
|
||||
}
|
||||
|
||||
impl<Id: AsId> IdentityInterner<Id> {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
IdentityInterner { data: PhantomData }
|
||||
}
|
||||
|
||||
pub fn intern(&self, _runtime: &Runtime, id: Id) -> Id {
|
||||
id
|
||||
}
|
||||
|
||||
pub fn data(&self, _runtime: &Runtime, id: Id) -> (Id,) {
|
||||
(id,)
|
||||
}
|
||||
}
|
||||
24
third-party/vendor/salsa-2022/src/jar.rs
vendored
Normal file
24
third-party/vendor/salsa-2022/src/jar.rs
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use crate::{
|
||||
storage::{HasJar, JarFromJars},
|
||||
Database, DbWithJar,
|
||||
};
|
||||
|
||||
use super::routes::Routes;
|
||||
|
||||
/// Representative trait of a salsa jar
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `init_jar` must fully initialize the jar
|
||||
pub unsafe trait Jar<'db>: Sized {
|
||||
type DynDb: ?Sized + HasJar<Self> + Database + 'db;
|
||||
|
||||
/// Initializes the jar at `place`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `place` must be a valid pointer to this jar
|
||||
unsafe fn init_jar<DB>(place: *mut Self, routes: &mut Routes<DB>)
|
||||
where
|
||||
DB: JarFromJars<Self> + DbWithJar<Self>;
|
||||
}
|
||||
106
third-party/vendor/salsa-2022/src/key.rs
vendored
Normal file
106
third-party/vendor/salsa-2022/src/key.rs
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use crate::{Database, DebugWithDb, Id, IngredientIndex};
|
||||
|
||||
/// An integer that uniquely identifies a particular query instance within the
|
||||
/// database. Used to track dependencies between queries. Fully ordered and
|
||||
/// equatable but those orderings are arbitrary, and meant to be used only for
|
||||
/// inserting into maps and the like.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct DependencyIndex {
|
||||
pub(crate) ingredient_index: IngredientIndex,
|
||||
pub(crate) key_index: Option<Id>,
|
||||
}
|
||||
|
||||
impl DependencyIndex {
|
||||
/// Create a database-key-index for an interning or entity table.
|
||||
/// The `key_index` here is always zero, which deliberately corresponds to
|
||||
/// no particular id or entry. This is because the data in such tables
|
||||
/// remains valid until the table as a whole is reset. Using a single id avoids
|
||||
/// creating tons of dependencies in the dependency listings.
|
||||
pub(crate) fn for_table(ingredient_index: IngredientIndex) -> Self {
|
||||
Self {
|
||||
ingredient_index,
|
||||
key_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ingredient_index(self) -> IngredientIndex {
|
||||
self.ingredient_index
|
||||
}
|
||||
|
||||
pub fn key_index(self) -> Option<Id> {
|
||||
self.key_index
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db> crate::debug::DebugWithDb<Db> for DependencyIndex
|
||||
where
|
||||
Db: ?Sized + Database,
|
||||
{
|
||||
fn fmt(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
db: &Db,
|
||||
_include_all_fields: bool,
|
||||
) -> std::fmt::Result {
|
||||
db.fmt_index(*self, f)
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: DatabaseKeyIndex
|
||||
/// An "active" database key index represents a database key index
|
||||
/// that is actively executing. In that case, the `key_index` cannot be
|
||||
/// None.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct DatabaseKeyIndex {
|
||||
pub(crate) ingredient_index: IngredientIndex,
|
||||
pub(crate) key_index: Id,
|
||||
}
|
||||
// ANCHOR_END: DatabaseKeyIndex
|
||||
|
||||
impl DatabaseKeyIndex {
|
||||
pub fn ingredient_index(self) -> IngredientIndex {
|
||||
self.ingredient_index
|
||||
}
|
||||
|
||||
pub fn key_index(self) -> Id {
|
||||
self.key_index
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db> crate::debug::DebugWithDb<Db> for DatabaseKeyIndex
|
||||
where
|
||||
Db: ?Sized + Database,
|
||||
{
|
||||
fn fmt(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
db: &Db,
|
||||
include_all_fields: bool,
|
||||
) -> std::fmt::Result {
|
||||
let i: DependencyIndex = (*self).into();
|
||||
DebugWithDb::fmt(&i, f, db, include_all_fields)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DatabaseKeyIndex> for DependencyIndex {
|
||||
fn from(value: DatabaseKeyIndex) -> Self {
|
||||
Self {
|
||||
ingredient_index: value.ingredient_index,
|
||||
key_index: Some(value.key_index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DependencyIndex> for DatabaseKeyIndex {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: DependencyIndex) -> Result<Self, Self::Error> {
|
||||
let key_index = value.key_index.ok_or(())?;
|
||||
Ok(Self {
|
||||
ingredient_index: value.ingredient_index,
|
||||
key_index,
|
||||
})
|
||||
}
|
||||
}
|
||||
53
third-party/vendor/salsa-2022/src/lib.rs
vendored
Normal file
53
third-party/vendor/salsa-2022/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
pub mod accumulator;
|
||||
pub mod cancelled;
|
||||
pub mod cycle;
|
||||
pub mod database;
|
||||
pub mod debug;
|
||||
pub mod durability;
|
||||
pub mod event;
|
||||
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;
|
||||
pub mod jar;
|
||||
pub mod key;
|
||||
pub mod plumbing;
|
||||
pub mod revision;
|
||||
pub mod routes;
|
||||
pub mod runtime;
|
||||
pub mod salsa_struct;
|
||||
pub mod setter;
|
||||
pub mod storage;
|
||||
#[doc(hidden)]
|
||||
pub mod tracked_struct;
|
||||
|
||||
pub use self::cancelled::Cancelled;
|
||||
pub use self::cycle::Cycle;
|
||||
pub use self::database::Database;
|
||||
pub use self::database::ParallelDatabase;
|
||||
pub use self::database::Snapshot;
|
||||
pub use self::debug::DebugWith;
|
||||
pub use self::debug::DebugWithDb;
|
||||
pub use self::durability::Durability;
|
||||
pub use self::event::Event;
|
||||
pub use self::event::EventKind;
|
||||
pub use self::id::AsId;
|
||||
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 self::tracked_struct::TrackedStructData;
|
||||
pub use self::tracked_struct::TrackedStructId;
|
||||
pub use salsa_2022_macros::accumulator;
|
||||
pub use salsa_2022_macros::db;
|
||||
pub use salsa_2022_macros::input;
|
||||
pub use salsa_2022_macros::interned;
|
||||
pub use salsa_2022_macros::jar;
|
||||
pub use salsa_2022_macros::tracked;
|
||||
28
third-party/vendor/salsa-2022/src/plumbing.rs
vendored
Normal file
28
third-party/vendor/salsa-2022/src/plumbing.rs
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
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) }
|
||||
}
|
||||
}
|
||||
63
third-party/vendor/salsa-2022/src/revision.rs
vendored
Normal file
63
third-party/vendor/salsa-2022/src/revision.rs
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
use std::num::NonZeroUsize;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
/// Value of the initial revision, as a u64. We don't use 0
|
||||
/// because we want to use a `NonZeroUsize`.
|
||||
const START: usize = 1;
|
||||
|
||||
/// A unique identifier for the current version of the database; each
|
||||
/// time an input is changed, the revision number is incremented.
|
||||
/// `Revision` is used internally to track which values may need to be
|
||||
/// recomputed, but is not something you should have to interact with
|
||||
/// directly as a user of salsa.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Revision {
|
||||
generation: NonZeroUsize,
|
||||
}
|
||||
|
||||
impl Revision {
|
||||
pub(crate) fn start() -> Self {
|
||||
Self::from(START)
|
||||
}
|
||||
|
||||
pub(crate) fn from(g: usize) -> Self {
|
||||
Self {
|
||||
generation: NonZeroUsize::new(g).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn next(self) -> Revision {
|
||||
Self::from(self.generation.get() + 1)
|
||||
}
|
||||
|
||||
fn as_usize(self) -> usize {
|
||||
self.generation.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Revision {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(fmt, "R{}", self.generation)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AtomicRevision {
|
||||
data: AtomicUsize,
|
||||
}
|
||||
|
||||
impl AtomicRevision {
|
||||
pub(crate) fn start() -> Self {
|
||||
Self {
|
||||
data: AtomicUsize::new(START),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load(&self) -> Revision {
|
||||
Revision::from(self.data.load(Ordering::SeqCst))
|
||||
}
|
||||
|
||||
pub(crate) fn store(&self, r: Revision) {
|
||||
self.data.store(r.as_usize(), Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
126
third-party/vendor/salsa-2022/src/routes.rs
vendored
Normal file
126
third-party/vendor/salsa-2022/src/routes.rs
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
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.
|
||||
fn from(v: usize) -> Self {
|
||||
assert!(v < (std::u32::MAX as usize));
|
||||
Self(v as u32)
|
||||
}
|
||||
|
||||
/// Convert the ingredient index back into a usize.
|
||||
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))
|
||||
}
|
||||
}
|
||||
444
third-party/vendor/salsa-2022/src/runtime.rs
vendored
Normal file
444
third-party/vendor/salsa-2022/src/runtime.rs
vendored
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
use std::{
|
||||
panic::panic_any,
|
||||
sync::{atomic::Ordering, Arc},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cycle::CycleRecoveryStrategy,
|
||||
debug::DebugWithDb,
|
||||
durability::Durability,
|
||||
key::{DatabaseKeyIndex, DependencyIndex},
|
||||
runtime::active_query::ActiveQuery,
|
||||
Cancelled, Cycle, Database, Event, EventKind, Revision,
|
||||
};
|
||||
|
||||
use self::{
|
||||
dependency_graph::DependencyGraph,
|
||||
local_state::{ActiveQueryGuard, EdgeKind},
|
||||
};
|
||||
|
||||
use super::{tracked_struct::Disambiguator, IngredientIndex};
|
||||
|
||||
mod active_query;
|
||||
mod dependency_graph;
|
||||
pub mod local_state;
|
||||
mod shared_state;
|
||||
|
||||
pub struct Runtime {
|
||||
/// Our unique runtime id.
|
||||
id: RuntimeId,
|
||||
|
||||
/// Local state that is specific to this runtime (thread).
|
||||
local_state: local_state::LocalState,
|
||||
|
||||
/// Shared state that is accessible via all runtimes.
|
||||
shared_state: Arc<shared_state::SharedState>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum WaitResult {
|
||||
Completed,
|
||||
Panicked,
|
||||
Cycle(Cycle),
|
||||
}
|
||||
|
||||
/// A unique identifier for a particular runtime. Each time you create
|
||||
/// a snapshot, a fresh `RuntimeId` is generated. Once a snapshot is
|
||||
/// complete, its `RuntimeId` may potentially be re-used.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct RuntimeId {
|
||||
counter: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct StampedValue<V> {
|
||||
pub(crate) value: V,
|
||||
pub(crate) durability: Durability,
|
||||
pub(crate) changed_at: Revision,
|
||||
}
|
||||
|
||||
impl<V> StampedValue<V> {
|
||||
// FIXME: Use or remove this.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn merge_revision_info<U>(&mut self, other: &StampedValue<U>) {
|
||||
self.durability = self.durability.min(other.durability);
|
||||
self.changed_at = self.changed_at.max(other.changed_at);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Runtime {
|
||||
fn default() -> Self {
|
||||
Runtime {
|
||||
id: RuntimeId { counter: 0 },
|
||||
shared_state: Default::default(),
|
||||
local_state: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Runtime {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fmt.debug_struct("Runtime")
|
||||
.field("id", &self.id())
|
||||
.field("shared_state", &self.shared_state)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub(crate) fn id(&self) -> RuntimeId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub(crate) fn current_revision(&self) -> Revision {
|
||||
self.shared_state.revisions[0].load()
|
||||
}
|
||||
|
||||
/// Returns the index of the active query along with its *current* durability/changed-at
|
||||
/// information. As the query continues to execute, naturally, that information may change.
|
||||
pub(crate) fn active_query(&self) -> Option<(DatabaseKeyIndex, StampedValue<()>)> {
|
||||
self.local_state.active_query()
|
||||
}
|
||||
|
||||
pub(crate) fn empty_dependencies(&self) -> Arc<[(EdgeKind, DependencyIndex)]> {
|
||||
self.shared_state.empty_dependencies.clone()
|
||||
}
|
||||
|
||||
pub fn snapshot(&self) -> Self {
|
||||
if self.local_state.query_in_progress() {
|
||||
panic!("it is not legal to `snapshot` during a query (see salsa-rs/salsa#80)");
|
||||
}
|
||||
|
||||
let id = RuntimeId {
|
||||
counter: self.shared_state.next_id.fetch_add(1, Ordering::SeqCst),
|
||||
};
|
||||
|
||||
Runtime {
|
||||
id,
|
||||
shared_state: self.shared_state.clone(),
|
||||
local_state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn report_tracked_read(
|
||||
&self,
|
||||
key_index: DependencyIndex,
|
||||
durability: Durability,
|
||||
changed_at: Revision,
|
||||
) {
|
||||
self.local_state
|
||||
.report_tracked_read(key_index, durability, changed_at)
|
||||
}
|
||||
|
||||
/// Reports that the query depends on some state unknown to salsa.
|
||||
///
|
||||
/// Queries which report untracked reads will be re-executed in the next
|
||||
/// revision.
|
||||
pub fn report_untracked_read(&self) {
|
||||
self.local_state
|
||||
.report_untracked_read(self.current_revision());
|
||||
}
|
||||
|
||||
/// Reports that an input with durability `durability` changed.
|
||||
/// This will update the 'last changed at' values for every durability
|
||||
/// less than or equal to `durability` to the current revision.
|
||||
pub(crate) fn report_tracked_write(&mut self, durability: Durability) {
|
||||
let new_revision = self.current_revision();
|
||||
for rev in &self.shared_state.revisions[1..=durability.index()] {
|
||||
rev.store(new_revision);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds `key` to the list of output created by the current query
|
||||
/// (if not already present).
|
||||
pub(crate) fn add_output(&self, key: DependencyIndex) {
|
||||
self.local_state.add_output(key);
|
||||
}
|
||||
|
||||
/// Check whether `entity` is contained the list of outputs written by the current query.
|
||||
pub(super) fn is_output_of_active_query(&self, entity: DependencyIndex) -> bool {
|
||||
self.local_state.is_output(entity)
|
||||
}
|
||||
|
||||
/// Called when the active queries creates an index from the
|
||||
/// entity table with the index `entity_index`. Has the following effects:
|
||||
///
|
||||
/// * Add a query read on `DatabaseKeyIndex::for_table(entity_index)`
|
||||
/// * Identify a unique disambiguator for the hash within the current query,
|
||||
/// adding the hash to the current query's disambiguator table.
|
||||
/// * Return that hash + id of the current query.
|
||||
pub(crate) fn disambiguate_entity(
|
||||
&self,
|
||||
entity_index: IngredientIndex,
|
||||
reset_at: Revision,
|
||||
data_hash: u64,
|
||||
) -> (DatabaseKeyIndex, Disambiguator) {
|
||||
self.report_tracked_read(
|
||||
DependencyIndex::for_table(entity_index),
|
||||
Durability::MAX,
|
||||
reset_at,
|
||||
);
|
||||
self.local_state.disambiguate(data_hash)
|
||||
}
|
||||
|
||||
/// The revision in which values with durability `d` may have last
|
||||
/// changed. For D0, this is just the current revision. But for
|
||||
/// higher levels of durability, this value may lag behind the
|
||||
/// current revision. If we encounter a value of durability Di,
|
||||
/// then, we can check this function to get a "bound" on when the
|
||||
/// value may have changed, which allows us to skip walking its
|
||||
/// dependencies.
|
||||
#[inline]
|
||||
pub(crate) fn last_changed_revision(&self, d: Durability) -> Revision {
|
||||
self.shared_state.revisions[d.index()].load()
|
||||
}
|
||||
|
||||
/// Starts unwinding the stack if the current revision is cancelled.
|
||||
///
|
||||
/// This method can be called by query implementations that perform
|
||||
/// potentially expensive computations, in order to speed up propagation of
|
||||
/// cancellation.
|
||||
///
|
||||
/// Cancellation will automatically be triggered by salsa on any query
|
||||
/// invocation.
|
||||
///
|
||||
/// This method should not be overridden by `Database` implementors. A
|
||||
/// `salsa_event` is emitted when this method is called, so that should be
|
||||
/// used instead.
|
||||
pub(crate) fn unwind_if_revision_cancelled<DB: ?Sized + Database>(&self, db: &DB) {
|
||||
db.salsa_event(Event {
|
||||
runtime_id: self.id(),
|
||||
kind: EventKind::WillCheckCancellation,
|
||||
});
|
||||
if self.shared_state.revision_canceled.load() {
|
||||
db.salsa_event(Event {
|
||||
runtime_id: self.id(),
|
||||
kind: EventKind::WillCheckCancellation,
|
||||
});
|
||||
self.unwind_cancelled();
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub(crate) fn unwind_cancelled(&self) {
|
||||
self.report_untracked_read();
|
||||
Cancelled::PendingWrite.throw();
|
||||
}
|
||||
|
||||
pub(crate) fn set_cancellation_flag(&self) {
|
||||
self.shared_state.revision_canceled.store(true);
|
||||
}
|
||||
|
||||
/// Increments the "current revision" counter and clears
|
||||
/// the cancellation flag.
|
||||
///
|
||||
/// This should only be done by the storage when the state is "quiescent".
|
||||
pub(crate) fn new_revision(&mut self) -> Revision {
|
||||
let r_old = self.current_revision();
|
||||
let r_new = r_old.next();
|
||||
self.shared_state.revisions[0].store(r_new);
|
||||
self.shared_state.revision_canceled.store(false);
|
||||
r_new
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn push_query(&self, database_key_index: DatabaseKeyIndex) -> ActiveQueryGuard<'_> {
|
||||
self.local_state.push_query(database_key_index)
|
||||
}
|
||||
|
||||
/// Block until `other_id` completes executing `database_key`;
|
||||
/// panic or unwind in the case of a cycle.
|
||||
///
|
||||
/// `query_mutex_guard` is the guard for the current query's state;
|
||||
/// it will be dropped after we have successfully registered the
|
||||
/// dependency.
|
||||
///
|
||||
/// # Propagating panics
|
||||
///
|
||||
/// If the thread `other_id` panics, then our thread is considered
|
||||
/// cancelled, so this function will panic with a `Cancelled` value.
|
||||
///
|
||||
/// # Cycle handling
|
||||
///
|
||||
/// If the thread `other_id` already depends on the current thread,
|
||||
/// and hence there is a cycle in the query graph, then this function
|
||||
/// will unwind instead of returning normally. The method of unwinding
|
||||
/// depends on the [`Self::mutual_cycle_recovery_strategy`]
|
||||
/// of the cycle participants:
|
||||
///
|
||||
/// * [`CycleRecoveryStrategy::Panic`]: panic with the [`Cycle`] as the value.
|
||||
/// * [`CycleRecoveryStrategy::Fallback`]: initiate unwinding with [`CycleParticipant::unwind`].
|
||||
pub(crate) fn block_on_or_unwind<QueryMutexGuard>(
|
||||
&self,
|
||||
db: &dyn Database,
|
||||
database_key: DatabaseKeyIndex,
|
||||
other_id: RuntimeId,
|
||||
query_mutex_guard: QueryMutexGuard,
|
||||
) {
|
||||
let mut dg = self.shared_state.dependency_graph.lock();
|
||||
|
||||
if dg.depends_on(other_id, self.id()) {
|
||||
self.unblock_cycle_and_maybe_throw(db, &mut dg, database_key, other_id);
|
||||
|
||||
// If the above fn returns, then (via cycle recovery) it has unblocked the
|
||||
// cycle, so we can continue.
|
||||
assert!(!dg.depends_on(other_id, self.id()));
|
||||
}
|
||||
|
||||
db.salsa_event(Event {
|
||||
runtime_id: self.id(),
|
||||
kind: EventKind::WillBlockOn {
|
||||
other_runtime_id: other_id,
|
||||
database_key,
|
||||
},
|
||||
});
|
||||
|
||||
let stack = self.local_state.take_query_stack();
|
||||
|
||||
let (stack, result) = DependencyGraph::block_on(
|
||||
dg,
|
||||
self.id(),
|
||||
database_key,
|
||||
other_id,
|
||||
stack,
|
||||
query_mutex_guard,
|
||||
);
|
||||
|
||||
self.local_state.restore_query_stack(stack);
|
||||
|
||||
match result {
|
||||
WaitResult::Completed => (),
|
||||
|
||||
// If the other thread panicked, then we consider this thread
|
||||
// cancelled. The assumption is that the panic will be detected
|
||||
// by the other thread and responded to appropriately.
|
||||
WaitResult::Panicked => Cancelled::PropagatedPanic.throw(),
|
||||
|
||||
WaitResult::Cycle(c) => c.throw(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles a cycle in the dependency graph that was detected when the
|
||||
/// current thread tried to block on `database_key_index` which is being
|
||||
/// executed by `to_id`. If this function returns, then `to_id` no longer
|
||||
/// depends on the current thread, and so we should continue executing
|
||||
/// as normal. Otherwise, the function will throw a `Cycle` which is expected
|
||||
/// to be caught by some frame on our stack. This occurs either if there is
|
||||
/// a frame on our stack with cycle recovery (possibly the top one!) or if there
|
||||
/// is no cycle recovery at all.
|
||||
fn unblock_cycle_and_maybe_throw(
|
||||
&self,
|
||||
db: &dyn Database,
|
||||
dg: &mut DependencyGraph,
|
||||
database_key_index: DatabaseKeyIndex,
|
||||
to_id: RuntimeId,
|
||||
) {
|
||||
log::debug!(
|
||||
"unblock_cycle_and_maybe_throw(database_key={:?})",
|
||||
database_key_index
|
||||
);
|
||||
|
||||
let mut from_stack = self.local_state.take_query_stack();
|
||||
let from_id = self.id();
|
||||
|
||||
// Make a "dummy stack frame". As we iterate through the cycle, we will collect the
|
||||
// inputs from each participant. Then, if we are participating in cycle recovery, we
|
||||
// will propagate those results to all participants.
|
||||
let mut cycle_query = ActiveQuery::new(database_key_index);
|
||||
|
||||
// Identify the cycle participants:
|
||||
let cycle = {
|
||||
let mut v = vec![];
|
||||
dg.for_each_cycle_participant(
|
||||
from_id,
|
||||
&mut from_stack,
|
||||
database_key_index,
|
||||
to_id,
|
||||
|aqs| {
|
||||
aqs.iter_mut().for_each(|aq| {
|
||||
cycle_query.add_from(aq);
|
||||
v.push(aq.database_key_index);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// We want to give the participants in a deterministic order
|
||||
// (at least for this execution, not necessarily across executions),
|
||||
// no matter where it started on the stack. Find the minimum
|
||||
// key and rotate it to the front.
|
||||
let min = v.iter().min().unwrap();
|
||||
let index = v.iter().position(|p| p == min).unwrap();
|
||||
v.rotate_left(index);
|
||||
|
||||
// No need to store extra memory.
|
||||
v.shrink_to_fit();
|
||||
|
||||
Cycle::new(Arc::new(v))
|
||||
};
|
||||
log::debug!(
|
||||
"cycle {:?}, cycle_query {:#?}",
|
||||
cycle.debug(db),
|
||||
cycle_query,
|
||||
);
|
||||
|
||||
// We can remove the cycle participants from the list of dependencies;
|
||||
// they are a strongly connected component (SCC) and we only care about
|
||||
// dependencies to things outside the SCC that control whether it will
|
||||
// form again.
|
||||
cycle_query.remove_cycle_participants(&cycle);
|
||||
|
||||
// Mark each cycle participant that has recovery set, along with
|
||||
// any frames that come after them on the same thread. Those frames
|
||||
// are going to be unwound so that fallback can occur.
|
||||
dg.for_each_cycle_participant(from_id, &mut from_stack, database_key_index, to_id, |aqs| {
|
||||
aqs.iter_mut()
|
||||
.skip_while(|aq| {
|
||||
match db.cycle_recovery_strategy(aq.database_key_index.ingredient_index) {
|
||||
CycleRecoveryStrategy::Panic => true,
|
||||
CycleRecoveryStrategy::Fallback => false,
|
||||
}
|
||||
})
|
||||
.for_each(|aq| {
|
||||
log::debug!("marking {:?} for fallback", aq.database_key_index.debug(db));
|
||||
aq.take_inputs_from(&cycle_query);
|
||||
assert!(aq.cycle.is_none());
|
||||
aq.cycle = Some(cycle.clone());
|
||||
});
|
||||
});
|
||||
|
||||
// Unblock every thread that has cycle recovery with a `WaitResult::Cycle`.
|
||||
// They will throw the cycle, which will be caught by the frame that has
|
||||
// cycle recovery so that it can execute that recovery.
|
||||
let (me_recovered, others_recovered) =
|
||||
dg.maybe_unblock_runtimes_in_cycle(from_id, &from_stack, database_key_index, to_id);
|
||||
|
||||
self.local_state.restore_query_stack(from_stack);
|
||||
|
||||
if me_recovered {
|
||||
// If the current thread has recovery, we want to throw
|
||||
// so that it can begin.
|
||||
cycle.throw()
|
||||
} else if others_recovered {
|
||||
// If other threads have recovery but we didn't: return and we will block on them.
|
||||
} else {
|
||||
// if nobody has recover, then we panic
|
||||
panic_any(cycle);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when this runtime completed computing `database_key` with
|
||||
/// the given result `wait_result` (`wait_result` should be `None` if
|
||||
/// computing `database_key` panicked and could not complete).
|
||||
/// This function unblocks any dependent queries and allows them
|
||||
/// to continue executing.
|
||||
pub(crate) fn unblock_queries_blocked_on(
|
||||
&self,
|
||||
database_key: DatabaseKeyIndex,
|
||||
wait_result: WaitResult,
|
||||
) {
|
||||
self.shared_state
|
||||
.dependency_graph
|
||||
.lock()
|
||||
.unblock_runtimes_blocked_on(database_key, wait_result);
|
||||
}
|
||||
}
|
||||
147
third-party/vendor/salsa-2022/src/runtime/active_query.rs
vendored
Normal file
147
third-party/vendor/salsa-2022/src/runtime/active_query.rs
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
use crate::{
|
||||
durability::Durability,
|
||||
hash::{FxIndexMap, FxIndexSet},
|
||||
key::{DatabaseKeyIndex, DependencyIndex},
|
||||
tracked_struct::Disambiguator,
|
||||
Cycle, Revision, Runtime,
|
||||
};
|
||||
|
||||
use super::local_state::{EdgeKind, QueryEdges, QueryOrigin, QueryRevisions};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ActiveQuery {
|
||||
/// What query is executing
|
||||
pub(super) database_key_index: DatabaseKeyIndex,
|
||||
|
||||
/// Minimum durability of inputs observed so far.
|
||||
pub(super) durability: Durability,
|
||||
|
||||
/// Maximum revision of all inputs observed. If we observe an
|
||||
/// untracked read, this will be set to the most recent revision.
|
||||
pub(super) changed_at: Revision,
|
||||
|
||||
/// Inputs: Set of subqueries that were accessed thus far.
|
||||
/// Outputs: Tracks values written by this query. Could be...
|
||||
///
|
||||
/// * tracked structs created
|
||||
/// * invocations of `specify`
|
||||
/// * accumulators pushed to
|
||||
pub(super) input_outputs: FxIndexSet<(EdgeKind, DependencyIndex)>,
|
||||
|
||||
/// True if there was an untracked read.
|
||||
pub(super) untracked_read: bool,
|
||||
|
||||
/// Stores the entire cycle, if one is found and this query is part of it.
|
||||
pub(super) cycle: Option<Cycle>,
|
||||
|
||||
/// When new entities are created, their data is hashed, and the resulting
|
||||
/// hash is added to this map. If it is not present, then the disambiguator is 0.
|
||||
/// Otherwise it is 1 more than the current value (which is incremented).
|
||||
pub(super) disambiguator_map: FxIndexMap<u64, Disambiguator>,
|
||||
}
|
||||
|
||||
impl ActiveQuery {
|
||||
pub(super) fn new(database_key_index: DatabaseKeyIndex) -> Self {
|
||||
ActiveQuery {
|
||||
database_key_index,
|
||||
durability: Durability::MAX,
|
||||
changed_at: Revision::start(),
|
||||
input_outputs: FxIndexSet::default(),
|
||||
untracked_read: false,
|
||||
cycle: None,
|
||||
disambiguator_map: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_read(
|
||||
&mut self,
|
||||
input: DependencyIndex,
|
||||
durability: Durability,
|
||||
revision: Revision,
|
||||
) {
|
||||
self.input_outputs.insert((EdgeKind::Input, input));
|
||||
self.durability = self.durability.min(durability);
|
||||
self.changed_at = self.changed_at.max(revision);
|
||||
}
|
||||
|
||||
pub(super) fn add_untracked_read(&mut self, changed_at: Revision) {
|
||||
self.untracked_read = true;
|
||||
self.durability = Durability::LOW;
|
||||
self.changed_at = changed_at;
|
||||
}
|
||||
|
||||
pub(super) fn add_synthetic_read(&mut self, durability: Durability, revision: Revision) {
|
||||
self.untracked_read = true;
|
||||
self.durability = self.durability.min(durability);
|
||||
self.changed_at = self.changed_at.max(revision);
|
||||
}
|
||||
|
||||
/// Adds a key to our list of outputs.
|
||||
pub(super) fn add_output(&mut self, key: DependencyIndex) {
|
||||
self.input_outputs.insert((EdgeKind::Output, key));
|
||||
}
|
||||
|
||||
/// True if the given key was output by this query.
|
||||
pub(super) fn is_output(&self, key: DependencyIndex) -> bool {
|
||||
self.input_outputs.contains(&(EdgeKind::Output, key))
|
||||
}
|
||||
|
||||
pub(crate) fn revisions(&self, runtime: &Runtime) -> QueryRevisions {
|
||||
let input_outputs = if self.input_outputs.is_empty() {
|
||||
runtime.empty_dependencies()
|
||||
} else {
|
||||
self.input_outputs.iter().copied().collect()
|
||||
};
|
||||
|
||||
let edges = QueryEdges::new(input_outputs);
|
||||
|
||||
let origin = if self.untracked_read {
|
||||
QueryOrigin::DerivedUntracked(edges)
|
||||
} else {
|
||||
QueryOrigin::Derived(edges)
|
||||
};
|
||||
|
||||
QueryRevisions {
|
||||
changed_at: self.changed_at,
|
||||
origin,
|
||||
durability: self.durability,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds any dependencies from `other` into `self`.
|
||||
/// Used during cycle recovery, see [`Runtime::create_cycle_error`].
|
||||
pub(super) fn add_from(&mut self, other: &ActiveQuery) {
|
||||
self.changed_at = self.changed_at.max(other.changed_at);
|
||||
self.durability = self.durability.min(other.durability);
|
||||
self.untracked_read |= other.untracked_read;
|
||||
self.input_outputs
|
||||
.extend(other.input_outputs.iter().copied());
|
||||
}
|
||||
|
||||
/// Removes the participants in `cycle` from my dependencies.
|
||||
/// Used during cycle recovery, see [`Runtime::create_cycle_error`].
|
||||
pub(super) fn remove_cycle_participants(&mut self, cycle: &Cycle) {
|
||||
for p in cycle.participant_keys() {
|
||||
let p: DependencyIndex = p.into();
|
||||
self.input_outputs.remove(&(EdgeKind::Input, p));
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy the changed-at, durability, and dependencies from `cycle_query`.
|
||||
/// Used during cycle recovery, see [`Runtime::create_cycle_error`].
|
||||
pub(crate) fn take_inputs_from(&mut self, cycle_query: &ActiveQuery) {
|
||||
self.changed_at = cycle_query.changed_at;
|
||||
self.durability = cycle_query.durability;
|
||||
self.input_outputs = cycle_query.input_outputs.clone();
|
||||
}
|
||||
|
||||
pub(super) fn disambiguate(&mut self, hash: u64) -> Disambiguator {
|
||||
let disambiguator = self
|
||||
.disambiguator_map
|
||||
.entry(hash)
|
||||
.or_insert(Disambiguator(0));
|
||||
let result = *disambiguator;
|
||||
disambiguator.0 += 1;
|
||||
result
|
||||
}
|
||||
}
|
||||
277
third-party/vendor/salsa-2022/src/runtime/dependency_graph.rs
vendored
Normal file
277
third-party/vendor/salsa-2022/src/runtime/dependency_graph.rs
vendored
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::key::DatabaseKeyIndex;
|
||||
use parking_lot::{Condvar, MutexGuard};
|
||||
use rustc_hash::FxHashMap;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::{active_query::ActiveQuery, RuntimeId, WaitResult};
|
||||
|
||||
type QueryStack = Vec<ActiveQuery>;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct DependencyGraph {
|
||||
/// A `(K -> V)` pair in this map indicates that the the runtime
|
||||
/// `K` is blocked on some query executing in the runtime `V`.
|
||||
/// This encodes a graph that must be acyclic (or else deadlock
|
||||
/// will result).
|
||||
edges: FxHashMap<RuntimeId, Edge>,
|
||||
|
||||
/// Encodes the `RuntimeId` that are blocked waiting for the result
|
||||
/// of a given query.
|
||||
query_dependents: FxHashMap<DatabaseKeyIndex, SmallVec<[RuntimeId; 4]>>,
|
||||
|
||||
/// When a key K completes which had dependent queries Qs blocked on it,
|
||||
/// it stores its `WaitResult` here. As they wake up, each query Q in Qs will
|
||||
/// come here to fetch their results.
|
||||
wait_results: FxHashMap<RuntimeId, (QueryStack, WaitResult)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Edge {
|
||||
blocked_on_id: RuntimeId,
|
||||
blocked_on_key: DatabaseKeyIndex,
|
||||
stack: QueryStack,
|
||||
|
||||
/// Signalled whenever a query with dependents completes.
|
||||
/// Allows those dependents to check if they are ready to unblock.
|
||||
condvar: Arc<parking_lot::Condvar>,
|
||||
}
|
||||
|
||||
impl DependencyGraph {
|
||||
/// True if `from_id` depends on `to_id`.
|
||||
///
|
||||
/// (i.e., there is a path from `from_id` to `to_id` in the graph.)
|
||||
pub(super) fn depends_on(&mut self, from_id: RuntimeId, to_id: RuntimeId) -> bool {
|
||||
let mut p = from_id;
|
||||
while let Some(q) = self.edges.get(&p).map(|edge| edge.blocked_on_id) {
|
||||
if q == to_id {
|
||||
return true;
|
||||
}
|
||||
|
||||
p = q;
|
||||
}
|
||||
p == to_id
|
||||
}
|
||||
|
||||
/// Invokes `closure` with a `&mut ActiveQuery` for each query that participates in the cycle.
|
||||
/// The cycle runs as follows:
|
||||
///
|
||||
/// 1. The runtime `from_id`, which has the stack `from_stack`, would like to invoke `database_key`...
|
||||
/// 2. ...but `database_key` is already being executed by `to_id`...
|
||||
/// 3. ...and `to_id` is transitively dependent on something which is present on `from_stack`.
|
||||
pub(super) fn for_each_cycle_participant(
|
||||
&mut self,
|
||||
from_id: RuntimeId,
|
||||
from_stack: &mut QueryStack,
|
||||
database_key: DatabaseKeyIndex,
|
||||
to_id: RuntimeId,
|
||||
mut closure: impl FnMut(&mut [ActiveQuery]),
|
||||
) {
|
||||
debug_assert!(self.depends_on(to_id, from_id));
|
||||
|
||||
// To understand this algorithm, consider this [drawing](https://is.gd/TGLI9v):
|
||||
//
|
||||
// database_key = QB2
|
||||
// from_id = A
|
||||
// to_id = B
|
||||
// from_stack = [QA1, QA2, QA3]
|
||||
//
|
||||
// self.edges[B] = { C, QC2, [QB1..QB3] }
|
||||
// self.edges[C] = { A, QA2, [QC1..QC3] }
|
||||
//
|
||||
// The cyclic
|
||||
// edge we have
|
||||
// failed to add.
|
||||
// :
|
||||
// A : B C
|
||||
// :
|
||||
// QA1 v QB1 QC1
|
||||
// ┌► QA2 ┌──► QB2 ┌─► QC2
|
||||
// │ QA3 ───┘ QB3 ──┘ QC3 ───┐
|
||||
// │ │
|
||||
// └───────────────────────────────┘
|
||||
//
|
||||
// Final output: [QB2, QB3, QC2, QC3, QA2, QA3]
|
||||
|
||||
let mut id = to_id;
|
||||
let mut key = database_key;
|
||||
while id != from_id {
|
||||
// Looking at the diagram above, the idea is to
|
||||
// take the edge from `to_id` starting at `key`
|
||||
// (inclusive) and down to the end. We can then
|
||||
// load up the next thread (i.e., we start at B/QB2,
|
||||
// and then load up the dependency on C/QC2).
|
||||
let edge = self.edges.get_mut(&id).unwrap();
|
||||
let prefix = edge
|
||||
.stack
|
||||
.iter_mut()
|
||||
.take_while(|p| p.database_key_index != key)
|
||||
.count();
|
||||
closure(&mut edge.stack[prefix..]);
|
||||
id = edge.blocked_on_id;
|
||||
key = edge.blocked_on_key;
|
||||
}
|
||||
|
||||
// Finally, we copy in the results from `from_stack`.
|
||||
let prefix = from_stack
|
||||
.iter_mut()
|
||||
.take_while(|p| p.database_key_index != key)
|
||||
.count();
|
||||
closure(&mut from_stack[prefix..]);
|
||||
}
|
||||
|
||||
/// Unblock each blocked runtime (excluding the current one) if some
|
||||
/// query executing in that runtime is participating in cycle fallback.
|
||||
///
|
||||
/// Returns a boolean (Current, Others) where:
|
||||
/// * Current is true if the current runtime has cycle participants
|
||||
/// with fallback;
|
||||
/// * Others is true if other runtimes were unblocked.
|
||||
pub(super) fn maybe_unblock_runtimes_in_cycle(
|
||||
&mut self,
|
||||
from_id: RuntimeId,
|
||||
from_stack: &QueryStack,
|
||||
database_key: DatabaseKeyIndex,
|
||||
to_id: RuntimeId,
|
||||
) -> (bool, bool) {
|
||||
// See diagram in `for_each_cycle_participant`.
|
||||
let mut id = to_id;
|
||||
let mut key = database_key;
|
||||
let mut others_unblocked = false;
|
||||
while id != from_id {
|
||||
let edge = self.edges.get(&id).unwrap();
|
||||
let prefix = edge
|
||||
.stack
|
||||
.iter()
|
||||
.take_while(|p| p.database_key_index != key)
|
||||
.count();
|
||||
let next_id = edge.blocked_on_id;
|
||||
let next_key = edge.blocked_on_key;
|
||||
|
||||
if let Some(cycle) = edge.stack[prefix..]
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|aq| aq.cycle.clone())
|
||||
{
|
||||
// Remove `id` from the list of runtimes blocked on `next_key`:
|
||||
self.query_dependents
|
||||
.get_mut(&next_key)
|
||||
.unwrap()
|
||||
.retain(|r| *r != id);
|
||||
|
||||
// Unblock runtime so that it can resume execution once lock is released:
|
||||
self.unblock_runtime(id, WaitResult::Cycle(cycle));
|
||||
|
||||
others_unblocked = true;
|
||||
}
|
||||
|
||||
id = next_id;
|
||||
key = next_key;
|
||||
}
|
||||
|
||||
let prefix = from_stack
|
||||
.iter()
|
||||
.take_while(|p| p.database_key_index != key)
|
||||
.count();
|
||||
let this_unblocked = from_stack[prefix..].iter().any(|aq| aq.cycle.is_some());
|
||||
|
||||
(this_unblocked, others_unblocked)
|
||||
}
|
||||
|
||||
/// Modifies the graph so that `from_id` is blocked
|
||||
/// on `database_key`, which is being computed by
|
||||
/// `to_id`.
|
||||
///
|
||||
/// For this to be reasonable, the lock on the
|
||||
/// results table for `database_key` must be held.
|
||||
/// This ensures that computing `database_key` doesn't
|
||||
/// complete before `block_on` executes.
|
||||
///
|
||||
/// Preconditions:
|
||||
/// * No path from `to_id` to `from_id`
|
||||
/// (i.e., `me.depends_on(to_id, from_id)` is false)
|
||||
/// * `held_mutex` is a read lock (or stronger) on `database_key`
|
||||
pub(super) fn block_on<QueryMutexGuard>(
|
||||
mut me: MutexGuard<'_, Self>,
|
||||
from_id: RuntimeId,
|
||||
database_key: DatabaseKeyIndex,
|
||||
to_id: RuntimeId,
|
||||
from_stack: QueryStack,
|
||||
query_mutex_guard: QueryMutexGuard,
|
||||
) -> (QueryStack, WaitResult) {
|
||||
let condvar = me.add_edge(from_id, database_key, to_id, from_stack);
|
||||
|
||||
// Release the mutex that prevents `database_key`
|
||||
// from completing, now that the edge has been added.
|
||||
drop(query_mutex_guard);
|
||||
|
||||
loop {
|
||||
if let Some(stack_and_result) = me.wait_results.remove(&from_id) {
|
||||
debug_assert!(!me.edges.contains_key(&from_id));
|
||||
return stack_and_result;
|
||||
}
|
||||
condvar.wait(&mut me);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for `block_on`: performs actual graph modification
|
||||
/// to add a dependency edge from `from_id` to `to_id`, which is
|
||||
/// computing `database_key`.
|
||||
fn add_edge(
|
||||
&mut self,
|
||||
from_id: RuntimeId,
|
||||
database_key: DatabaseKeyIndex,
|
||||
to_id: RuntimeId,
|
||||
from_stack: QueryStack,
|
||||
) -> Arc<parking_lot::Condvar> {
|
||||
assert_ne!(from_id, to_id);
|
||||
debug_assert!(!self.edges.contains_key(&from_id));
|
||||
debug_assert!(!self.depends_on(to_id, from_id));
|
||||
|
||||
let condvar = Arc::new(Condvar::new());
|
||||
self.edges.insert(
|
||||
from_id,
|
||||
Edge {
|
||||
blocked_on_id: to_id,
|
||||
blocked_on_key: database_key,
|
||||
stack: from_stack,
|
||||
condvar: condvar.clone(),
|
||||
},
|
||||
);
|
||||
self.query_dependents
|
||||
.entry(database_key)
|
||||
.or_default()
|
||||
.push(from_id);
|
||||
condvar
|
||||
}
|
||||
|
||||
/// Invoked when runtime `to_id` completes executing
|
||||
/// `database_key`.
|
||||
pub(super) fn unblock_runtimes_blocked_on(
|
||||
&mut self,
|
||||
database_key: DatabaseKeyIndex,
|
||||
wait_result: WaitResult,
|
||||
) {
|
||||
let dependents = self
|
||||
.query_dependents
|
||||
.remove(&database_key)
|
||||
.unwrap_or_default();
|
||||
|
||||
for from_id in dependents {
|
||||
self.unblock_runtime(from_id, wait_result.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Unblock the runtime with the given id with the given wait-result.
|
||||
/// This will cause it resume execution (though it will have to grab
|
||||
/// the lock on this data structure first, to recover the wait result).
|
||||
fn unblock_runtime(&mut self, id: RuntimeId, wait_result: WaitResult) {
|
||||
let edge = self.edges.remove(&id).expect("not blocked");
|
||||
self.wait_results.insert(id, (edge.stack, wait_result));
|
||||
|
||||
// Now that we have inserted the `wait_results`,
|
||||
// notify the thread.
|
||||
edge.condvar.notify_one();
|
||||
}
|
||||
}
|
||||
364
third-party/vendor/salsa-2022/src/runtime/local_state.rs
vendored
Normal file
364
third-party/vendor/salsa-2022/src/runtime/local_state.rs
vendored
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
use log::debug;
|
||||
|
||||
use crate::durability::Durability;
|
||||
use crate::key::DatabaseKeyIndex;
|
||||
use crate::key::DependencyIndex;
|
||||
use crate::runtime::Revision;
|
||||
use crate::tracked_struct::Disambiguator;
|
||||
use crate::Cycle;
|
||||
use crate::Runtime;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::active_query::ActiveQuery;
|
||||
use super::StampedValue;
|
||||
|
||||
/// State that is specific to a single execution thread.
|
||||
///
|
||||
/// Internally, this type uses ref-cells.
|
||||
///
|
||||
/// **Note also that all mutations to the database handle (and hence
|
||||
/// to the local-state) must be undone during unwinding.**
|
||||
pub(super) struct LocalState {
|
||||
/// Vector of active queries.
|
||||
///
|
||||
/// This is normally `Some`, but it is set to `None`
|
||||
/// while the query is blocked waiting for a result.
|
||||
///
|
||||
/// Unwinding note: pushes onto this vector must be popped -- even
|
||||
/// during unwinding.
|
||||
query_stack: RefCell<Option<Vec<ActiveQuery>>>,
|
||||
}
|
||||
|
||||
/// Summarizes "all the inputs that a query used"
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct QueryRevisions {
|
||||
/// The most revision in which some input changed.
|
||||
pub(crate) changed_at: Revision,
|
||||
|
||||
/// Minimum durability of the inputs to this query.
|
||||
pub(crate) durability: Durability,
|
||||
|
||||
/// How was this query computed?
|
||||
pub(crate) origin: QueryOrigin,
|
||||
}
|
||||
|
||||
impl QueryRevisions {
|
||||
pub(crate) fn stamped_value<V>(&self, value: V) -> StampedValue<V> {
|
||||
StampedValue {
|
||||
value,
|
||||
durability: self.durability,
|
||||
changed_at: self.changed_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks the way that a memoized value for a query was created.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum QueryOrigin {
|
||||
/// The value was assigned as the output of another query (e.g., using `specify`).
|
||||
/// The `DatabaseKeyIndex` is the identity of the assigning query.
|
||||
Assigned(DatabaseKeyIndex),
|
||||
|
||||
/// This value was set as a base input to the computation.
|
||||
BaseInput,
|
||||
|
||||
/// The value was derived by executing a function
|
||||
/// and we were able to track ALL of that function's inputs.
|
||||
/// Those inputs are described in [`QueryEdges`].
|
||||
Derived(QueryEdges),
|
||||
|
||||
/// The value was derived by executing a function
|
||||
/// but that function also reported that it read untracked inputs.
|
||||
/// The [`QueryEdges`] argument contains a listing of all the inputs we saw
|
||||
/// (but we know there were more).
|
||||
DerivedUntracked(QueryEdges),
|
||||
}
|
||||
|
||||
impl QueryOrigin {
|
||||
/// Indices for queries *written* by this query (or `vec![]` if its value was assigned).
|
||||
pub(crate) fn outputs(&self) -> impl Iterator<Item = DependencyIndex> + '_ {
|
||||
let opt_edges = match self {
|
||||
QueryOrigin::Derived(edges) | QueryOrigin::DerivedUntracked(edges) => Some(edges),
|
||||
QueryOrigin::Assigned(_) | QueryOrigin::BaseInput => None,
|
||||
};
|
||||
opt_edges.into_iter().flat_map(|edges| edges.outputs())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum EdgeKind {
|
||||
Input,
|
||||
Output,
|
||||
}
|
||||
|
||||
/// The edges between a memoized value and other queries in the dependency graph.
|
||||
/// These edges include both dependency edges
|
||||
/// e.g., when creating the memoized value for Q0 executed another function Q1)
|
||||
/// and output edges
|
||||
/// (e.g., when Q0 specified the value for another query Q2).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QueryEdges {
|
||||
/// The list of outgoing edges from this node.
|
||||
/// This list combines *both* inputs and outputs.
|
||||
///
|
||||
/// Note that we always track input dependencies even when there are untracked reads.
|
||||
/// Untracked reads mean that we can't verify values, so we don't use the list of inputs for that,
|
||||
/// but we still use it for finding the transitive inputs to an accumulator.
|
||||
///
|
||||
/// You can access the input/output list via the methods [`inputs`] and [`outputs`] respectively.
|
||||
///
|
||||
/// Important:
|
||||
///
|
||||
/// * The inputs must be in **execution order** for the red-green algorithm to work.
|
||||
pub input_outputs: Arc<[(EdgeKind, DependencyIndex)]>,
|
||||
}
|
||||
|
||||
impl QueryEdges {
|
||||
/// Returns the (tracked) inputs that were executed in computing this memoized value.
|
||||
///
|
||||
/// These will always be in execution order.
|
||||
pub(crate) fn inputs(&self) -> impl Iterator<Item = DependencyIndex> + '_ {
|
||||
self.input_outputs
|
||||
.iter()
|
||||
.filter(|(edge_kind, _)| *edge_kind == EdgeKind::Input)
|
||||
.map(|(_, dependency_index)| *dependency_index)
|
||||
}
|
||||
|
||||
/// Returns the (tracked) outputs that were executed in computing this memoized value.
|
||||
///
|
||||
/// These will always be in execution order.
|
||||
pub(crate) fn outputs(&self) -> impl Iterator<Item = DependencyIndex> + '_ {
|
||||
self.input_outputs
|
||||
.iter()
|
||||
.filter(|(edge_kind, _)| *edge_kind == EdgeKind::Output)
|
||||
.map(|(_, dependency_index)| *dependency_index)
|
||||
}
|
||||
|
||||
/// Creates a new `QueryEdges`; the values given for each field must meet struct invariants.
|
||||
pub(crate) fn new(input_outputs: Arc<[(EdgeKind, DependencyIndex)]>) -> Self {
|
||||
Self { input_outputs }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LocalState {
|
||||
fn default() -> Self {
|
||||
LocalState {
|
||||
query_stack: RefCell::new(Some(Vec::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalState {
|
||||
#[inline]
|
||||
pub(super) fn push_query(&self, database_key_index: DatabaseKeyIndex) -> ActiveQueryGuard<'_> {
|
||||
let mut query_stack = self.query_stack.borrow_mut();
|
||||
let query_stack = query_stack.as_mut().expect("local stack taken");
|
||||
query_stack.push(ActiveQuery::new(database_key_index));
|
||||
ActiveQueryGuard {
|
||||
local_state: self,
|
||||
database_key_index,
|
||||
push_len: query_stack.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_query_stack<R>(&self, c: impl FnOnce(&mut Vec<ActiveQuery>) -> R) -> R {
|
||||
c(self
|
||||
.query_stack
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.expect("query stack taken"))
|
||||
}
|
||||
|
||||
pub(super) fn query_in_progress(&self) -> bool {
|
||||
self.with_query_stack(|stack| !stack.is_empty())
|
||||
}
|
||||
|
||||
/// Returns the index of the active query along with its *current* durability/changed-at
|
||||
/// information. As the query continues to execute, naturally, that information may change.
|
||||
pub(super) fn active_query(&self) -> Option<(DatabaseKeyIndex, StampedValue<()>)> {
|
||||
self.with_query_stack(|stack| {
|
||||
stack.last().map(|active_query| {
|
||||
(
|
||||
active_query.database_key_index,
|
||||
StampedValue {
|
||||
value: (),
|
||||
durability: active_query.durability,
|
||||
changed_at: active_query.changed_at,
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn add_output(&self, entity: DependencyIndex) {
|
||||
self.with_query_stack(|stack| {
|
||||
if let Some(top_query) = stack.last_mut() {
|
||||
top_query.add_output(entity)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn is_output(&self, entity: DependencyIndex) -> bool {
|
||||
self.with_query_stack(|stack| {
|
||||
if let Some(top_query) = stack.last_mut() {
|
||||
top_query.is_output(entity)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn report_tracked_read(
|
||||
&self,
|
||||
input: DependencyIndex,
|
||||
durability: Durability,
|
||||
changed_at: Revision,
|
||||
) {
|
||||
debug!(
|
||||
"report_query_read_and_unwind_if_cycle_resulted(input={:?}, durability={:?}, changed_at={:?})",
|
||||
input, durability, changed_at
|
||||
);
|
||||
self.with_query_stack(|stack| {
|
||||
if let Some(top_query) = stack.last_mut() {
|
||||
top_query.add_read(input, durability, changed_at);
|
||||
|
||||
// We are a cycle participant:
|
||||
//
|
||||
// C0 --> ... --> Ci --> Ci+1 -> ... -> Cn --> C0
|
||||
// ^ ^
|
||||
// : |
|
||||
// This edge -----+ |
|
||||
// |
|
||||
// |
|
||||
// N0
|
||||
//
|
||||
// In this case, the value we have just read from `Ci+1`
|
||||
// is actually the cycle fallback value and not especially
|
||||
// interesting. We unwind now with `CycleParticipant` to avoid
|
||||
// executing the rest of our query function. This unwinding
|
||||
// will be caught and our own fallback value will be used.
|
||||
//
|
||||
// Note that `Ci+1` may` have *other* callers who are not
|
||||
// participants in the cycle (e.g., N0 in the graph above).
|
||||
// They will not have the `cycle` marker set in their
|
||||
// stack frames, so they will just read the fallback value
|
||||
// from `Ci+1` and continue on their merry way.
|
||||
if let Some(cycle) = &top_query.cycle {
|
||||
cycle.clone().throw()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn report_untracked_read(&self, current_revision: Revision) {
|
||||
self.with_query_stack(|stack| {
|
||||
if let Some(top_query) = stack.last_mut() {
|
||||
top_query.add_untracked_read(current_revision);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Update the top query on the stack to act as though it read a value
|
||||
/// of durability `durability` which changed in `revision`.
|
||||
// FIXME: Use or remove this.
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn report_synthetic_read(&self, durability: Durability, revision: Revision) {
|
||||
self.with_query_stack(|stack| {
|
||||
if let Some(top_query) = stack.last_mut() {
|
||||
top_query.add_synthetic_read(durability, revision);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Takes the query stack and returns it. This is used when
|
||||
/// the current thread is blocking. The stack must be restored
|
||||
/// with [`Self::restore_query_stack`] when the thread unblocks.
|
||||
pub(super) fn take_query_stack(&self) -> Vec<ActiveQuery> {
|
||||
assert!(
|
||||
self.query_stack.borrow().is_some(),
|
||||
"query stack already taken"
|
||||
);
|
||||
self.query_stack.take().unwrap()
|
||||
}
|
||||
|
||||
/// Restores a query stack taken with [`Self::take_query_stack`] once
|
||||
/// the thread unblocks.
|
||||
pub(super) fn restore_query_stack(&self, stack: Vec<ActiveQuery>) {
|
||||
assert!(self.query_stack.borrow().is_none(), "query stack not taken");
|
||||
self.query_stack.replace(Some(stack));
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn disambiguate(&self, data_hash: u64) -> (DatabaseKeyIndex, Disambiguator) {
|
||||
assert!(
|
||||
self.query_in_progress(),
|
||||
"cannot create a tracked struct disambiguator outside of a tracked function"
|
||||
);
|
||||
self.with_query_stack(|stack| {
|
||||
let top_query = stack.last_mut().unwrap();
|
||||
let disambiguator = top_query.disambiguate(data_hash);
|
||||
(top_query.database_key_index, disambiguator)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::panic::RefUnwindSafe for LocalState {}
|
||||
|
||||
/// When a query is pushed onto the `active_query` stack, this guard
|
||||
/// is returned to represent its slot. The guard can be used to pop
|
||||
/// the query from the stack -- in the case of unwinding, the guard's
|
||||
/// destructor will also remove the query.
|
||||
pub(crate) struct ActiveQueryGuard<'me> {
|
||||
local_state: &'me LocalState,
|
||||
push_len: usize,
|
||||
pub(crate) database_key_index: DatabaseKeyIndex,
|
||||
}
|
||||
|
||||
impl ActiveQueryGuard<'_> {
|
||||
fn pop_helper(&self) -> ActiveQuery {
|
||||
self.local_state.with_query_stack(|stack| {
|
||||
// Sanity check: pushes and pops should be balanced.
|
||||
assert_eq!(stack.len(), self.push_len);
|
||||
debug_assert_eq!(
|
||||
stack.last().unwrap().database_key_index,
|
||||
self.database_key_index
|
||||
);
|
||||
stack.pop().unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
/// Invoked when the query has successfully completed execution.
|
||||
pub(super) fn complete(self) -> ActiveQuery {
|
||||
let query = self.pop_helper();
|
||||
std::mem::forget(self);
|
||||
query
|
||||
}
|
||||
|
||||
/// Pops an active query from the stack. Returns the [`QueryRevisions`]
|
||||
/// which summarizes the other queries that were accessed during this
|
||||
/// query's execution.
|
||||
#[inline]
|
||||
pub(crate) fn pop(self, runtime: &Runtime) -> QueryRevisions {
|
||||
// Extract accumulated inputs.
|
||||
let popped_query = self.complete();
|
||||
|
||||
// If this frame were a cycle participant, it would have unwound.
|
||||
assert!(popped_query.cycle.is_none());
|
||||
|
||||
popped_query.revisions(runtime)
|
||||
}
|
||||
|
||||
/// If the active query is registered as a cycle participant, remove and
|
||||
/// return that cycle.
|
||||
pub(crate) fn take_cycle(&self) -> Option<Cycle> {
|
||||
self.local_state
|
||||
.with_query_stack(|stack| stack.last_mut()?.cycle.take())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ActiveQueryGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.pop_helper();
|
||||
}
|
||||
}
|
||||
56
third-party/vendor/salsa-2022/src/runtime/shared_state.rs
vendored
Normal file
56
third-party/vendor/salsa-2022/src/runtime/shared_state.rs
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use std::sync::{atomic::AtomicUsize, Arc};
|
||||
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{durability::Durability, key::DependencyIndex, revision::AtomicRevision};
|
||||
|
||||
use super::{dependency_graph::DependencyGraph, local_state::EdgeKind};
|
||||
|
||||
/// State that will be common to all threads (when we support multiple threads)
|
||||
#[derive(Debug)]
|
||||
pub(super) struct SharedState {
|
||||
/// Stores the next id to use for a snapshotted runtime (starts at 1).
|
||||
pub(super) next_id: AtomicUsize,
|
||||
|
||||
/// Vector we can clone
|
||||
pub(super) empty_dependencies: Arc<[(EdgeKind, DependencyIndex)]>,
|
||||
|
||||
/// Set to true when the current revision has been canceled.
|
||||
/// This is done when we an input is being changed. The flag
|
||||
/// is set back to false once the input has been changed.
|
||||
pub(super) revision_canceled: AtomicCell<bool>,
|
||||
|
||||
/// Stores the "last change" revision for values of each duration.
|
||||
/// This vector is always of length at least 1 (for Durability 0)
|
||||
/// but its total length depends on the number of durations. The
|
||||
/// element at index 0 is special as it represents the "current
|
||||
/// revision". In general, we have the invariant that revisions
|
||||
/// in here are *declining* -- that is, `revisions[i] >=
|
||||
/// revisions[i + 1]`, for all `i`. This is because when you
|
||||
/// modify a value with durability D, that implies that values
|
||||
/// with durability less than D may have changed too.
|
||||
pub(super) revisions: Vec<AtomicRevision>,
|
||||
|
||||
/// The dependency graph tracks which runtimes are blocked on one
|
||||
/// another, waiting for queries to terminate.
|
||||
pub(super) dependency_graph: Mutex<DependencyGraph>,
|
||||
}
|
||||
|
||||
impl Default for SharedState {
|
||||
fn default() -> Self {
|
||||
Self::with_durabilities(Durability::LEN)
|
||||
}
|
||||
}
|
||||
|
||||
impl SharedState {
|
||||
fn with_durabilities(durabilities: usize) -> Self {
|
||||
SharedState {
|
||||
next_id: AtomicUsize::new(1),
|
||||
empty_dependencies: None.into_iter().collect(),
|
||||
revision_canceled: Default::default(),
|
||||
revisions: (0..durabilities).map(|_| AtomicRevision::start()).collect(),
|
||||
dependency_graph: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
15
third-party/vendor/salsa-2022/src/salsa_struct.rs
vendored
Normal file
15
third-party/vendor/salsa-2022/src/salsa_struct.rs
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use crate::{Database, IngredientIndex};
|
||||
|
||||
pub trait SalsaStructInDb<DB: ?Sized + Database> {
|
||||
fn register_dependent_fn(db: &DB, index: IngredientIndex);
|
||||
}
|
||||
|
||||
/// A ZST that implements [`SalsaStructInDb`]
|
||||
///
|
||||
/// It is used for implementing "constant" tracked function
|
||||
/// (ones that only take a database as an argument).
|
||||
pub struct Singleton;
|
||||
|
||||
impl<DB: ?Sized + Database> SalsaStructInDb<DB> for Singleton {
|
||||
fn register_dependent_fn(_db: &DB, _index: IngredientIndex) {}
|
||||
}
|
||||
39
third-party/vendor/salsa-2022/src/setter.rs
vendored
Normal file
39
third-party/vendor/salsa-2022/src/setter.rs
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use crate::input_field::InputFieldIngredient;
|
||||
use crate::{AsId, Durability, Runtime};
|
||||
use std::hash::Hash;
|
||||
|
||||
#[must_use]
|
||||
pub struct Setter<'setter, K, F> {
|
||||
runtime: &'setter mut Runtime,
|
||||
key: K,
|
||||
ingredient: &'setter mut InputFieldIngredient<K, F>,
|
||||
durability: Durability,
|
||||
}
|
||||
|
||||
impl<'setter, K, F> Setter<'setter, K, F>
|
||||
where
|
||||
K: Eq + Hash + AsId,
|
||||
{
|
||||
pub fn new(
|
||||
runtime: &'setter mut Runtime,
|
||||
key: K,
|
||||
ingredient: &'setter mut InputFieldIngredient<K, F>,
|
||||
) -> Self {
|
||||
Setter {
|
||||
runtime,
|
||||
key,
|
||||
ingredient,
|
||||
durability: Durability::LOW,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_durability(self, durability: Durability) -> Self {
|
||||
Setter { durability, ..self }
|
||||
}
|
||||
|
||||
pub fn to(self, value: F) -> F {
|
||||
self.ingredient
|
||||
.store_mut(self.runtime, self.key, value, self.durability)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
264
third-party/vendor/salsa-2022/src/storage.rs
vendored
Normal file
264
third-party/vendor/salsa-2022/src/storage.rs
vendored
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
use std::{fmt, sync::Arc};
|
||||
|
||||
use parking_lot::Condvar;
|
||||
|
||||
use crate::cycle::CycleRecoveryStrategy;
|
||||
use crate::ingredient::Ingredient;
|
||||
use crate::jar::Jar;
|
||||
use crate::key::DependencyIndex;
|
||||
use crate::runtime::local_state::QueryOrigin;
|
||||
use crate::runtime::Runtime;
|
||||
use crate::{Database, DatabaseKeyIndex, Id, IngredientIndex};
|
||||
|
||||
use super::routes::Routes;
|
||||
use super::{ParallelDatabase, Revision};
|
||||
|
||||
/// 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> {
|
||||
/// 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>>,
|
||||
|
||||
/// The runtime for this particular salsa database handle.
|
||||
/// Each handle gets its own runtime, but the runtimes have shared state between them.
|
||||
runtime: Runtime,
|
||||
}
|
||||
|
||||
/// 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`]).
|
||||
///
|
||||
/// 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>>,
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
// ANCHOR: default
|
||||
impl<DB> Default for Storage<DB>
|
||||
where
|
||||
DB: HasJars,
|
||||
{
|
||||
fn default() -> Self {
|
||||
let mut routes = Routes::new();
|
||||
let jars = DB::create_jars(&mut routes);
|
||||
Self {
|
||||
shared: Shared {
|
||||
jars: Some(Arc::from(jars)),
|
||||
cvar: Arc::new(Default::default()),
|
||||
},
|
||||
routes: Arc::new(routes),
|
||||
runtime: Runtime::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: default
|
||||
|
||||
impl<DB> Storage<DB>
|
||||
where
|
||||
DB: HasJars,
|
||||
{
|
||||
pub fn snapshot(&self) -> Storage<DB>
|
||||
where
|
||||
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.
|
||||
///
|
||||
/// This could deadlock if there is a single worker with two handles to the
|
||||
/// same database!
|
||||
fn cancel_other_workers(&mut self) {
|
||||
loop {
|
||||
self.runtime.set_cancellation_flag();
|
||||
|
||||
// If we have unique access to the jars, we are done.
|
||||
if Arc::get_mut(self.shared.jars.as_mut().unwrap()).is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, wait until some other storage entities have dropped.
|
||||
// We create a mutex here because the cvar api requires it, but we
|
||||
// don't really need one as the data being protected is actually
|
||||
// the jars above.
|
||||
//
|
||||
// The cvar `self.shared.cvar` is notified by the `Drop` impl.
|
||||
let mutex = parking_lot::Mutex::new(());
|
||||
let mut guard = mutex.lock();
|
||||
self.shared.cvar.wait(&mut guard);
|
||||
}
|
||||
}
|
||||
// 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,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
jars: self.jars.clone(),
|
||||
cvar: self.cvar.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> Drop for Storage<DB>
|
||||
where
|
||||
DB: HasJars,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
// Drop the Arc reference before the cvar is notified,
|
||||
// since other threads are sleeping, waiting for it to reach 1.
|
||||
drop(self.shared.jars.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) -> &<J as Jar<'db>>::DynDb
|
||||
where
|
||||
J: Jar<'db>;
|
||||
}
|
||||
|
||||
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 {
|
||||
fn runtime(&self) -> &Runtime;
|
||||
|
||||
fn runtime_mut(&mut self) -> &mut Runtime;
|
||||
|
||||
fn maybe_changed_after(&self, input: DependencyIndex, revision: Revision) -> bool;
|
||||
|
||||
fn cycle_recovery_strategy(&self, input: IngredientIndex) -> CycleRecoveryStrategy;
|
||||
|
||||
fn origin(&self, input: DatabaseKeyIndex) -> Option<QueryOrigin>;
|
||||
|
||||
fn mark_validated_output(&self, executor: DatabaseKeyIndex, output: DependencyIndex);
|
||||
|
||||
/// Invoked when `executor` used to output `stale_output` but no longer does.
|
||||
/// This method routes that into a call to the [`remove_stale_output`](`crate::ingredient::Ingredient::remove_stale_output`)
|
||||
/// method on the ingredient for `stale_output`.
|
||||
fn remove_stale_output(&self, executor: DatabaseKeyIndex, stale_output: DependencyIndex);
|
||||
|
||||
/// Informs `ingredient` that the salsa struct with id `id` has been deleted.
|
||||
/// This means that `id` will not be used in this revision and hence
|
||||
/// any memoized values keyed by that struct can be discarded.
|
||||
///
|
||||
/// In order to receive this callback, `ingredient` must have registered itself
|
||||
/// as a dependent function using
|
||||
/// [`SalsaStructInDb::register_dependent_fn`](`crate::salsa_struct::SalsaStructInDb::register_dependent_fn`).
|
||||
fn salsa_struct_deleted(&self, ingredient: IngredientIndex, id: Id);
|
||||
|
||||
fn fmt_index(&self, index: DependencyIndex, fmt: &mut fmt::Formatter<'_>) -> fmt::Result;
|
||||
}
|
||||
// ANCHOR_END: HasJarsDyn
|
||||
|
||||
pub trait HasIngredientsFor<I>
|
||||
where
|
||||
I: IngredientsFor,
|
||||
{
|
||||
fn ingredient(&self) -> &I::Ingredients;
|
||||
fn ingredient_mut(&mut self) -> &mut I::Ingredients;
|
||||
}
|
||||
|
||||
pub trait IngredientsFor {
|
||||
type Jar;
|
||||
type Ingredients;
|
||||
|
||||
fn create_ingredients<DB>(routes: &mut Routes<DB>) -> Self::Ingredients
|
||||
where
|
||||
DB: DbWithJar<Self::Jar> + JarFromJars<Self::Jar>;
|
||||
}
|
||||
198
third-party/vendor/salsa-2022/src/tracked_struct.rs
vendored
Normal file
198
third-party/vendor/salsa-2022/src/tracked_struct.rs
vendored
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::{
|
||||
cycle::CycleRecoveryStrategy,
|
||||
ingredient::{fmt_index, Ingredient, IngredientRequiresReset},
|
||||
ingredient_list::IngredientList,
|
||||
interned::{InternedData, InternedId, InternedIngredient},
|
||||
key::{DatabaseKeyIndex, DependencyIndex},
|
||||
runtime::{local_state::QueryOrigin, Runtime},
|
||||
salsa_struct::SalsaStructInDb,
|
||||
Database, Event, IngredientIndex, Revision,
|
||||
};
|
||||
|
||||
pub trait TrackedStructId: InternedId {}
|
||||
impl<T: InternedId> TrackedStructId for T {}
|
||||
|
||||
pub trait TrackedStructData: InternedData {}
|
||||
impl<T: InternedData> TrackedStructData for T {}
|
||||
|
||||
pub trait TrackedStructInDb<DB: ?Sized + Database>: SalsaStructInDb<DB> {
|
||||
/// Converts the identifier for this tracked struct into a `DatabaseKeyIndex`.
|
||||
fn database_key_index(self, db: &DB) -> DatabaseKeyIndex;
|
||||
}
|
||||
|
||||
/// Created for each tracked struct.
|
||||
/// This ingredient only stores the "id" fields.
|
||||
/// It is a kind of "dressed up" interner;
|
||||
/// the active query + values of id fields are hashed to create the tracked struct id.
|
||||
/// The value fields are stored in [`crate::function::FunctionIngredient`] instances keyed by the tracked struct id.
|
||||
/// Unlike normal interners, tracked struct indices can be deleted and reused aggressively:
|
||||
/// when a tracked function re-executes,
|
||||
/// any tracked structs that it created before but did not create this time can be deleted.
|
||||
pub struct TrackedStructIngredient<Id, Data>
|
||||
where
|
||||
Id: TrackedStructId,
|
||||
Data: TrackedStructData,
|
||||
{
|
||||
interned: InternedIngredient<Id, TrackedStructKey<Data>>,
|
||||
|
||||
/// 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,
|
||||
|
||||
debug_name: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
|
||||
struct TrackedStructKey<Data> {
|
||||
query_key: Option<DatabaseKeyIndex>,
|
||||
disambiguator: Disambiguator,
|
||||
data: Data,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
|
||||
pub struct Disambiguator(pub u32);
|
||||
|
||||
impl<Id, Data> TrackedStructIngredient<Id, Data>
|
||||
where
|
||||
Id: TrackedStructId,
|
||||
Data: TrackedStructData,
|
||||
{
|
||||
pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self {
|
||||
Self {
|
||||
interned: InternedIngredient::new(index, debug_name),
|
||||
dependent_fns: IngredientList::new(),
|
||||
debug_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn database_key_index(&self, id: Id) -> DatabaseKeyIndex {
|
||||
DatabaseKeyIndex {
|
||||
ingredient_index: self.interned.ingredient_index(),
|
||||
key_index: id.as_id(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_struct(&self, runtime: &Runtime, data: Data) -> Id {
|
||||
let data_hash = crate::hash::hash(&data);
|
||||
let (query_key, disambiguator) = runtime.disambiguate_entity(
|
||||
self.interned.ingredient_index(),
|
||||
self.interned.reset_at(),
|
||||
data_hash,
|
||||
);
|
||||
let entity_key = TrackedStructKey {
|
||||
query_key: Some(query_key),
|
||||
disambiguator,
|
||||
data,
|
||||
};
|
||||
let result = self.interned.intern(runtime, entity_key);
|
||||
runtime.add_output(self.database_key_index(result).into());
|
||||
result
|
||||
}
|
||||
|
||||
pub fn tracked_struct_data<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db Data {
|
||||
&self.interned.data(runtime, id).data
|
||||
}
|
||||
|
||||
/// Deletes the given entities. This is used after a query `Q` executes and we can compare
|
||||
/// the entities `E_now` that it produced in this revision vs the entities
|
||||
/// `E_prev` it produced in the last revision. Any missing entities `E_prev - E_new` can be
|
||||
/// deleted.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// Using this method on an entity id that MAY be used in the current revision will lead to
|
||||
/// unspecified results (but not UB). See [`InternedIngredient::delete_index`] for more
|
||||
/// discussion and important considerations.
|
||||
pub(crate) fn delete_entity(&self, db: &dyn crate::Database, id: Id) {
|
||||
db.salsa_event(Event {
|
||||
runtime_id: db.runtime().id(),
|
||||
kind: crate::EventKind::DidDiscard {
|
||||
key: self.database_key_index(id),
|
||||
},
|
||||
});
|
||||
|
||||
self.interned.delete_index(id);
|
||||
for dependent_fn in self.dependent_fns.iter() {
|
||||
db.salsa_struct_deleted(dependent_fn, id.as_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<DB: ?Sized, Id, Data> Ingredient<DB> for TrackedStructIngredient<Id, Data>
|
||||
where
|
||||
Id: TrackedStructId,
|
||||
Data: TrackedStructData,
|
||||
DB: crate::Database,
|
||||
{
|
||||
fn ingredient_index(&self) -> IngredientIndex {
|
||||
self.interned.ingredient_index()
|
||||
}
|
||||
|
||||
fn maybe_changed_after(&self, db: &DB, input: DependencyIndex, revision: Revision) -> bool {
|
||||
self.interned.maybe_changed_after(db, input, revision)
|
||||
}
|
||||
|
||||
fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy {
|
||||
<_ as Ingredient<DB>>::cycle_recovery_strategy(&self.interned)
|
||||
}
|
||||
|
||||
fn origin(&self, _key_index: crate::Id) -> Option<QueryOrigin> {
|
||||
None
|
||||
}
|
||||
|
||||
fn mark_validated_output(
|
||||
&self,
|
||||
_db: &DB,
|
||||
_executor: DatabaseKeyIndex,
|
||||
_output_key: Option<crate::Id>,
|
||||
) {
|
||||
// FIXME
|
||||
}
|
||||
|
||||
fn remove_stale_output(
|
||||
&self,
|
||||
db: &DB,
|
||||
_executor: DatabaseKeyIndex,
|
||||
stale_output_key: Option<crate::Id>,
|
||||
) {
|
||||
// This method is called when, in prior revisions,
|
||||
// `executor` creates a tracked struct `salsa_output_key`,
|
||||
// but it did not in the current revision.
|
||||
// In that case, we can delete `stale_output_key` and any data associated with it.
|
||||
let stale_output_key: Id = Id::from_id(stale_output_key.unwrap());
|
||||
self.delete_entity(db.as_salsa_database(), stale_output_key);
|
||||
}
|
||||
|
||||
fn reset_for_new_revision(&mut self) {
|
||||
self.interned.clear_deleted_indices();
|
||||
}
|
||||
|
||||
fn salsa_struct_deleted(&self, _db: &DB, _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(self.debug_name, index, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Id, Data> IngredientRequiresReset for TrackedStructIngredient<Id, Data>
|
||||
where
|
||||
Id: TrackedStructId,
|
||||
Data: TrackedStructData,
|
||||
{
|
||||
const RESET_ON_NEW_REVISION: bool = true;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue