Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View 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;
}

View 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 {}

View 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,
}

View 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
}
}

View 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 {}
}

View 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
}
}

View 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(),
}
}
}

View 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;
}

View 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)
}
}
}
}
}
}
}

View 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;
}
}
}
}

View 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
}
}
}

View 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);
}
}

View 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
}
}

View 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);
}
}

View 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())
}
}

View 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();
}
}
}

View 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
}
}

View 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);
}
}
}

View 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);
}
}

View 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);
}
}
}

View 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)
}
}

View 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
View 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());
}
}

View 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;
}

View 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;
}
}
}
}

View 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;
}

View 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;
}

View 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,)
}
}

View 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
View 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,
})
}
}

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

View 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) }
}
}

View 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);
}
}

View 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))
}
}

View 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);
}
}

View 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
}
}

View 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();
}
}

View 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();
}
}

View 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(),
}
}
}

View 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) {}
}

View 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()
}
}

View 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>;
}

View 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;
}