Do manual trait casting (#922)

* Do manual trait upcasting instead of downcasting

* Remove another dynamic `zalsa` call

* Rename UpCaster back to DownCaster

* Address reviews
This commit is contained in:
Lukas Wirth 2025-07-31 17:56:13 +02:00 committed by GitHub
parent f3dc2f30f9
commit 211bc158df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 373 additions and 278 deletions

View file

@ -118,8 +118,7 @@ macro_rules! setup_input_struct {
} }
} }
pub fn ingredient_mut(db: &mut dyn $zalsa::Database) -> (&mut $zalsa_struct::IngredientImpl<Self>, &mut $zalsa::Runtime) { pub fn ingredient_mut(zalsa_mut: &mut $zalsa::Zalsa) -> (&mut $zalsa_struct::IngredientImpl<Self>, &mut $zalsa::Runtime) {
let zalsa_mut = db.zalsa_mut();
zalsa_mut.new_revision(); zalsa_mut.new_revision();
let index = zalsa_mut.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>(); let index = zalsa_mut.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>();
let (ingredient, runtime) = zalsa_mut.lookup_ingredient_mut(index); let (ingredient, runtime) = zalsa_mut.lookup_ingredient_mut(index);
@ -208,8 +207,10 @@ macro_rules! setup_input_struct {
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database` // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + $zalsa::Database, $Db: ?Sized + $zalsa::Database,
{ {
let fields = $Configuration::ingredient_(db.zalsa()).field( let (zalsa, zalsa_local) = db.zalsas();
db.as_dyn_database(), let fields = $Configuration::ingredient_(zalsa).field(
zalsa,
zalsa_local,
self, self,
$field_index, $field_index,
); );
@ -228,7 +229,8 @@ macro_rules! setup_input_struct {
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database` // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + $zalsa::Database, $Db: ?Sized + $zalsa::Database,
{ {
let (ingredient, revision) = $Configuration::ingredient_mut(db.as_dyn_database_mut()); let zalsa = db.zalsa_mut();
let (ingredient, revision) = $Configuration::ingredient_mut(zalsa);
$zalsa::input::SetterImpl::new( $zalsa::input::SetterImpl::new(
revision, revision,
self, self,
@ -267,7 +269,8 @@ macro_rules! setup_input_struct {
$(for<'__trivial_bounds> $field_ty: std::fmt::Debug),* $(for<'__trivial_bounds> $field_ty: std::fmt::Debug),*
{ {
$zalsa::with_attached_database(|db| { $zalsa::with_attached_database(|db| {
let fields = $Configuration::ingredient(db).leak_fields(db, this); let zalsa = db.zalsa();
let fields = $Configuration::ingredient_(zalsa).leak_fields(zalsa, this);
let mut f = f.debug_struct(stringify!($Struct)); let mut f = f.debug_struct(stringify!($Struct));
let f = f.field("[salsa id]", &$zalsa::AsId::as_id(&this)); let f = f.field("[salsa id]", &$zalsa::AsId::as_id(&this));
$( $(
@ -296,11 +299,11 @@ macro_rules! setup_input_struct {
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database` // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + salsa::Database $Db: ?Sized + salsa::Database
{ {
let zalsa = db.zalsa(); let (zalsa, zalsa_local) = db.zalsas();
let current_revision = zalsa.current_revision(); let current_revision = zalsa.current_revision();
let ingredient = $Configuration::ingredient_(zalsa); let ingredient = $Configuration::ingredient_(zalsa);
let (fields, revision, durabilities) = builder::builder_into_inner(self, current_revision); let (fields, revision, durabilities) = builder::builder_into_inner(self, current_revision);
ingredient.new_input(db.as_dyn_database(), fields, revision, durabilities) ingredient.new_input(zalsa, zalsa_local, fields, revision, durabilities)
} }
} }

View file

@ -149,15 +149,11 @@ macro_rules! setup_interned_struct {
} }
impl $Configuration { impl $Configuration {
pub fn ingredient<Db>(db: &Db) -> &$zalsa_struct::IngredientImpl<Self> pub fn ingredient(zalsa: &$zalsa::Zalsa) -> &$zalsa_struct::IngredientImpl<Self>
where
Db: ?Sized + $zalsa::Database,
{ {
static CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Configuration>> = static CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Configuration>> =
$zalsa::IngredientCache::new(); $zalsa::IngredientCache::new();
let zalsa = db.zalsa();
// SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the only // SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the only
// ingredient created by our jar is the struct ingredient. // ingredient created by our jar is the struct ingredient.
unsafe { unsafe {
@ -239,7 +235,8 @@ macro_rules! setup_interned_struct {
$field_ty: $zalsa::interned::HashEqLike<$indexed_ty>, $field_ty: $zalsa::interned::HashEqLike<$indexed_ty>,
)* )*
{ {
$Configuration::ingredient(db).intern(db.as_dyn_database(), let (zalsa, zalsa_local) = db.zalsas();
$Configuration::ingredient(zalsa).intern(zalsa, zalsa_local,
StructKey::<$db_lt>($($field_id,)* std::marker::PhantomData::default()), |_, data| ($($zalsa::interned::Lookup::into_owned(data.$field_index),)*)) StructKey::<$db_lt>($($field_id,)* std::marker::PhantomData::default()), |_, data| ($($zalsa::interned::Lookup::into_owned(data.$field_index),)*))
} }
@ -250,7 +247,8 @@ macro_rules! setup_interned_struct {
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database` // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + $zalsa::Database, $Db: ?Sized + $zalsa::Database,
{ {
let fields = $Configuration::ingredient(db).fields(db.as_dyn_database(), self); let zalsa = db.zalsa();
let fields = $Configuration::ingredient(zalsa).fields(zalsa, self);
$zalsa::return_mode_expression!( $zalsa::return_mode_expression!(
$field_option, $field_option,
$field_ty, $field_ty,
@ -262,7 +260,8 @@ macro_rules! setup_interned_struct {
/// Default debug formatting for this struct (may be useful if you define your own `Debug` impl) /// Default debug formatting for this struct (may be useful if you define your own `Debug` impl)
pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
$zalsa::with_attached_database(|db| { $zalsa::with_attached_database(|db| {
let fields = $Configuration::ingredient(db).fields(db.as_dyn_database(), this); let zalsa = db.zalsa();
let fields = $Configuration::ingredient(zalsa).fields(zalsa, this);
let mut f = f.debug_struct(stringify!($Struct)); let mut f = f.debug_struct(stringify!($Struct));
$( $(
let f = f.field(stringify!($field_id), &fields.$field_index); let f = f.field(stringify!($field_id), &fields.$field_index);

View file

@ -175,17 +175,21 @@ macro_rules! setup_tracked_fn {
impl $Configuration { impl $Configuration {
fn fn_ingredient(db: &dyn $Db) -> &$zalsa::function::IngredientImpl<$Configuration> { fn fn_ingredient(db: &dyn $Db) -> &$zalsa::function::IngredientImpl<$Configuration> {
let zalsa = db.zalsa(); let zalsa = db.zalsa();
Self::fn_ingredient_(db, zalsa)
}
#[inline]
fn fn_ingredient_<'z>(db: &dyn $Db, zalsa: &'z $zalsa::Zalsa) -> &'z $zalsa::function::IngredientImpl<$Configuration> {
// SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the first // SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the first
// ingredient created by our jar is the function ingredient. // ingredient created by our jar is the function ingredient.
unsafe { unsafe {
$FN_CACHE.get_or_create(zalsa, || zalsa.lookup_jar_by_type::<$fn_name>()) $FN_CACHE.get_or_create(zalsa, || zalsa.lookup_jar_by_type::<$fn_name>())
} }
.get_or_init(|| <dyn $Db as $Db>::zalsa_register_downcaster(db)) .get_or_init(|| *<dyn $Db as $Db>::zalsa_register_downcaster(db))
} }
pub fn fn_ingredient_mut(db: &mut dyn $Db) -> &mut $zalsa::function::IngredientImpl<Self> { pub fn fn_ingredient_mut(db: &mut dyn $Db) -> &mut $zalsa::function::IngredientImpl<Self> {
let view = <dyn $Db as $Db>::zalsa_register_downcaster(db); let view = *<dyn $Db as $Db>::zalsa_register_downcaster(db);
let zalsa_mut = db.zalsa_mut(); let zalsa_mut = db.zalsa_mut();
let index = zalsa_mut.lookup_jar_by_type::<$fn_name>(); let index = zalsa_mut.lookup_jar_by_type::<$fn_name>();
let (ingredient, _) = zalsa_mut.lookup_ingredient_mut(index); let (ingredient, _) = zalsa_mut.lookup_ingredient_mut(index);
@ -199,7 +203,12 @@ macro_rules! setup_tracked_fn {
db: &dyn $Db, db: &dyn $Db,
) -> &$zalsa::interned::IngredientImpl<$Configuration> { ) -> &$zalsa::interned::IngredientImpl<$Configuration> {
let zalsa = db.zalsa(); let zalsa = db.zalsa();
Self::intern_ingredient_(zalsa)
}
#[inline]
fn intern_ingredient_<'z>(
zalsa: &'z $zalsa::Zalsa
) -> &'z $zalsa::interned::IngredientImpl<$Configuration> {
// SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the second // SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the second
// ingredient created by our jar is the interned ingredient (given `needs_interner`). // ingredient created by our jar is the interned ingredient (given `needs_interner`).
unsafe { unsafe {
@ -257,12 +266,12 @@ macro_rules! setup_tracked_fn {
$($cycle_recovery_fn)*(db, value, count, $($input_id),*) $($cycle_recovery_fn)*(db, value, count, $($input_id),*)
} }
fn id_to_input<$db_lt>(db: &$db_lt Self::DbView, key: salsa::Id) -> Self::Input<$db_lt> { fn id_to_input<$db_lt>(zalsa: &$db_lt $zalsa::Zalsa, key: salsa::Id) -> Self::Input<$db_lt> {
$zalsa::macro_if! { $zalsa::macro_if! {
if $needs_interner { if $needs_interner {
$Configuration::intern_ingredient(db).data(db.as_dyn_database(), key).clone() $Configuration::intern_ingredient_(zalsa).data(zalsa, key).clone()
} else { } else {
$zalsa::FromIdWithDb::from_id(key, db.zalsa()) $zalsa::FromIdWithDb::from_id(key, zalsa)
} }
} }
} }
@ -340,9 +349,10 @@ macro_rules! setup_tracked_fn {
) -> Vec<&$db_lt A> { ) -> Vec<&$db_lt A> {
use salsa::plumbing as $zalsa; use salsa::plumbing as $zalsa;
let key = $zalsa::macro_if! { let key = $zalsa::macro_if! {
if $needs_interner { if $needs_interner {{
$Configuration::intern_ingredient($db).intern_id($db.as_dyn_database(), ($($input_id),*), |_, data| data) let (zalsa, zalsa_local) = $db.zalsas();
} else { $Configuration::intern_ingredient($db).intern_id(zalsa, zalsa_local, ($($input_id),*), |_, data| data)
}} else {
$zalsa::AsId::as_id(&($($input_id),*)) $zalsa::AsId::as_id(&($($input_id),*))
} }
}; };
@ -380,14 +390,17 @@ macro_rules! setup_tracked_fn {
} }
$zalsa::attach($db, || { $zalsa::attach($db, || {
let (zalsa, zalsa_local) = $db.zalsas();
let result = $zalsa::macro_if! { let result = $zalsa::macro_if! {
if $needs_interner { if $needs_interner {
{ {
let key = $Configuration::intern_ingredient($db).intern_id($db.as_dyn_database(), ($($input_id),*), |_, data| data); let key = $Configuration::intern_ingredient_(zalsa).intern_id(zalsa, zalsa_local, ($($input_id),*), |_, data| data);
$Configuration::fn_ingredient($db).fetch($db, key) $Configuration::fn_ingredient_($db, zalsa).fetch($db, zalsa, zalsa_local, key)
} }
} else { } else {
$Configuration::fn_ingredient($db).fetch($db, $zalsa::AsId::as_id(&($($input_id),*))) {
$Configuration::fn_ingredient_($db, zalsa).fetch($db, zalsa, zalsa_local, $zalsa::AsId::as_id(&($($input_id),*)))
}
} }
}; };

View file

@ -282,8 +282,9 @@ macro_rules! setup_tracked_struct {
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database` // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + $zalsa::Database, $Db: ?Sized + $zalsa::Database,
{ {
$Configuration::ingredient(db.as_dyn_database()).new_struct( let (zalsa, zalsa_local) = db.zalsas();
db.as_dyn_database(), $Configuration::ingredient_(zalsa).new_struct(
zalsa,zalsa_local,
($($field_id,)*) ($($field_id,)*)
) )
} }
@ -295,8 +296,8 @@ macro_rules! setup_tracked_struct {
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database` // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + $zalsa::Database, $Db: ?Sized + $zalsa::Database,
{ {
let db = db.as_dyn_database(); let (zalsa, zalsa_local) = db.zalsas();
let fields = $Configuration::ingredient(db).tracked_field(db, self, $relative_tracked_index); let fields = $Configuration::ingredient_(zalsa).tracked_field(zalsa, zalsa_local, self, $relative_tracked_index);
$crate::return_mode_expression!( $crate::return_mode_expression!(
$tracked_option, $tracked_option,
$tracked_ty, $tracked_ty,
@ -312,8 +313,8 @@ macro_rules! setup_tracked_struct {
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database` // FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + $zalsa::Database, $Db: ?Sized + $zalsa::Database,
{ {
let db = db.as_dyn_database(); let zalsa = db.zalsa();
let fields = $Configuration::ingredient(db).untracked_field(db, self); let fields = $Configuration::ingredient_(zalsa).untracked_field(zalsa, self);
$crate::return_mode_expression!( $crate::return_mode_expression!(
$untracked_option, $untracked_option,
$untracked_ty, $untracked_ty,
@ -335,7 +336,8 @@ macro_rules! setup_tracked_struct {
$(for<$db_lt> $field_ty: std::fmt::Debug),* $(for<$db_lt> $field_ty: std::fmt::Debug),*
{ {
$zalsa::with_attached_database(|db| { $zalsa::with_attached_database(|db| {
let fields = $Configuration::ingredient(db).leak_fields(db, this); let zalsa = db.zalsa();
let fields = $Configuration::ingredient_(zalsa).leak_fields(zalsa, this);
let mut f = f.debug_struct(stringify!($Struct)); let mut f = f.debug_struct(stringify!($Struct));
let f = f.field("[salsa id]", &$zalsa::AsId::as_id(&this)); let f = f.field("[salsa id]", &$zalsa::AsId::as_id(&this));
$( $(

View file

@ -110,18 +110,14 @@ impl DbMacro {
let trait_name = &input.ident; let trait_name = &input.ident;
input.items.push(parse_quote! { input.items.push(parse_quote! {
#[doc(hidden)] #[doc(hidden)]
fn zalsa_register_downcaster(&self) -> salsa::plumbing::DatabaseDownCaster<dyn #trait_name>; fn zalsa_register_downcaster(&self) -> &salsa::plumbing::DatabaseDownCaster<dyn #trait_name>;
}); });
let comment = format!(" Downcast a [`dyn Database`] to a [`dyn {trait_name}`]"); let comment = format!(" downcast `Self` to a [`dyn {trait_name}`]");
input.items.push(parse_quote! { input.items.push(parse_quote! {
#[doc = #comment] #[doc = #comment]
///
/// # Safety
///
/// The input database must be of type `Self`.
#[doc(hidden)] #[doc(hidden)]
unsafe fn downcast(db: &dyn salsa::plumbing::Database) -> &dyn #trait_name where Self: Sized; fn downcast(&self) -> &dyn #trait_name where Self: Sized;
}); });
Ok(()) Ok(())
} }
@ -138,17 +134,17 @@ impl DbMacro {
#[cold] #[cold]
#[inline(never)] #[inline(never)]
#[doc(hidden)] #[doc(hidden)]
fn zalsa_register_downcaster(&self) -> salsa::plumbing::DatabaseDownCaster<dyn #TraitPath> { fn zalsa_register_downcaster(&self) -> &salsa::plumbing::DatabaseDownCaster<dyn #TraitPath> {
salsa::plumbing::views(self).add(<Self as #TraitPath>::downcast) salsa::plumbing::views(self).add::<Self, dyn #TraitPath>(unsafe {
::std::mem::transmute(<Self as #TraitPath>::downcast as fn(_) -> _)
})
} }
}); });
input.items.push(parse_quote! { input.items.push(parse_quote! {
#[doc(hidden)] #[doc(hidden)]
#[inline(always)] #[inline(always)]
unsafe fn downcast(db: &dyn salsa::plumbing::Database) -> &dyn #TraitPath where Self: Sized { fn downcast(&self) -> &dyn #TraitPath where Self: Sized {
debug_assert_eq!(db.type_id(), ::core::any::TypeId::of::<Self>()); self
// SAFETY: The input database must be of type `Self`.
unsafe { &*salsa::plumbing::transmute_data_ptr::<dyn salsa::plumbing::Database, Self>(db) }
} }
}); });
Ok(()) Ok(())

View file

@ -102,7 +102,8 @@ impl<A: Accumulator> Ingredient for IngredientImpl<A> {
unsafe fn maybe_changed_after( unsafe fn maybe_changed_after(
&self, &self,
_db: &dyn Database, _zalsa: &crate::zalsa::Zalsa,
_db: crate::database::RawDatabase<'_>,
_input: Id, _input: Id,
_revision: Revision, _revision: Revision,
_cycle_heads: &mut CycleHeads, _cycle_heads: &mut CycleHeads,

View file

@ -1,13 +1,39 @@
use std::any::Any;
use std::borrow::Cow; use std::borrow::Cow;
use std::ptr::NonNull;
use crate::views::DatabaseDownCaster; use crate::views::DatabaseDownCaster;
use crate::zalsa::{IngredientIndex, ZalsaDatabase}; use crate::zalsa::{IngredientIndex, ZalsaDatabase};
use crate::{Durability, Revision}; use crate::{Durability, Revision};
#[derive(Copy, Clone)]
pub struct RawDatabase<'db> {
pub(crate) ptr: NonNull<()>,
_marker: std::marker::PhantomData<&'db dyn Database>,
}
impl<'db, Db: Database + ?Sized> From<&'db Db> for RawDatabase<'db> {
#[inline]
fn from(db: &'db Db) -> Self {
RawDatabase {
ptr: NonNull::from(db).cast(),
_marker: std::marker::PhantomData,
}
}
}
impl<'db, Db: Database + ?Sized> From<&'db mut Db> for RawDatabase<'db> {
#[inline]
fn from(db: &'db mut Db) -> Self {
RawDatabase {
ptr: NonNull::from(db).cast(),
_marker: std::marker::PhantomData,
}
}
}
/// The trait implemented by all Salsa databases. /// The trait implemented by all Salsa databases.
/// You can create your own subtraits of this trait using the `#[salsa::db]`(`crate::db`) procedural macro. /// You can create your own subtraits of this trait using the `#[salsa::db]`(`crate::db`) procedural macro.
pub trait Database: Send + AsDynDatabase + Any + ZalsaDatabase { pub trait Database: Send + ZalsaDatabase + AsDynDatabase {
/// Enforces current LRU limits, evicting entries if necessary. /// Enforces current LRU limits, evicting entries if necessary.
/// ///
/// **WARNING:** Just like an ordinary write, this method triggers /// **WARNING:** Just like an ordinary write, this method triggers
@ -84,28 +110,27 @@ pub trait Database: Send + AsDynDatabase + Any + ZalsaDatabase {
#[cold] #[cold]
#[inline(never)] #[inline(never)]
#[doc(hidden)] #[doc(hidden)]
fn zalsa_register_downcaster(&self) -> DatabaseDownCaster<dyn Database> { fn zalsa_register_downcaster(&self) -> &DatabaseDownCaster<dyn Database> {
self.zalsa().views().downcaster_for::<dyn Database>() self.zalsa().views().downcaster_for::<dyn Database>()
// The no-op downcaster is special cased in view caster construction. // The no-op downcaster is special cased in view caster construction.
} }
#[doc(hidden)] #[doc(hidden)]
#[inline(always)] #[inline(always)]
unsafe fn downcast(db: &dyn Database) -> &dyn Database fn downcast(&self) -> &dyn Database
where where
Self: Sized, Self: Sized,
{ {
// No-op // No-op
db self
} }
} }
/// Upcast to a `dyn Database`. /// Upcast to a `dyn Database`.
/// ///
/// Only required because upcasts not yet stabilized (*grr*). /// Only required because upcasting does not work for unsized generic parameters.
pub trait AsDynDatabase { pub trait AsDynDatabase {
fn as_dyn_database(&self) -> &dyn Database; fn as_dyn_database(&self) -> &dyn Database;
fn as_dyn_database_mut(&mut self) -> &mut dyn Database;
} }
impl<T: Database> AsDynDatabase for T { impl<T: Database> AsDynDatabase for T {
@ -113,30 +138,12 @@ impl<T: Database> AsDynDatabase for T {
fn as_dyn_database(&self) -> &dyn Database { fn as_dyn_database(&self) -> &dyn Database {
self self
} }
#[inline(always)]
fn as_dyn_database_mut(&mut self) -> &mut dyn Database {
self
}
} }
pub fn current_revision<Db: ?Sized + Database>(db: &Db) -> Revision { pub fn current_revision<Db: ?Sized + Database>(db: &Db) -> Revision {
db.zalsa().current_revision() db.zalsa().current_revision()
} }
impl dyn Database {
/// Upcasts `self` to the given view.
///
/// # Panics
///
/// If the view has not been added to the database (see [`crate::views::Views`]).
#[track_caller]
pub fn as_view<DbView: ?Sized + Database>(&self) -> &DbView {
let views = self.zalsa().views();
views.downcaster_for().downcast(self)
}
}
#[cfg(feature = "salsa_unstable")] #[cfg(feature = "salsa_unstable")]
pub use memory_usage::IngredientInfo; pub use memory_usage::IngredientInfo;

View file

@ -10,6 +10,7 @@ use crate::accumulator::accumulated_map::{AccumulatedMap, InputAccumulatedValues
use crate::cycle::{ use crate::cycle::{
empty_cycle_heads, CycleHeads, CycleRecoveryAction, CycleRecoveryStrategy, ProvisionalStatus, empty_cycle_heads, CycleHeads, CycleRecoveryAction, CycleRecoveryStrategy, ProvisionalStatus,
}; };
use crate::database::RawDatabase;
use crate::function::delete::DeletedEntries; use crate::function::delete::DeletedEntries;
use crate::function::sync::{ClaimResult, SyncTable}; use crate::function::sync::{ClaimResult, SyncTable};
use crate::ingredient::{Ingredient, WaitForResult}; use crate::ingredient::{Ingredient, WaitForResult};
@ -22,7 +23,7 @@ use crate::table::Table;
use crate::views::DatabaseDownCaster; use crate::views::DatabaseDownCaster;
use crate::zalsa::{IngredientIndex, MemoIngredientIndex, Zalsa}; use crate::zalsa::{IngredientIndex, MemoIngredientIndex, Zalsa};
use crate::zalsa_local::QueryOriginRef; use crate::zalsa_local::QueryOriginRef;
use crate::{Database, Id, Revision}; use crate::{Id, Revision};
mod accumulated; mod accumulated;
mod backdate; mod backdate;
@ -68,10 +69,9 @@ pub trait Configuration: Any {
/// This invokes user code in form of the `Eq` impl. /// This invokes user code in form of the `Eq` impl.
fn values_equal<'db>(old_value: &Self::Output<'db>, new_value: &Self::Output<'db>) -> bool; fn values_equal<'db>(old_value: &Self::Output<'db>, new_value: &Self::Output<'db>) -> bool;
// FIXME: This should take a `&Zalsa`
/// Convert from the id used internally to the value that execute is expecting. /// Convert from the id used internally to the value that execute is expecting.
/// This is a no-op if the input to the function is a salsa struct. /// This is a no-op if the input to the function is a salsa struct.
fn id_to_input(db: &Self::DbView, key: Id) -> Self::Input<'_>; fn id_to_input(zalsa: &Zalsa, key: Id) -> Self::Input<'_>;
/// Returns the size of any heap allocations in the output value, in bytes. /// Returns the size of any heap allocations in the output value, in bytes.
fn heap_size(_value: &Self::Output<'_>) -> usize { fn heap_size(_value: &Self::Output<'_>) -> usize {
@ -124,7 +124,7 @@ pub struct IngredientImpl<C: Configuration> {
/// Used to find memos to throw out when we have too many memoized values. /// Used to find memos to throw out when we have too many memoized values.
lru: lru::Lru, lru: lru::Lru,
/// A downcaster from `dyn Database` to `C::DbView`. /// An downcaster to `C::DbView`.
/// ///
/// # Safety /// # Safety
/// ///
@ -261,7 +261,8 @@ where
unsafe fn maybe_changed_after( unsafe fn maybe_changed_after(
&self, &self,
db: &dyn Database, _zalsa: &crate::zalsa::Zalsa,
db: RawDatabase<'_>,
input: Id, input: Id,
revision: Revision, revision: Revision,
cycle_heads: &mut CycleHeads, cycle_heads: &mut CycleHeads,
@ -370,12 +371,13 @@ where
C::CYCLE_STRATEGY C::CYCLE_STRATEGY
} }
fn accumulated<'db>( unsafe fn accumulated<'db>(
&'db self, &'db self,
db: &'db dyn Database, db: RawDatabase<'db>,
key_index: Id, key_index: Id,
) -> (Option<&'db AccumulatedMap>, InputAccumulatedValues) { ) -> (Option<&'db AccumulatedMap>, InputAccumulatedValues) {
let db = self.view_caster().downcast(db); // SAFETY: The `db` belongs to the ingredient as per caller invariant
let db = unsafe { self.view_caster().downcast_unchecked(db) };
self.accumulated_map(db, key_index) self.accumulated_map(db, key_index)
} }
} }

View file

@ -4,7 +4,7 @@ use crate::function::{Configuration, IngredientImpl};
use crate::hash::FxHashSet; use crate::hash::FxHashSet;
use crate::zalsa::ZalsaDatabase; use crate::zalsa::ZalsaDatabase;
use crate::zalsa_local::QueryOriginRef; use crate::zalsa_local::QueryOriginRef;
use crate::{AsDynDatabase, DatabaseKeyIndex, Id}; use crate::{DatabaseKeyIndex, Id};
impl<C> IngredientImpl<C> impl<C> IngredientImpl<C>
where where
@ -37,9 +37,8 @@ where
let mut output = vec![]; let mut output = vec![];
// First ensure the result is up to date // First ensure the result is up to date
self.fetch(db, key); self.fetch(db, zalsa, zalsa_local, key);
let db = db.as_dyn_database();
let db_key = self.database_key_index(key); let db_key = self.database_key_index(key);
let mut visited: FxHashSet<DatabaseKeyIndex> = FxHashSet::default(); let mut visited: FxHashSet<DatabaseKeyIndex> = FxHashSet::default();
let mut stack: Vec<DatabaseKeyIndex> = vec![db_key]; let mut stack: Vec<DatabaseKeyIndex> = vec![db_key];
@ -54,7 +53,9 @@ where
let ingredient = zalsa.lookup_ingredient(k.ingredient_index()); let ingredient = zalsa.lookup_ingredient(k.ingredient_index());
// Extend `output` with any values accumulated by `k`. // Extend `output` with any values accumulated by `k`.
let (accumulated_map, input) = ingredient.accumulated(db, k.key_index()); // SAFETY: `db` owns the `ingredient`
let (accumulated_map, input) =
unsafe { ingredient.accumulated(db.into(), k.key_index()) };
if let Some(accumulated_map) = accumulated_map { if let Some(accumulated_map) = accumulated_map {
accumulated_map.extend_with_accumulated(accumulator.index(), &mut output); accumulated_map.extend_with_accumulated(accumulator.index(), &mut output);
} }

View file

@ -4,7 +4,7 @@ use crate::function::{Configuration, IngredientImpl};
use crate::sync::atomic::{AtomicBool, Ordering}; use crate::sync::atomic::{AtomicBool, Ordering};
use crate::zalsa::{MemoIngredientIndex, Zalsa, ZalsaDatabase}; use crate::zalsa::{MemoIngredientIndex, Zalsa, ZalsaDatabase};
use crate::zalsa_local::{ActiveQueryGuard, QueryRevisions}; use crate::zalsa_local::{ActiveQueryGuard, QueryRevisions};
use crate::{Event, EventKind, Id, Revision}; use crate::{Event, EventKind, Id};
impl<C> IngredientImpl<C> impl<C> IngredientImpl<C>
where where
@ -41,16 +41,11 @@ where
let (new_value, mut revisions) = match C::CYCLE_STRATEGY { let (new_value, mut revisions) = match C::CYCLE_STRATEGY {
CycleRecoveryStrategy::Panic => { CycleRecoveryStrategy::Panic => {
Self::execute_query(db, active_query, opt_old_memo, zalsa.current_revision(), id) Self::execute_query(db, zalsa, active_query, opt_old_memo, id)
} }
CycleRecoveryStrategy::FallbackImmediate => { CycleRecoveryStrategy::FallbackImmediate => {
let (mut new_value, mut revisions) = Self::execute_query( let (mut new_value, mut revisions) =
db, Self::execute_query(db, zalsa, active_query, opt_old_memo, id);
active_query,
opt_old_memo,
zalsa.current_revision(),
id,
);
if let Some(cycle_heads) = revisions.cycle_heads_mut() { if let Some(cycle_heads) = revisions.cycle_heads_mut() {
// Did the new result we got depend on our own provisional value, in a cycle? // Did the new result we got depend on our own provisional value, in a cycle?
@ -77,7 +72,7 @@ where
let active_query = db let active_query = db
.zalsa_local() .zalsa_local()
.push_query(database_key_index, IterationCount::initial()); .push_query(database_key_index, IterationCount::initial());
new_value = C::cycle_initial(db, C::id_to_input(db, id)); new_value = C::cycle_initial(db, C::id_to_input(zalsa, id));
revisions = active_query.pop(); revisions = active_query.pop();
// We need to set `cycle_heads` and `verified_final` because it needs to propagate to the callers. // We need to set `cycle_heads` and `verified_final` because it needs to propagate to the callers.
// When verifying this, we will see we have fallback and mark ourselves verified. // When verifying this, we will see we have fallback and mark ourselves verified.
@ -136,13 +131,8 @@ where
let mut opt_last_provisional: Option<&Memo<'db, C>> = None; let mut opt_last_provisional: Option<&Memo<'db, C>> = None;
loop { loop {
let previous_memo = opt_last_provisional.or(opt_old_memo); let previous_memo = opt_last_provisional.or(opt_old_memo);
let (mut new_value, mut revisions) = Self::execute_query( let (mut new_value, mut revisions) =
db, Self::execute_query(db, zalsa, active_query, previous_memo, id);
active_query,
previous_memo,
zalsa.current_revision(),
id,
);
// Did the new result we got depend on our own provisional value, in a cycle? // Did the new result we got depend on our own provisional value, in a cycle?
if let Some(cycle_heads) = revisions if let Some(cycle_heads) = revisions
@ -192,7 +182,7 @@ where
db, db,
&new_value, &new_value,
iteration_count.as_u32(), iteration_count.as_u32(),
C::id_to_input(db, id), C::id_to_input(zalsa, id),
) { ) {
crate::CycleRecoveryAction::Iterate => {} crate::CycleRecoveryAction::Iterate => {}
crate::CycleRecoveryAction::Fallback(fallback_value) => { crate::CycleRecoveryAction::Fallback(fallback_value) => {
@ -258,9 +248,9 @@ where
#[inline] #[inline]
fn execute_query<'db>( fn execute_query<'db>(
db: &'db C::DbView, db: &'db C::DbView,
zalsa: &'db Zalsa,
active_query: ActiveQueryGuard<'db>, active_query: ActiveQueryGuard<'db>,
opt_old_memo: Option<&Memo<'db, C>>, opt_old_memo: Option<&Memo<'db, C>>,
current_revision: Revision,
id: Id, id: Id,
) -> (C::Output<'db>, QueryRevisions) { ) -> (C::Output<'db>, QueryRevisions) {
if let Some(old_memo) = opt_old_memo { if let Some(old_memo) = opt_old_memo {
@ -275,14 +265,16 @@ where
// * ensure that tracked struct created during the previous iteration // * ensure that tracked struct created during the previous iteration
// (and are owned by the query) are alive even if the query in this iteration no longer creates them. // (and are owned by the query) are alive even if the query in this iteration no longer creates them.
// * ensure the final returned memo depends on all inputs from all iterations. // * ensure the final returned memo depends on all inputs from all iterations.
if old_memo.may_be_provisional() && old_memo.verified_at.load() == current_revision { if old_memo.may_be_provisional()
&& old_memo.verified_at.load() == zalsa.current_revision()
{
active_query.seed_iteration(&old_memo.revisions); active_query.seed_iteration(&old_memo.revisions);
} }
} }
// Query was not previously executed, or value is potentially // Query was not previously executed, or value is potentially
// stale, or value is absent. Let's execute! // stale, or value is absent. Let's execute!
let new_value = C::execute(db, C::id_to_input(db, id)); let new_value = C::execute(db, C::id_to_input(zalsa, id));
(new_value, active_query.pop()) (new_value, active_query.pop())
} }

View file

@ -2,7 +2,7 @@ use crate::cycle::{CycleHeads, CycleRecoveryStrategy, IterationCount};
use crate::function::memo::Memo; use crate::function::memo::Memo;
use crate::function::sync::ClaimResult; use crate::function::sync::ClaimResult;
use crate::function::{Configuration, IngredientImpl, VerifyResult}; use crate::function::{Configuration, IngredientImpl, VerifyResult};
use crate::zalsa::{MemoIngredientIndex, Zalsa, ZalsaDatabase}; use crate::zalsa::{MemoIngredientIndex, Zalsa};
use crate::zalsa_local::{QueryRevisions, ZalsaLocal}; use crate::zalsa_local::{QueryRevisions, ZalsaLocal};
use crate::Id; use crate::Id;
@ -10,8 +10,13 @@ impl<C> IngredientImpl<C>
where where
C: Configuration, C: Configuration,
{ {
pub fn fetch<'db>(&'db self, db: &'db C::DbView, id: Id) -> &'db C::Output<'db> { pub fn fetch<'db>(
let (zalsa, zalsa_local) = db.zalsas(); &'db self,
db: &'db C::DbView,
zalsa: &'db Zalsa,
zalsa_local: &'db ZalsaLocal,
id: Id,
) -> &'db C::Output<'db> {
zalsa.unwind_if_revision_cancelled(zalsa_local); zalsa.unwind_if_revision_cancelled(zalsa_local);
let database_key_index = self.database_key_index(id); let database_key_index = self.database_key_index(id);
@ -175,7 +180,7 @@ where
inserting and returning fixpoint initial value" inserting and returning fixpoint initial value"
); );
let revisions = QueryRevisions::fixpoint_initial(database_key_index); let revisions = QueryRevisions::fixpoint_initial(database_key_index);
let initial_value = C::cycle_initial(db, C::id_to_input(db, id)); let initial_value = C::cycle_initial(db, C::id_to_input(zalsa, id));
Some(self.insert_memo( Some(self.insert_memo(
zalsa, zalsa,
id, id,
@ -189,7 +194,7 @@ where
); );
let active_query = let active_query =
zalsa_local.push_query(database_key_index, IterationCount::initial()); zalsa_local.push_query(database_key_index, IterationCount::initial());
let fallback_value = C::cycle_initial(db, C::id_to_input(db, id)); let fallback_value = C::cycle_initial(db, C::id_to_input(zalsa, id));
let mut revisions = active_query.pop(); let mut revisions = active_query.pop();
revisions.set_cycle_heads(CycleHeads::initial(database_key_index)); revisions.set_cycle_heads(CycleHeads::initial(database_key_index));
// We need this for `cycle_heads()` to work. We will unset this in the outer `execute()`. // We need this for `cycle_heads()` to work. We will unset this in the outer `execute()`.

View file

@ -7,7 +7,7 @@ use crate::key::DatabaseKeyIndex;
use crate::sync::atomic::Ordering; use crate::sync::atomic::Ordering;
use crate::zalsa::{MemoIngredientIndex, Zalsa, ZalsaDatabase}; use crate::zalsa::{MemoIngredientIndex, Zalsa, ZalsaDatabase};
use crate::zalsa_local::{QueryEdgeKind, QueryOriginRef, ZalsaLocal}; use crate::zalsa_local::{QueryEdgeKind, QueryOriginRef, ZalsaLocal};
use crate::{AsDynDatabase as _, Id, Revision}; use crate::{Id, Revision};
/// Result of memo validation. /// Result of memo validation.
pub enum VerifyResult { pub enum VerifyResult {
@ -434,8 +434,6 @@ where
return VerifyResult::Changed; return VerifyResult::Changed;
} }
let dyn_db = db.as_dyn_database();
let mut inputs = InputAccumulatedValues::Empty; let mut inputs = InputAccumulatedValues::Empty;
// Fully tracked inputs? Iterate over the inputs and check them, one by one. // Fully tracked inputs? Iterate over the inputs and check them, one by one.
// //
@ -447,7 +445,7 @@ where
match edge.kind() { match edge.kind() {
QueryEdgeKind::Input(dependency_index) => { QueryEdgeKind::Input(dependency_index) => {
match dependency_index.maybe_changed_after( match dependency_index.maybe_changed_after(
dyn_db, db.into(),
zalsa, zalsa,
old_memo.verified_at.load(), old_memo.verified_at.load(),
cycle_heads, cycle_heads,

View file

@ -497,7 +497,7 @@ mod _memory_usage {
unimplemented!() unimplemented!()
} }
fn id_to_input(_: &Self::DbView, _: Id) -> Self::Input<'_> { fn id_to_input(_: &Zalsa, _: Id) -> Self::Input<'_> {
unimplemented!() unimplemented!()
} }

View file

@ -5,6 +5,7 @@ use crate::accumulator::accumulated_map::{AccumulatedMap, InputAccumulatedValues
use crate::cycle::{ use crate::cycle::{
empty_cycle_heads, CycleHeads, CycleRecoveryStrategy, IterationCount, ProvisionalStatus, empty_cycle_heads, CycleHeads, CycleRecoveryStrategy, IterationCount, ProvisionalStatus,
}; };
use crate::database::RawDatabase;
use crate::function::VerifyResult; use crate::function::VerifyResult;
use crate::runtime::Running; use crate::runtime::Running;
use crate::sync::Arc; use crate::sync::Arc;
@ -12,7 +13,7 @@ use crate::table::memo::MemoTableTypes;
use crate::table::Table; use crate::table::Table;
use crate::zalsa::{transmute_data_mut_ptr, transmute_data_ptr, IngredientIndex, Zalsa}; use crate::zalsa::{transmute_data_mut_ptr, transmute_data_ptr, IngredientIndex, Zalsa};
use crate::zalsa_local::QueryOriginRef; use crate::zalsa_local::QueryOriginRef;
use crate::{Database, DatabaseKeyIndex, Id, Revision}; use crate::{DatabaseKeyIndex, Id, Revision};
/// A "jar" is a group of ingredients that are added atomically. /// A "jar" is a group of ingredients that are added atomically.
/// ///
@ -45,9 +46,10 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
/// # Safety /// # Safety
/// ///
/// The passed in database needs to be the same one that the ingredient was created with. /// The passed in database needs to be the same one that the ingredient was created with.
unsafe fn maybe_changed_after<'db>( unsafe fn maybe_changed_after(
&'db self, &self,
db: &'db dyn Database, zalsa: &crate::zalsa::Zalsa,
db: crate::database::RawDatabase<'_>,
input: Id, input: Id,
revision: Revision, revision: Revision,
cycle_heads: &mut CycleHeads, cycle_heads: &mut CycleHeads,
@ -159,9 +161,13 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
/// What values were accumulated during the creation of the value at `key_index` /// What values were accumulated during the creation of the value at `key_index`
/// (if any). /// (if any).
fn accumulated<'db>( ///
/// # Safety
///
/// The passed in database needs to be the same one that the ingredient was created with.
unsafe fn accumulated<'db>(
&'db self, &'db self,
db: &'db dyn Database, db: RawDatabase<'db>,
key_index: Id, key_index: Id,
) -> (Option<&'db AccumulatedMap>, InputAccumulatedValues) { ) -> (Option<&'db AccumulatedMap>, InputAccumulatedValues) {
let _ = (db, key_index); let _ = (db, key_index);
@ -171,7 +177,7 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
/// Returns memory usage information about any instances of the ingredient, /// Returns memory usage information about any instances of the ingredient,
/// if applicable. /// if applicable.
#[cfg(feature = "salsa_unstable")] #[cfg(feature = "salsa_unstable")]
fn memory_usage(&self, _db: &dyn Database) -> Option<Vec<crate::database::SlotInfo>> { fn memory_usage(&self, _db: &dyn crate::Database) -> Option<Vec<crate::database::SlotInfo>> {
None None
} }
} }
@ -179,7 +185,7 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
impl dyn Ingredient { impl dyn Ingredient {
/// Equivalent to the `downcast` method on `Any`. /// Equivalent to the `downcast` method on `Any`.
/// ///
/// Because we do not have dyn-upcasting support, we need this workaround. /// Because we do not have dyn-downcasting support, we need this workaround.
pub fn assert_type<T: Any>(&self) -> &T { pub fn assert_type<T: Any>(&self) -> &T {
assert_eq!( assert_eq!(
self.type_id(), self.type_id(),
@ -195,7 +201,7 @@ impl dyn Ingredient {
/// Equivalent to the `downcast` methods on `Any`. /// Equivalent to the `downcast` methods on `Any`.
/// ///
/// Because we do not have dyn-upcasting support, we need this workaround. /// Because we do not have dyn-downcasting support, we need this workaround.
/// ///
/// # Safety /// # Safety
/// ///
@ -214,7 +220,7 @@ impl dyn Ingredient {
/// Equivalent to the `downcast` method on `Any`. /// Equivalent to the `downcast` method on `Any`.
/// ///
/// Because we do not have dyn-upcasting support, we need this workaround. /// Because we do not have dyn-downcasting support, we need this workaround.
pub fn assert_type_mut<T: Any>(&mut self) -> &mut T { pub fn assert_type_mut<T: Any>(&mut self) -> &mut T {
assert_eq!( assert_eq!(
Any::type_id(self), Any::type_id(self),

View file

@ -19,7 +19,7 @@ use crate::sync::Arc;
use crate::table::memo::{MemoTable, MemoTableTypes}; use crate::table::memo::{MemoTable, MemoTableTypes};
use crate::table::{Slot, Table}; use crate::table::{Slot, Table};
use crate::zalsa::{IngredientIndex, Zalsa}; use crate::zalsa::{IngredientIndex, Zalsa};
use crate::{Database, Durability, Id, Revision, Runtime}; use crate::{zalsa_local, Durability, Id, Revision, Runtime};
pub trait Configuration: Any { pub trait Configuration: Any {
const DEBUG_NAME: &'static str; const DEBUG_NAME: &'static str;
@ -104,13 +104,12 @@ impl<C: Configuration> IngredientImpl<C> {
pub fn new_input( pub fn new_input(
&self, &self,
db: &dyn Database, zalsa: &Zalsa,
zalsa_local: &zalsa_local::ZalsaLocal,
fields: C::Fields, fields: C::Fields,
revisions: C::Revisions, revisions: C::Revisions,
durabilities: C::Durabilities, durabilities: C::Durabilities,
) -> C::Struct { ) -> C::Struct {
let (zalsa, zalsa_local) = db.zalsas();
let id = self.singleton.with_scope(|| { let id = self.singleton.with_scope(|| {
zalsa_local.allocate(zalsa, self.ingredient_index, |_| Value::<C> { zalsa_local.allocate(zalsa, self.ingredient_index, |_| Value::<C> {
fields, fields,
@ -177,11 +176,11 @@ impl<C: Configuration> IngredientImpl<C> {
/// The caller is responsible for selecting the appropriate element. /// The caller is responsible for selecting the appropriate element.
pub fn field<'db>( pub fn field<'db>(
&'db self, &'db self,
db: &'db dyn crate::Database, zalsa: &'db Zalsa,
zalsa_local: &'db zalsa_local::ZalsaLocal,
id: C::Struct, id: C::Struct,
field_index: usize, field_index: usize,
) -> &'db C::Fields { ) -> &'db C::Fields {
let (zalsa, zalsa_local) = db.zalsas();
let field_ingredient_index = self.ingredient_index.successor(field_index); let field_ingredient_index = self.ingredient_index.successor(field_index);
let id = id.as_id(); let id = id.as_id();
let value = Self::data(zalsa, id); let value = Self::data(zalsa, id);
@ -197,17 +196,13 @@ impl<C: Configuration> IngredientImpl<C> {
#[cfg(feature = "salsa_unstable")] #[cfg(feature = "salsa_unstable")]
/// Returns all data corresponding to the input struct. /// Returns all data corresponding to the input struct.
pub fn entries<'db>( pub fn entries<'db>(&'db self, zalsa: &'db Zalsa) -> impl Iterator<Item = &'db Value<C>> {
&'db self, zalsa.table().slots_of::<Value<C>>()
db: &'db dyn crate::Database,
) -> impl Iterator<Item = &'db Value<C>> {
db.zalsa().table().slots_of::<Value<C>>()
} }
/// Peek at the field values without recording any read dependency. /// Peek at the field values without recording any read dependency.
/// Used for debug printouts. /// Used for debug printouts.
pub fn leak_fields<'db>(&'db self, db: &'db dyn Database, id: C::Struct) -> &'db C::Fields { pub fn leak_fields<'db>(&'db self, zalsa: &'db Zalsa, id: C::Struct) -> &'db C::Fields {
let zalsa = db.zalsa();
let id = id.as_id(); let id = id.as_id();
let value = Self::data(zalsa, id); let value = Self::data(zalsa, id);
&value.fields &value.fields
@ -225,7 +220,8 @@ impl<C: Configuration> Ingredient for IngredientImpl<C> {
unsafe fn maybe_changed_after( unsafe fn maybe_changed_after(
&self, &self,
_db: &dyn Database, _zalsa: &crate::zalsa::Zalsa,
_db: crate::database::RawDatabase<'_>,
_input: Id, _input: Id,
_revision: Revision, _revision: Revision,
_cycle_heads: &mut CycleHeads, _cycle_heads: &mut CycleHeads,
@ -249,9 +245,9 @@ impl<C: Configuration> Ingredient for IngredientImpl<C> {
/// Returns memory usage information about any inputs. /// Returns memory usage information about any inputs.
#[cfg(feature = "salsa_unstable")] #[cfg(feature = "salsa_unstable")]
fn memory_usage(&self, db: &dyn Database) -> Option<Vec<crate::database::SlotInfo>> { fn memory_usage(&self, db: &dyn crate::Database) -> Option<Vec<crate::database::SlotInfo>> {
let memory_usage = self let memory_usage = self
.entries(db) .entries(db.zalsa())
// SAFETY: The memo table belongs to a value that we allocated, so it // SAFETY: The memo table belongs to a value that we allocated, so it
// has the correct type. // has the correct type.
.map(|value| unsafe { value.memory_usage(&self.memo_table_types) }) .map(|value| unsafe { value.memory_usage(&self.memo_table_types) })

View file

@ -8,7 +8,7 @@ use crate::input::{Configuration, IngredientImpl, Value};
use crate::sync::Arc; use crate::sync::Arc;
use crate::table::memo::MemoTableTypes; use crate::table::memo::MemoTableTypes;
use crate::zalsa::IngredientIndex; use crate::zalsa::IngredientIndex;
use crate::{Database, Id, Revision}; use crate::{Id, Revision};
/// Ingredient used to represent the fields of a `#[salsa::input]`. /// Ingredient used to represent the fields of a `#[salsa::input]`.
/// ///
@ -52,12 +52,12 @@ where
unsafe fn maybe_changed_after( unsafe fn maybe_changed_after(
&self, &self,
db: &dyn Database, zalsa: &crate::zalsa::Zalsa,
_db: crate::database::RawDatabase<'_>,
input: Id, input: Id,
revision: Revision, revision: Revision,
_cycle_heads: &mut CycleHeads, _cycle_heads: &mut CycleHeads,
) -> VerifyResult { ) -> VerifyResult {
let zalsa = db.zalsa();
let value = <IngredientImpl<C>>::data(zalsa, input); let value = <IngredientImpl<C>>::data(zalsa, input);
VerifyResult::changed_if(value.revisions[self.field_index] > revision) VerifyResult::changed_if(value.revisions[self.field_index] > revision)
} }

View file

@ -21,7 +21,7 @@ use crate::sync::{Arc, Mutex, OnceLock};
use crate::table::memo::{MemoTable, MemoTableTypes, MemoTableWithTypesMut}; use crate::table::memo::{MemoTable, MemoTableTypes, MemoTableWithTypesMut};
use crate::table::Slot; use crate::table::Slot;
use crate::zalsa::{IngredientIndex, Zalsa}; use crate::zalsa::{IngredientIndex, Zalsa};
use crate::{Database, DatabaseKeyIndex, Event, EventKind, Id, Revision}; use crate::{DatabaseKeyIndex, Event, EventKind, Id, Revision};
/// Trait that defines the key properties of an interned struct. /// Trait that defines the key properties of an interned struct.
/// ///
@ -296,7 +296,8 @@ where
/// the database ends up trying to intern or allocate a new value. /// the database ends up trying to intern or allocate a new value.
pub fn intern<'db, Key>( pub fn intern<'db, Key>(
&'db self, &'db self,
db: &'db dyn crate::Database, zalsa: &'db Zalsa,
zalsa_local: &'db ZalsaLocal,
key: Key, key: Key,
assemble: impl FnOnce(Id, Key) -> C::Fields<'db>, assemble: impl FnOnce(Id, Key) -> C::Fields<'db>,
) -> C::Struct<'db> ) -> C::Struct<'db>
@ -304,7 +305,7 @@ where
Key: Hash, Key: Hash,
C::Fields<'db>: HashEqLike<Key>, C::Fields<'db>: HashEqLike<Key>,
{ {
FromId::from_id(self.intern_id(db, key, assemble)) FromId::from_id(self.intern_id(zalsa, zalsa_local, key, assemble))
} }
/// Intern data to a unique reference. /// Intern data to a unique reference.
@ -319,7 +320,8 @@ where
/// the database ends up trying to intern or allocate a new value. /// the database ends up trying to intern or allocate a new value.
pub fn intern_id<'db, Key>( pub fn intern_id<'db, Key>(
&'db self, &'db self,
db: &'db dyn crate::Database, zalsa: &'db Zalsa,
zalsa_local: &'db ZalsaLocal,
key: Key, key: Key,
assemble: impl FnOnce(Id, Key) -> C::Fields<'db>, assemble: impl FnOnce(Id, Key) -> C::Fields<'db>,
) -> crate::Id ) -> crate::Id
@ -331,8 +333,6 @@ where
// so instead we go with this and transmute the lifetime in the `eq` closure // so instead we go with this and transmute the lifetime in the `eq` closure
C::Fields<'db>: HashEqLike<Key>, C::Fields<'db>: HashEqLike<Key>,
{ {
let (zalsa, zalsa_local) = db.zalsas();
// Record the current revision as active. // Record the current revision as active.
let current_revision = zalsa.current_revision(); let current_revision = zalsa.current_revision();
self.revision_queue.record(current_revision); self.revision_queue.record(current_revision);
@ -735,8 +735,7 @@ where
} }
/// Lookup the data for an interned value based on its ID. /// Lookup the data for an interned value based on its ID.
pub fn data<'db>(&'db self, db: &'db dyn Database, id: Id) -> &'db C::Fields<'db> { pub fn data<'db>(&'db self, zalsa: &'db Zalsa, id: Id) -> &'db C::Fields<'db> {
let zalsa = db.zalsa();
let value = zalsa.table().get::<Value<C>>(id); let value = zalsa.table().get::<Value<C>>(id);
debug_assert!( debug_assert!(
@ -761,12 +760,12 @@ where
/// Lookup the fields from an interned struct. /// Lookup the fields from an interned struct.
/// ///
/// Note that this is not "leaking" since no dependency edge is required. /// Note that this is not "leaking" since no dependency edge is required.
pub fn fields<'db>(&'db self, db: &'db dyn Database, s: C::Struct<'db>) -> &'db C::Fields<'db> { pub fn fields<'db>(&'db self, zalsa: &'db Zalsa, s: C::Struct<'db>) -> &'db C::Fields<'db> {
self.data(db, AsId::as_id(&s)) self.data(zalsa, AsId::as_id(&s))
} }
pub fn reset(&mut self, db: &mut dyn Database) { pub fn reset(&mut self, zalsa_mut: &mut Zalsa) {
_ = db.zalsa_mut(); _ = zalsa_mut;
for shard in self.shards.iter() { for shard in self.shards.iter() {
// We can clear the key maps now that we have cancelled all other handles. // We can clear the key maps now that we have cancelled all other handles.
@ -776,11 +775,8 @@ where
#[cfg(feature = "salsa_unstable")] #[cfg(feature = "salsa_unstable")]
/// Returns all data corresponding to the interned struct. /// Returns all data corresponding to the interned struct.
pub fn entries<'db>( pub fn entries<'db>(&'db self, zalsa: &'db Zalsa) -> impl Iterator<Item = &'db Value<C>> {
&'db self, zalsa.table().slots_of::<Value<C>>()
db: &'db dyn crate::Database,
) -> impl Iterator<Item = &'db Value<C>> {
db.zalsa().table().slots_of::<Value<C>>()
} }
} }
@ -798,13 +794,12 @@ where
unsafe fn maybe_changed_after( unsafe fn maybe_changed_after(
&self, &self,
db: &dyn Database, zalsa: &crate::zalsa::Zalsa,
_db: crate::database::RawDatabase<'_>,
input: Id, input: Id,
_revision: Revision, _revision: Revision,
_cycle_heads: &mut CycleHeads, _cycle_heads: &mut CycleHeads,
) -> VerifyResult { ) -> VerifyResult {
let zalsa = db.zalsa();
// Record the current revision as active. // Record the current revision as active.
let current_revision = zalsa.current_revision(); let current_revision = zalsa.current_revision();
self.revision_queue.record(current_revision); self.revision_queue.record(current_revision);
@ -852,7 +847,7 @@ where
/// Returns memory usage information about any interned values. /// Returns memory usage information about any interned values.
#[cfg(all(not(feature = "shuttle"), feature = "salsa_unstable"))] #[cfg(all(not(feature = "shuttle"), feature = "salsa_unstable"))]
fn memory_usage(&self, db: &dyn Database) -> Option<Vec<crate::database::SlotInfo>> { fn memory_usage(&self, db: &dyn crate::Database) -> Option<Vec<crate::database::SlotInfo>> {
use parking_lot::lock_api::RawMutex; use parking_lot::lock_api::RawMutex;
for shard in self.shards.iter() { for shard in self.shards.iter() {
@ -861,7 +856,7 @@ where
} }
let memory_usage = self let memory_usage = self
.entries(db) .entries(db.zalsa())
// SAFETY: The memo table belongs to a value that we allocated, so it // SAFETY: The memo table belongs to a value that we allocated, so it
// has the correct type. Additionally, we are holding the locks for all shards. // has the correct type. Additionally, we are holding the locks for all shards.
.map(|value| unsafe { value.memory_usage(&self.memo_table_types) }) .map(|value| unsafe { value.memory_usage(&self.memo_table_types) })

View file

@ -3,7 +3,7 @@ use core::fmt;
use crate::cycle::CycleHeads; use crate::cycle::CycleHeads;
use crate::function::VerifyResult; use crate::function::VerifyResult;
use crate::zalsa::{IngredientIndex, Zalsa}; use crate::zalsa::{IngredientIndex, Zalsa};
use crate::{Database, Id}; use crate::Id;
// ANCHOR: DatabaseKeyIndex // ANCHOR: DatabaseKeyIndex
/// An integer that uniquely identifies a particular query instance within the /// An integer that uniquely identifies a particular query instance within the
@ -36,16 +36,18 @@ impl DatabaseKeyIndex {
pub(crate) fn maybe_changed_after( pub(crate) fn maybe_changed_after(
&self, &self,
db: &dyn Database, db: crate::database::RawDatabase<'_>,
zalsa: &Zalsa, zalsa: &Zalsa,
last_verified_at: crate::Revision, last_verified_at: crate::Revision,
cycle_heads: &mut CycleHeads, cycle_heads: &mut CycleHeads,
) -> VerifyResult { ) -> VerifyResult {
// SAFETY: The `db` belongs to the ingredient // SAFETY: The `db` belongs to the ingredient
unsafe { unsafe {
// here, `db` has to be either the correct type already, or a subtype (as far as trait
// hierarchy is concerned)
zalsa zalsa
.lookup_ingredient(self.ingredient_index()) .lookup_ingredient(self.ingredient_index())
.maybe_changed_after(db, self.key_index(), last_verified_at, cycle_heads) .maybe_changed_after(zalsa, db, self.key_index(), last_verified_at, cycle_heads)
} }
} }

View file

@ -51,7 +51,7 @@ pub use self::accumulator::Accumulator;
pub use self::active_query::Backtrace; pub use self::active_query::Backtrace;
pub use self::cancelled::Cancelled; pub use self::cancelled::Cancelled;
pub use self::cycle::CycleRecoveryAction; pub use self::cycle::CycleRecoveryAction;
pub use self::database::{AsDynDatabase, Database}; pub use self::database::Database;
pub use self::database_impl::DatabaseImpl; pub use self::database_impl::DatabaseImpl;
pub use self::durability::Durability; pub use self::durability::Durability;
pub use self::event::{Event, EventKind}; pub use self::event::{Event, EventKind};

View file

@ -1,44 +1,91 @@
use rayon::iter::{FromParallelIterator, IntoParallelIterator, ParallelIterator}; use rayon::iter::{FromParallelIterator, IntoParallelIterator, ParallelIterator};
use crate::Database; use crate::{database::RawDatabase, views::DatabaseDownCaster, Database};
pub fn par_map<Db, F, T, R, C>(db: &Db, inputs: impl IntoParallelIterator<Item = T>, op: F) -> C pub fn par_map<Db, F, T, R, C>(db: &Db, inputs: impl IntoParallelIterator<Item = T>, op: F) -> C
where where
Db: Database + ?Sized, Db: Database + ?Sized + Send,
F: Fn(&Db, T) -> R + Sync + Send, F: Fn(&Db, T) -> R + Sync + Send,
T: Send, T: Send,
R: Send + Sync, R: Send + Sync,
C: FromParallelIterator<R>, C: FromParallelIterator<R>,
{ {
let views = db.zalsa().views();
let caster = &views.downcaster_for::<Db>();
let db_caster = &views.downcaster_for::<dyn Database>();
inputs inputs
.into_par_iter() .into_par_iter()
.map_with(DbForkOnClone(db.fork_db()), |db, element| { .map_with(
op(db.0.as_view(), element) DbForkOnClone(db.fork_db(), caster, db_caster),
}) |db, element| op(db.as_view(), element),
)
.collect() .collect()
} }
struct DbForkOnClone(Box<dyn Database>); struct DbForkOnClone<'views, Db: Database + ?Sized>(
RawDatabase<'static>,
&'views DatabaseDownCaster<Db>,
&'views DatabaseDownCaster<dyn Database>,
);
impl Clone for DbForkOnClone { // SAFETY: `T: Send` -> `&own T: Send`, `DbForkOnClone` is an owning pointer
fn clone(&self) -> Self { unsafe impl<Db: Send + Database + ?Sized> Send for DbForkOnClone<'_, Db> {}
DbForkOnClone(self.0.fork_db())
impl<Db: Database + ?Sized> DbForkOnClone<'_, Db> {
fn as_view(&self) -> &Db {
// SAFETY: The downcaster ensures that the pointer is valid for the lifetime of the view.
unsafe { self.1.downcast_unchecked(self.0) }
} }
} }
pub fn join<A, B, RA, RB, Db: Database + ?Sized>(db: &Db, a: A, b: B) -> (RA, RB) impl<Db: Database + ?Sized> Drop for DbForkOnClone<'_, Db> {
fn drop(&mut self) {
// SAFETY: `caster` is derived from a `db` fitting for our database clone
let db = unsafe { self.1.downcast_mut_unchecked(self.0) };
// SAFETY: `db` has been box allocated and leaked by `fork_db`
_ = unsafe { Box::from_raw(db) };
}
}
impl<Db: Database + ?Sized> Clone for DbForkOnClone<'_, Db> {
fn clone(&self) -> Self {
DbForkOnClone(
// SAFETY: `caster` is derived from a `db` fitting for our database clone
unsafe { self.2.downcast_unchecked(self.0) }.fork_db(),
self.1,
self.2,
)
}
}
pub fn join<A, B, RA, RB, Db: Send + Database + ?Sized>(db: &Db, a: A, b: B) -> (RA, RB)
where where
A: FnOnce(&Db) -> RA + Send, A: FnOnce(&Db) -> RA + Send,
B: FnOnce(&Db) -> RB + Send, B: FnOnce(&Db) -> RB + Send,
RA: Send, RA: Send,
RB: Send, RB: Send,
{ {
#[derive(Copy, Clone)]
struct AssertSend<T>(T);
// SAFETY: We send owning pointers over, which are Send, given the `Db` type parameter above is Send
unsafe impl<T> Send for AssertSend<T> {}
let caster = &db.zalsa().views().downcaster_for::<Db>();
// we need to fork eagerly, as `rayon::join_context` gives us no option to tell whether we get // we need to fork eagerly, as `rayon::join_context` gives us no option to tell whether we get
// moved to another thread before the closure is executed // moved to another thread before the closure is executed
let db_a = db.fork_db(); let db_a = AssertSend(db.fork_db());
let db_b = db.fork_db(); let db_b = AssertSend(db.fork_db());
rayon::join( let res = rayon::join(
move || a(db_a.as_view::<Db>()), // SAFETY: `caster` is derived from a `db` fitting for our database clone
move || b(db_b.as_view::<Db>()), move || a(unsafe { caster.downcast_unchecked({ db_a }.0) }),
) // SAFETY: `caster` is derived from a `db` fitting for our database clone
move || b(unsafe { caster.downcast_unchecked({ db_b }.0) }),
);
// SAFETY: `db` has been box allocated and leaked by `fork_db`
// FIXME: Clean this mess up, RAII
_ = unsafe { Box::from_raw(caster.downcast_mut_unchecked(db_a.0)) };
// SAFETY: `db` has been box allocated and leaked by `fork_db`
_ = unsafe { Box::from_raw(caster.downcast_mut_unchecked(db_b.0)) };
res
} }

View file

@ -2,6 +2,7 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::panic::RefUnwindSafe; use std::panic::RefUnwindSafe;
use crate::database::RawDatabase;
use crate::sync::{Arc, Condvar, Mutex}; use crate::sync::{Arc, Condvar, Mutex};
use crate::zalsa::{ErasedJar, HasJar, Zalsa, ZalsaDatabase}; use crate::zalsa::{ErasedJar, HasJar, Zalsa, ZalsaDatabase};
use crate::zalsa_local::{self, ZalsaLocal}; use crate::zalsa_local::{self, ZalsaLocal};
@ -245,8 +246,8 @@ unsafe impl<T: HasStorage> ZalsaDatabase for T {
} }
#[inline(always)] #[inline(always)]
fn fork_db(&self) -> Box<dyn Database> { fn fork_db(&self) -> RawDatabase<'static> {
Box::new(self.clone()) Box::leak(Box::new(self.clone())).into()
} }
} }

View file

@ -23,7 +23,7 @@ use crate::sync::Arc;
use crate::table::memo::{MemoTable, MemoTableTypes, MemoTableWithTypesMut}; use crate::table::memo::{MemoTable, MemoTableTypes, MemoTableWithTypesMut};
use crate::table::{Slot, Table}; use crate::table::{Slot, Table};
use crate::zalsa::{IngredientIndex, Zalsa}; use crate::zalsa::{IngredientIndex, Zalsa};
use crate::{Database, Durability, Event, EventKind, Id, Revision}; use crate::{Durability, Event, EventKind, Id, Revision};
pub mod tracked_field; pub mod tracked_field;
@ -375,11 +375,10 @@ where
pub fn new_struct<'db>( pub fn new_struct<'db>(
&'db self, &'db self,
db: &'db dyn Database, zalsa: &'db Zalsa,
zalsa_local: &'db ZalsaLocal,
mut fields: C::Fields<'db>, mut fields: C::Fields<'db>,
) -> C::Struct<'db> { ) -> C::Struct<'db> {
let (zalsa, zalsa_local) = db.zalsas();
let identity_hash = IdentityHash { let identity_hash = IdentityHash {
ingredient_index: self.ingredient_index, ingredient_index: self.ingredient_index,
hash: crate::hash::hash(&C::untracked_fields(&fields)), hash: crate::hash::hash(&C::untracked_fields(&fields)),
@ -734,11 +733,11 @@ where
/// Used for debugging. /// Used for debugging.
pub fn leak_fields<'db>( pub fn leak_fields<'db>(
&'db self, &'db self,
db: &'db dyn Database, zalsa: &'db Zalsa,
s: C::Struct<'db>, s: C::Struct<'db>,
) -> &'db C::Fields<'db> { ) -> &'db C::Fields<'db> {
let id = AsId::as_id(&s); let id = AsId::as_id(&s);
let data = Self::data(db.zalsa().table(), id); let data = Self::data(zalsa.table(), id);
data.fields() data.fields()
} }
@ -748,11 +747,11 @@ where
/// The caller is responsible for selecting the appropriate element. /// The caller is responsible for selecting the appropriate element.
pub fn tracked_field<'db>( pub fn tracked_field<'db>(
&'db self, &'db self,
db: &'db dyn crate::Database, zalsa: &'db Zalsa,
zalsa_local: &'db ZalsaLocal,
s: C::Struct<'db>, s: C::Struct<'db>,
relative_tracked_index: usize, relative_tracked_index: usize,
) -> &'db C::Fields<'db> { ) -> &'db C::Fields<'db> {
let (zalsa, zalsa_local) = db.zalsas();
let id = AsId::as_id(&s); let id = AsId::as_id(&s);
let field_ingredient_index = self.ingredient_index.successor(relative_tracked_index); let field_ingredient_index = self.ingredient_index.successor(relative_tracked_index);
let data = Self::data(zalsa.table(), id); let data = Self::data(zalsa.table(), id);
@ -776,10 +775,9 @@ where
/// The caller is responsible for selecting the appropriate element. /// The caller is responsible for selecting the appropriate element.
pub fn untracked_field<'db>( pub fn untracked_field<'db>(
&'db self, &'db self,
db: &'db dyn crate::Database, zalsa: &'db Zalsa,
s: C::Struct<'db>, s: C::Struct<'db>,
) -> &'db C::Fields<'db> { ) -> &'db C::Fields<'db> {
let zalsa = db.zalsa();
let id = AsId::as_id(&s); let id = AsId::as_id(&s);
let data = Self::data(zalsa.table(), id); let data = Self::data(zalsa.table(), id);
@ -794,11 +792,8 @@ where
#[cfg(feature = "salsa_unstable")] #[cfg(feature = "salsa_unstable")]
/// Returns all data corresponding to the tracked struct. /// Returns all data corresponding to the tracked struct.
pub fn entries<'db>( pub fn entries<'db>(&'db self, zalsa: &'db Zalsa) -> impl Iterator<Item = &'db Value<C>> {
&'db self, zalsa.table().slots_of::<Value<C>>()
db: &'db dyn crate::Database,
) -> impl Iterator<Item = &'db Value<C>> {
db.zalsa().table().slots_of::<Value<C>>()
} }
} }
@ -816,7 +811,8 @@ where
unsafe fn maybe_changed_after( unsafe fn maybe_changed_after(
&self, &self,
_db: &dyn Database, _zalsa: &crate::zalsa::Zalsa,
_db: crate::database::RawDatabase<'_>,
_input: Id, _input: Id,
_revision: Revision, _revision: Revision,
_cycle_heads: &mut CycleHeads, _cycle_heads: &mut CycleHeads,
@ -863,9 +859,9 @@ where
/// Returns memory usage information about any tracked structs. /// Returns memory usage information about any tracked structs.
#[cfg(feature = "salsa_unstable")] #[cfg(feature = "salsa_unstable")]
fn memory_usage(&self, db: &dyn Database) -> Option<Vec<crate::database::SlotInfo>> { fn memory_usage(&self, db: &dyn crate::Database) -> Option<Vec<crate::database::SlotInfo>> {
let memory_usage = self let memory_usage = self
.entries(db) .entries(db.zalsa())
// SAFETY: The memo table belongs to a value that we allocated, so it // SAFETY: The memo table belongs to a value that we allocated, so it
// has the correct type. // has the correct type.
.map(|value| unsafe { value.memory_usage(&self.memo_table_types) }) .map(|value| unsafe { value.memory_usage(&self.memo_table_types) })

View file

@ -7,7 +7,7 @@ use crate::sync::Arc;
use crate::table::memo::MemoTableTypes; use crate::table::memo::MemoTableTypes;
use crate::tracked_struct::{Configuration, Value}; use crate::tracked_struct::{Configuration, Value};
use crate::zalsa::IngredientIndex; use crate::zalsa::IngredientIndex;
use crate::{Database, Id}; use crate::Id;
/// Created for each tracked struct. /// Created for each tracked struct.
/// ///
@ -55,14 +55,14 @@ where
self.ingredient_index self.ingredient_index
} }
unsafe fn maybe_changed_after<'db>( unsafe fn maybe_changed_after(
&'db self, &self,
db: &'db dyn Database, zalsa: &crate::zalsa::Zalsa,
_db: crate::database::RawDatabase<'_>,
input: Id, input: Id,
revision: crate::Revision, revision: crate::Revision,
_cycle_heads: &mut CycleHeads, _cycle_heads: &mut CycleHeads,
) -> VerifyResult { ) -> VerifyResult {
let zalsa = db.zalsa();
let data = <super::IngredientImpl<C>>::data(zalsa.table(), input); let data = <super::IngredientImpl<C>>::data(zalsa.table(), input);
let field_changed_at = data.revisions[self.field_index]; let field_changed_at = data.revisions[self.field_index];
VerifyResult::changed_if(field_changed_at > revision) VerifyResult::changed_if(field_changed_at > revision)

View file

@ -1,10 +1,15 @@
use std::any::{Any, TypeId}; use std::{
any::{Any, TypeId},
marker::PhantomData,
mem,
ptr::NonNull,
};
use crate::Database; use crate::{database::RawDatabase, Database};
/// A `Views` struct is associated with some specific database type /// A `Views` struct is associated with some specific database type
/// (a `DatabaseImpl<U>` for some existential `U`). It contains functions /// (a `DatabaseImpl<U>` for some existential `U`). It contains functions
/// to downcast from `dyn Database` to `dyn DbView` for various traits `DbView` via this specific /// to downcast to `dyn DbView` for various traits `DbView` via this specific
/// database type. /// database type.
/// None of these types are known at compilation time, they are all checked /// None of these types are known at compilation time, they are all checked
/// dynamically through `TypeId` magic. /// dynamically through `TypeId` magic.
@ -13,6 +18,7 @@ pub struct Views {
view_casters: boxcar::Vec<ViewCaster>, view_casters: boxcar::Vec<ViewCaster>,
} }
#[derive(Copy, Clone)]
struct ViewCaster { struct ViewCaster {
/// The id of the target type `dyn DbView` that we can cast to. /// The id of the target type `dyn DbView` that we can cast to.
target_type_id: TypeId, target_type_id: TypeId,
@ -20,50 +26,69 @@ struct ViewCaster {
/// The name of the target type `dyn DbView` that we can cast to. /// The name of the target type `dyn DbView` that we can cast to.
type_name: &'static str, type_name: &'static str,
/// Type-erased function pointer that downcasts from `dyn Database` to `dyn DbView`. /// Type-erased function pointer that downcasts to `dyn DbView`.
cast: ErasedDatabaseDownCasterSig, cast: ErasedDatabaseDownCasterSig,
} }
impl ViewCaster { impl ViewCaster {
fn new<DbView: ?Sized + Any>(func: unsafe fn(&dyn Database) -> &DbView) -> ViewCaster { fn new<DbView: ?Sized + Any>(func: DatabaseDownCasterSig<DbView>) -> ViewCaster {
ViewCaster { ViewCaster {
target_type_id: TypeId::of::<DbView>(), target_type_id: TypeId::of::<DbView>(),
type_name: std::any::type_name::<DbView>(), type_name: std::any::type_name::<DbView>(),
// SAFETY: We are type erasing for storage, taking care of unerasing before we call // SAFETY: We are type erasing for storage, taking care of unerasing before we call
// the function pointer. // the function pointer.
cast: unsafe { cast: unsafe {
std::mem::transmute::<DatabaseDownCasterSig<DbView>, ErasedDatabaseDownCasterSig>( mem::transmute::<DatabaseDownCasterSig<DbView>, ErasedDatabaseDownCasterSig>(func)
func,
)
}, },
} }
} }
} }
type ErasedDatabaseDownCasterSig = unsafe fn(&dyn Database) -> *const (); type ErasedDatabaseDownCasterSig = unsafe fn(RawDatabase<'_>) -> NonNull<()>;
type DatabaseDownCasterSig<DbView> = unsafe fn(&dyn Database) -> &DbView; type DatabaseDownCasterSig<DbView> = unsafe fn(RawDatabase<'_>) -> NonNull<DbView>;
pub struct DatabaseDownCaster<DbView: ?Sized>(TypeId, DatabaseDownCasterSig<DbView>); #[repr(transparent)]
pub struct DatabaseDownCaster<DbView: ?Sized>(ViewCaster, PhantomData<fn() -> DbView>);
impl<DbView: ?Sized> Copy for DatabaseDownCaster<DbView> {}
impl<DbView: ?Sized> Clone for DatabaseDownCaster<DbView> {
fn clone(&self) -> Self {
*self
}
}
impl<DbView: ?Sized + Any> DatabaseDownCaster<DbView> { impl<DbView: ?Sized + Any> DatabaseDownCaster<DbView> {
pub fn downcast<'db>(&self, db: &'db dyn Database) -> &'db DbView {
assert_eq!(
self.0,
db.type_id(),
"Database type does not match the expected type for this `Views` instance"
);
// SAFETY: We've asserted that the database is correct.
unsafe { (self.1)(db) }
}
/// Downcast `db` to `DbView`. /// Downcast `db` to `DbView`.
/// ///
/// # Safety /// # Safety
/// ///
/// The caller must ensure that `db` is of the correct type. /// The caller must ensure that `db` is of the correct type.
pub unsafe fn downcast_unchecked<'db>(&self, db: &'db dyn Database) -> &'db DbView { #[inline]
pub unsafe fn downcast_unchecked<'db>(&self, db: RawDatabase<'db>) -> &'db DbView {
// SAFETY: The caller must ensure that `db` is of the correct type. // SAFETY: The caller must ensure that `db` is of the correct type.
unsafe { (self.1)(db) } // The returned pointer is live for `'db` due to construction of the downcaster functions.
unsafe { (self.unerased_downcaster())(db).as_ref() }
}
/// Downcast `db` to `DbView`.
///
/// # Safety
///
/// The caller must ensure that `db` is of the correct type.
#[inline]
pub unsafe fn downcast_mut_unchecked<'db>(&self, db: RawDatabase<'db>) -> &'db mut DbView {
// SAFETY: The caller must ensure that `db` is of the correct type.
// The returned pointer is live for `'db` due to construction of the downcaster functions.
unsafe { (self.unerased_downcaster())(db).as_mut() }
}
#[inline]
fn unerased_downcaster(&self) -> DatabaseDownCasterSig<DbView> {
// SAFETY: The type-erased function pointer is guaranteed to be ABI compatible for `DbView`
unsafe {
mem::transmute::<ErasedDatabaseDownCasterSig, DatabaseDownCasterSig<DbView>>(
self.0.cast,
)
}
} }
} }
@ -71,58 +96,63 @@ impl Views {
pub(crate) fn new<Db: Database>() -> Self { pub(crate) fn new<Db: Database>() -> Self {
let source_type_id = TypeId::of::<Db>(); let source_type_id = TypeId::of::<Db>();
let view_casters = boxcar::Vec::new(); let view_casters = boxcar::Vec::new();
// special case the no-op transformation, that way we skip out on reconstructing the wide pointer view_casters.push(ViewCaster::new::<dyn Database>(|db| db.ptr.cast::<Db>()));
view_casters.push(ViewCaster::new::<dyn Database>(|db| db));
Self { Self {
source_type_id, source_type_id,
view_casters, view_casters,
} }
} }
/// Add a new downcaster from `dyn Database` to `dyn DbView`. /// Add a new downcaster to `dyn DbView`.
pub fn add<DbView: ?Sized + Any>( pub fn add<Concrete: 'static, DbView: ?Sized + Any>(
&self, &self,
func: DatabaseDownCasterSig<DbView>, func: fn(NonNull<Concrete>) -> NonNull<DbView>,
) -> DatabaseDownCaster<DbView> { ) -> &DatabaseDownCaster<DbView> {
if let Some(view) = self.try_downcaster_for() { assert_eq!(self.source_type_id, TypeId::of::<Concrete>());
return view; let target_type_id = TypeId::of::<DbView>();
if let Some((_, caster)) = self
.view_casters
.iter()
.find(|(_, u)| u.target_type_id == target_type_id)
{
// SAFETY: The type-erased function pointer is guaranteed to be valid for `DbView`
return unsafe { &*(&raw const *caster).cast::<DatabaseDownCaster<DbView>>() };
} }
self.view_casters.push(ViewCaster::new::<DbView>(func)); // SAFETY: We are type erasing the function pointer for storage, and we will unerase it
DatabaseDownCaster(self.source_type_id, func) // before we call it.
let caster = unsafe {
mem::transmute::<fn(NonNull<Concrete>) -> NonNull<DbView>, DatabaseDownCasterSig<DbView>>(
func,
)
};
let caster = ViewCaster::new::<DbView>(caster);
let idx = self.view_casters.push(caster);
// SAFETY: The type-erased function pointer is guaranteed to be valid for `DbView`
unsafe { &*(&raw const self.view_casters[idx]).cast::<DatabaseDownCaster<DbView>>() }
} }
/// Retrieve an downcaster function from `dyn Database` to `dyn DbView`. /// Retrieve an downcaster function to `dyn DbView`.
/// ///
/// # Panics /// # Panics
/// ///
/// If the underlying type of `db` is not the same as the database type this upcasts was created for. /// If the underlying type of `db` is not the same as the database type this downcasts was created for.
pub fn downcaster_for<DbView: ?Sized + Any>(&self) -> DatabaseDownCaster<DbView> { pub fn downcaster_for<DbView: ?Sized + Any>(&self) -> &DatabaseDownCaster<DbView> {
self.try_downcaster_for().unwrap_or_else(|| {
panic!(
"No downcaster registered for type `{}` in `Views`",
std::any::type_name::<DbView>(),
)
})
}
/// Retrieve an downcaster function from `dyn Database` to `dyn DbView`, if it exists.
#[inline]
pub fn try_downcaster_for<DbView: ?Sized + Any>(&self) -> Option<DatabaseDownCaster<DbView>> {
let view_type_id = TypeId::of::<DbView>(); let view_type_id = TypeId::of::<DbView>();
for (_, view) in self.view_casters.iter() { for (_, view) in self.view_casters.iter() {
if view.target_type_id == view_type_id { if view.target_type_id == view_type_id {
// SAFETY: We are unerasing the type erased function pointer having made sure the // SAFETY: We are unerasing the type erased function pointer having made sure the
// `TypeId` matches. // TypeId matches.
return Some(DatabaseDownCaster(self.source_type_id, unsafe { return unsafe {
std::mem::transmute::<ErasedDatabaseDownCasterSig, DatabaseDownCasterSig<DbView>>( &*((view as *const ViewCaster).cast::<DatabaseDownCaster<DbView>>())
view.cast, };
)
}));
} }
} }
None panic!(
"No downcaster registered for type `{}` in `Views`",
std::any::type_name::<DbView>(),
);
} }
} }

View file

@ -5,6 +5,7 @@ use std::panic::RefUnwindSafe;
use hashbrown::HashMap; use hashbrown::HashMap;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use crate::database::RawDatabase;
use crate::hash::TypeIdHasher; use crate::hash::TypeIdHasher;
use crate::ingredient::{Ingredient, Jar}; use crate::ingredient::{Ingredient, Jar};
use crate::plumbing::SalsaStructInDb; use crate::plumbing::SalsaStructInDb;
@ -52,7 +53,7 @@ pub unsafe trait ZalsaDatabase: Any {
/// Clone the database. /// Clone the database.
#[doc(hidden)] #[doc(hidden)]
fn fork_db(&self) -> Box<dyn Database>; fn fork_db(&self) -> RawDatabase<'static>;
} }
pub fn views<Db: ?Sized + Database>(db: &Db) -> &Views { pub fn views<Db: ?Sized + Database>(db: &Db) -> &Views {

View file

@ -22,14 +22,15 @@ fn tracked_fn(db: &dyn salsa::Database, input: InputStruct) -> TrackedStruct<'_>
#[test] #[test]
fn execute() { fn execute() {
use salsa::plumbing::ZalsaDatabase;
let db = salsa::DatabaseImpl::new(); let db = salsa::DatabaseImpl::new();
let _ = InternedStruct::new(&db, "Salsa".to_string()); let _ = InternedStruct::new(&db, "Salsa".to_string());
let _ = InternedStruct::new(&db, "Salsa2".to_string()); let _ = InternedStruct::new(&db, "Salsa2".to_string());
// test interned structs // test interned structs
let interned = InternedStruct::ingredient(&db) let interned = InternedStruct::ingredient(db.zalsa())
.entries(&db) .entries(db.zalsa())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(interned.len(), 2); assert_eq!(interned.len(), 2);
@ -40,7 +41,7 @@ fn execute() {
let input = InputStruct::new(&db, 22); let input = InputStruct::new(&db, 22);
let inputs = InputStruct::ingredient(&db) let inputs = InputStruct::ingredient(&db)
.entries(&db) .entries(db.zalsa())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(inputs.len(), 1); assert_eq!(inputs.len(), 1);
@ -50,7 +51,7 @@ fn execute() {
let computed = tracked_fn(&db, input).field(&db); let computed = tracked_fn(&db, input).field(&db);
assert_eq!(computed, 44); assert_eq!(computed, 44);
let tracked = TrackedStruct::ingredient(&db) let tracked = TrackedStruct::ingredient(&db)
.entries(&db) .entries(db.zalsa())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(tracked.len(), 1); assert_eq!(tracked.len(), 1);

View file

@ -132,13 +132,13 @@ fn interning_boxed() {
#[test] #[test]
fn interned_structs_have_public_ingredients() { fn interned_structs_have_public_ingredients() {
use salsa::plumbing::AsId; use salsa::plumbing::{AsId, ZalsaDatabase};
let db = salsa::DatabaseImpl::new(); let db = salsa::DatabaseImpl::new();
let s = InternedString::new(&db, String::from("Hello, world!")); let s = InternedString::new(&db, String::from("Hello, world!"));
let underlying_id = s.0; let underlying_id = s.0;
let data = InternedString::ingredient(&db).data(&db, underlying_id.as_id()); let data = InternedString::ingredient(db.zalsa()).data(db.zalsa(), underlying_id.as_id());
assert_eq!(data, &(String::from("Hello, world!"),)); assert_eq!(data, &(String::from("Hello, world!"),));
} }

View file

@ -181,7 +181,8 @@ const _: () = {
String: zalsa_::interned::HashEqLike<T0>, String: zalsa_::interned::HashEqLike<T0>,
{ {
Configuration_::ingredient(db).intern( Configuration_::ingredient(db).intern(
db.as_dyn_database(), db.zalsa(),
db.zalsa_local(),
StructKey::<'db>(data, std::marker::PhantomData::default()), StructKey::<'db>(data, std::marker::PhantomData::default()),
|id, data| { |id, data| {
StructData( StructData(
@ -195,20 +196,20 @@ const _: () = {
where where
Db_: ?Sized + zalsa_::Database, Db_: ?Sized + zalsa_::Database,
{ {
let fields = Configuration_::ingredient(db).fields(db.as_dyn_database(), self); let fields = Configuration_::ingredient(db).fields(db.zalsa(), self);
std::clone::Clone::clone((&fields.0)) std::clone::Clone::clone((&fields.0))
} }
fn other<Db_>(self, db: &'db Db_) -> InternedString<'db> fn other<Db_>(self, db: &'db Db_) -> InternedString<'db>
where where
Db_: ?Sized + zalsa_::Database, Db_: ?Sized + zalsa_::Database,
{ {
let fields = Configuration_::ingredient(db).fields(db.as_dyn_database(), self); let fields = Configuration_::ingredient(db).fields(db.zalsa(), self);
std::clone::Clone::clone((&fields.1)) std::clone::Clone::clone((&fields.1))
} }
#[doc = r" Default debug formatting for this struct (may be useful if you define your own `Debug` impl)"] #[doc = r" Default debug formatting for this struct (may be useful if you define your own `Debug` impl)"]
pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
zalsa_::with_attached_database(|db| { zalsa_::with_attached_database(|db| {
let fields = Configuration_::ingredient(db).fields(db.as_dyn_database(), this); let fields = Configuration_::ingredient(db).fields(db.zalsa(), this);
let mut f = f.debug_struct("InternedString"); let mut f = f.debug_struct("InternedString");
let f = f.field("data", &fields.0); let f = f.field("data", &fields.0);
let f = f.field("other", &fields.1); let f = f.field("other", &fields.1);