mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-01-13 00:40:22 +00:00
create macro-rules macros to encapsulate output
This is a new idea for how to manage procedural macros, which I kind of hate.
This commit is contained in:
parent
d666744704
commit
dde7341f97
8 changed files with 244 additions and 206 deletions
|
@ -18,6 +18,7 @@ log = "0.4.5"
|
|||
orx-concurrent-vec = "1.10.0"
|
||||
parking_lot = "0.12.1"
|
||||
rustc-hash = "1.1.0"
|
||||
salsa-macro-rules = { version = "0.1.0", path = "components/salsa-macro-rules" }
|
||||
salsa-macros = { path = "components/salsa-macros" }
|
||||
smallvec = "1.0.0"
|
||||
|
||||
|
@ -32,4 +33,4 @@ test-log = "0.2.11"
|
|||
trybuild = "1.0"
|
||||
|
||||
[workspace]
|
||||
members = ["components/salsa-macros"]
|
||||
members = [ "components/salsa-macro-rules","components/salsa-macros"]
|
||||
|
|
6
components/salsa-macro-rules/Cargo.toml
Normal file
6
components/salsa-macro-rules/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
200
components/salsa-macro-rules/src/lib.rs
Normal file
200
components/salsa-macro-rules/src/lib.rs
Normal file
|
@ -0,0 +1,200 @@
|
|||
//! This crate defines various `macro_rules` macros
|
||||
//! used as part of Salsa's internal plumbing.
|
||||
//!
|
||||
//! The procedural macros typically emit calls to these
|
||||
//! `macro_rules` macros.
|
||||
//!
|
||||
//! Modifying `macro_rules` macro definitions is generally
|
||||
//! more ergonomic and also permits true hygiene.
|
||||
|
||||
// Macro that generates the body of the cycle recovery function
|
||||
// for the case where no cycle recovery is possible. This has to be
|
||||
// a macro because it can take a variadic number of arguments.
|
||||
#[macro_export]
|
||||
macro_rules! unexpected_cycle_recovery {
|
||||
($db:ident, $cycle:ident, $($other_inputs:ident),*) => {
|
||||
{
|
||||
std::mem::drop($db);
|
||||
std::mem::drop(($($other_inputs),*));
|
||||
panic!("cannot recover from cycle `{:?}`", $cycle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro for setting up a function that must intern its arguments.
|
||||
#[macro_export]
|
||||
macro_rules! setup_interned_fn {
|
||||
(
|
||||
// Visibility of the function
|
||||
vis: $vis:vis,
|
||||
|
||||
// Name of the function
|
||||
fn_name: $fn_name:ident,
|
||||
|
||||
// Name of the `'db` lifetime that the user gave; if they didn't, then defaults to `'db`
|
||||
db_lt: $db_lt:lifetime,
|
||||
|
||||
// Path to the database trait that the user's database parameter used
|
||||
Db: $Db:path,
|
||||
|
||||
// Name of the database parameter given by the user.
|
||||
db: $db:ident,
|
||||
|
||||
// An identifier for each function argument EXCEPT the database.
|
||||
// We prefer to use the identifier the user gave, but if the user gave a pattern
|
||||
// (e.g., `(a, b): (u32, u32)`) we will synthesize an identifier.
|
||||
input_ids: [$($input_id:ident),*],
|
||||
|
||||
// Patterns that the user gave for each argument EXCEPT the database.
|
||||
// May be identifiers, but could be something else.
|
||||
input_pats: [$($input_pat:pat),*],
|
||||
|
||||
// Types of the function arguments (may reference `$generics`).
|
||||
input_tys: [$($input_ty:ty),*],
|
||||
|
||||
// Return type of the function (may reference `$generics`).
|
||||
output_ty: $output_ty:ty,
|
||||
|
||||
// Function body, may reference identifiers defined in `$input_pats` and the generics from `$generics`
|
||||
body: $body:block,
|
||||
|
||||
// Path to the cycle recovery function to use.
|
||||
cycle_recovery_fn: ($($cycle_recovery_fn:tt)*),
|
||||
|
||||
// Name of cycle recovery strategy variant to use.
|
||||
cycle_recovery_strategy: $cycle_recovery_strategy:ident,
|
||||
|
||||
// Annoyingly macro-rules hygiene does not extend to items defined in the macro.
|
||||
// We have the procedural macro generate names for those items that are
|
||||
// not used elsewhere in the user's code.
|
||||
unused_names: [
|
||||
$zalsa:ident,
|
||||
$Configuration:ident,
|
||||
$InternedData:ident,
|
||||
$FN_CACHE:ident,
|
||||
$INTERN_CACHE:ident,
|
||||
$inner:ident,
|
||||
]
|
||||
) => {
|
||||
$vis fn $fn_name<$db_lt>(
|
||||
$db: &$db_lt dyn $Db,
|
||||
$($input_id: $input_ty,)*
|
||||
) -> $output_ty {
|
||||
use salsa::plumbing as $zalsa;
|
||||
|
||||
struct $Configuration;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct $InternedData<'db>(
|
||||
std::ptr::NonNull<$zalsa::interned::ValueStruct<$Configuration>>,
|
||||
std::marker::PhantomData<&'db $zalsa::interned::ValueStruct<$Configuration>>,
|
||||
);
|
||||
|
||||
static $FN_CACHE: $zalsa::IngredientCache<$zalsa::function::IngredientImpl<$Configuration>> =
|
||||
$zalsa::IngredientCache::new();
|
||||
|
||||
static $INTERN_CACHE: $zalsa::IngredientCache<$zalsa::interned::IngredientImpl<$Configuration>> =
|
||||
$zalsa::IngredientCache::new();
|
||||
|
||||
impl $zalsa::SalsaStructInDb<dyn $Db> for $InternedData<'_> {
|
||||
fn register_dependent_fn(_db: &dyn $Db, _index: $zalsa::IngredientIndex) {}
|
||||
}
|
||||
|
||||
impl $zalsa::function::Configuration for $Configuration {
|
||||
const DEBUG_NAME: &'static str = stringify!($fn_name);
|
||||
|
||||
type DbView = dyn $Db;
|
||||
|
||||
type SalsaStruct<$db_lt> = $InternedData<$db_lt>;
|
||||
|
||||
type Input<$db_lt> = ($($input_ty),*);
|
||||
|
||||
type Output<$db_lt> = $output_ty;
|
||||
|
||||
const CYCLE_STRATEGY: $zalsa::CycleRecoveryStrategy = $zalsa::CycleRecoveryStrategy::$cycle_recovery_strategy;
|
||||
|
||||
fn should_backdate_value(
|
||||
old_value: &Self::Output<'_>,
|
||||
new_value: &Self::Output<'_>,
|
||||
) -> bool {
|
||||
old_value == new_value
|
||||
}
|
||||
|
||||
fn execute<'db>($db: &'db Self::DbView, ($($input_id),*): ($($input_ty),*)) -> Self::Output<'db> {
|
||||
$vis fn $inner<$db_lt>(
|
||||
$db: &$db_lt dyn $Db,
|
||||
$($input_pat: $input_ty,)*
|
||||
) -> $output_ty {
|
||||
$body
|
||||
}
|
||||
|
||||
$inner($db, $($input_id),*)
|
||||
}
|
||||
|
||||
fn recover_from_cycle<'db>(
|
||||
db: &$db_lt dyn $Db,
|
||||
cycle: &$zalsa::Cycle,
|
||||
($($input_id),*): ($($input_ty),*)
|
||||
) -> Self::Output<'db> {
|
||||
$($cycle_recovery_fn)*(db, cycle, $($input_id),*)
|
||||
}
|
||||
|
||||
fn id_to_input<'db>(db: &'db Self::DbView, key: salsa::Id) -> Self::Input<'db> {
|
||||
let ingredient = $INTERN_CACHE.get_or_create(db.as_salsa_database(), || {
|
||||
db.add_or_lookup_jar_by_type(&$Configuration) + 1
|
||||
});
|
||||
ingredient.data(key).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl $zalsa::interned::Configuration for $Configuration {
|
||||
const DEBUG_NAME: &'static str = "Configuration";
|
||||
|
||||
type Data<$db_lt> = ($($input_ty),*);
|
||||
|
||||
type Struct<$db_lt> = $InternedData<$db_lt>;
|
||||
|
||||
unsafe fn struct_from_raw<'db>(
|
||||
ptr: std::ptr::NonNull<$zalsa::interned::ValueStruct<Self>>,
|
||||
) -> Self::Struct<'db> {
|
||||
$InternedData(ptr, std::marker::PhantomData)
|
||||
}
|
||||
|
||||
fn deref_struct(s: Self::Struct<'_>) -> &$zalsa::interned::ValueStruct<Self> {
|
||||
unsafe { s.0.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl $zalsa::Jar for $Configuration {
|
||||
fn create_ingredients(
|
||||
&self,
|
||||
first_index: $zalsa::IngredientIndex,
|
||||
) -> Vec<Box<dyn $zalsa::Ingredient>> {
|
||||
vec![
|
||||
Box::new(<$zalsa::function::IngredientImpl<$Configuration>>::new(
|
||||
first_index,
|
||||
)),
|
||||
Box::new(<$zalsa::interned::IngredientImpl<$Configuration>>::new(
|
||||
first_index + 1,
|
||||
)),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
let intern_ingredient = $INTERN_CACHE.get_or_create($db.as_salsa_database(), || {
|
||||
$db.add_or_lookup_jar_by_type(&$Configuration) + 1
|
||||
});
|
||||
let key = intern_ingredient.intern_id($db.runtime(), ($($input_id),*));
|
||||
|
||||
let fn_ingredient = $FN_CACHE.get_or_create($db.as_salsa_database(), || {
|
||||
$db.add_or_lookup_jar_by_type(&$Configuration)
|
||||
});
|
||||
fn_ingredient.fetch($db, key).clone()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! setup_fn {
|
||||
() => {};
|
||||
}
|
|
@ -5,7 +5,7 @@ use crossbeam::atomic::AtomicCell;
|
|||
use crate::{
|
||||
cycle::CycleRecoveryStrategy,
|
||||
ingredient::{fmt_index, IngredientRequiresReset},
|
||||
key::{DatabaseKeyIndex, DependencyIndex},
|
||||
key::DatabaseKeyIndex,
|
||||
runtime::local_state::QueryOrigin,
|
||||
salsa_struct::SalsaStructInDb,
|
||||
storage::IngredientIndex,
|
||||
|
@ -30,8 +30,6 @@ mod specify;
|
|||
mod store;
|
||||
mod sync;
|
||||
|
||||
pub mod interned;
|
||||
|
||||
pub trait Configuration: Any {
|
||||
const DEBUG_NAME: &'static str;
|
||||
|
||||
|
@ -75,7 +73,11 @@ pub trait Configuration: Any {
|
|||
/// 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>(db: &'db Self::DbView, cycle: &Cycle, key: Id) -> Self::Output<'db>;
|
||||
fn recover_from_cycle<'db>(
|
||||
db: &'db Self::DbView,
|
||||
cycle: &Cycle,
|
||||
input: Self::Input<'db>,
|
||||
) -> Self::Output<'db>;
|
||||
}
|
||||
|
||||
/// Function ingredients are the "workhorse" of salsa.
|
||||
|
|
|
@ -56,7 +56,7 @@ where
|
|||
crate::cycle::CycleRecoveryStrategy::Fallback => {
|
||||
if let Some(c) = active_query.take_cycle() {
|
||||
assert!(c.is(&cycle));
|
||||
C::recover_from_cycle(db, &cycle, key)
|
||||
C::recover_from_cycle(db, &cycle, C::id_to_input(db, key))
|
||||
} else {
|
||||
// we are not a participant in this cycle
|
||||
debug_assert!(!cycle
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
//! Helper code for tracked functions that take arbitrary arguments.
|
||||
//! These arguments must be interned to create a salsa id before the
|
||||
//! salsa machinery can execute.
|
||||
|
||||
use std::{any::Any, fmt, hash::Hash, marker::PhantomData};
|
||||
|
||||
use crate::{
|
||||
function, interned, plumbing::CycleRecoveryStrategy, salsa_struct::SalsaStructInDb, Cycle, Id,
|
||||
};
|
||||
|
||||
pub trait Configuration: Any + Copy {
|
||||
const DEBUG_NAME: &'static str;
|
||||
type DbView: ?Sized + crate::Database;
|
||||
type SalsaStruct<'db>: SalsaStructInDb<Self::DbView>;
|
||||
type Input<'db>: Send + Sync + Clone + Hash + Eq;
|
||||
type Output<'db>: fmt::Debug + Send + Sync;
|
||||
const CYCLE_STRATEGY: CycleRecoveryStrategy;
|
||||
fn should_backdate_value(old_value: &Self::Output<'_>, new_value: &Self::Output<'_>) -> bool;
|
||||
fn id_to_input<'db>(db: &'db Self::DbView, key: Id) -> Self::Input<'db>;
|
||||
fn execute<'db>(db: &'db Self::DbView, input: Self::Input<'db>) -> Self::Output<'db>;
|
||||
fn recover_from_cycle<'db>(db: &'db Self::DbView, cycle: &Cycle, key: Id) -> Self::Output<'db>;
|
||||
}
|
||||
|
||||
pub struct InterningConfiguration<C: Configuration> {
|
||||
phantom: PhantomData<C>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct InternedData<'db, C: Configuration>(
|
||||
std::ptr::NonNull<interned::ValueStruct<C>>,
|
||||
std::marker::PhantomData<&'db interned::ValueStruct<C>>,
|
||||
);
|
||||
|
||||
impl<C: Configuration> SalsaStructInDb<C::DbView> for InternedData<'_, C> {
|
||||
fn register_dependent_fn(_db: &C::DbView, _index: crate::storage::IngredientIndex) {}
|
||||
}
|
||||
|
||||
impl<C: Configuration> interned::Configuration for C {
|
||||
const DEBUG_NAME: &'static str = C::DEBUG_NAME;
|
||||
|
||||
type Data<'db> = C::Input<'db>;
|
||||
|
||||
type Struct<'db> = InternedData<'db, C>;
|
||||
|
||||
unsafe fn struct_from_raw<'db>(
|
||||
ptr: std::ptr::NonNull<interned::ValueStruct<Self>>,
|
||||
) -> Self::Struct<'db> {
|
||||
InternedData(ptr, std::marker::PhantomData)
|
||||
}
|
||||
|
||||
fn deref_struct(s: Self::Struct<'_>) -> &interned::ValueStruct<Self> {
|
||||
unsafe { s.0.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Configuration> function::Configuration for C {
|
||||
const DEBUG_NAME: &'static str = C::DEBUG_NAME;
|
||||
|
||||
type DbView = C::DbView;
|
||||
|
||||
type SalsaStruct<'db> = InternedData<'db, C>;
|
||||
|
||||
type Input<'db> = C::Input<'db>;
|
||||
|
||||
type Output<'db> = C::Output<'db>;
|
||||
|
||||
const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = C::CYCLE_STRATEGY;
|
||||
|
||||
fn should_backdate_value(old_value: &Self::Output<'_>, new_value: &Self::Output<'_>) -> bool {
|
||||
C::should_backdate_value(old_value, new_value)
|
||||
}
|
||||
|
||||
fn id_to_input<'db>(db: &'db Self::DbView, key: crate::Id) -> Self::Input<'db> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn execute<'db>(db: &'db Self::DbView, input: Self::Input<'db>) -> Self::Output<'db> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn recover_from_cycle<'db>(
|
||||
db: &'db Self::DbView,
|
||||
cycle: &crate::Cycle,
|
||||
key: crate::Id,
|
||||
) -> Self::Output<'db> {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -66,6 +66,9 @@ pub mod plumbing {
|
|||
pub use crate::storage::IngredientIndex;
|
||||
pub use crate::storage::Storage;
|
||||
|
||||
pub use salsa_macro_rules::setup_interned_fn;
|
||||
pub use salsa_macro_rules::unexpected_cycle_recovery;
|
||||
|
||||
pub mod input {
|
||||
pub use crate::input::Configuration;
|
||||
pub use crate::input::IngredientImpl;
|
||||
|
|
|
@ -30,119 +30,33 @@ impl HasLogger for Database {
|
|||
}
|
||||
|
||||
// #[salsa::tracked]
|
||||
// fn identity(db: &dyn Db, input: u32) -> u32 {
|
||||
// fn final_result(db: &dyn Db, input: i32, (b, c): (i32, i32)) -> i32 {
|
||||
// db.push_log(format!("final_result({:?})", input));
|
||||
// input
|
||||
// }
|
||||
|
||||
fn identity(db: &dyn Db, input: u32) -> u32 {
|
||||
use salsa::plumbing as zalsa;
|
||||
|
||||
struct Configuration;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct InternedData<'db>(
|
||||
std::ptr::NonNull<zalsa::interned::ValueStruct<Configuration>>,
|
||||
std::marker::PhantomData<&'db zalsa::interned::ValueStruct<Configuration>>,
|
||||
);
|
||||
|
||||
static FN_CACHE: zalsa::IngredientCache<zalsa::function::IngredientImpl<Configuration>> =
|
||||
zalsa::IngredientCache::new();
|
||||
|
||||
static INTERN_CACHE: zalsa::IngredientCache<zalsa::interned::IngredientImpl<Configuration>> =
|
||||
zalsa::IngredientCache::new();
|
||||
|
||||
impl zalsa::SalsaStructInDb<dyn Db> for InternedData<'_> {
|
||||
fn register_dependent_fn(_db: &dyn Db, _index: zalsa::IngredientIndex) {}
|
||||
}
|
||||
|
||||
impl zalsa::function::Configuration for Configuration {
|
||||
const DEBUG_NAME: &'static str = "identity";
|
||||
|
||||
type DbView = dyn Db;
|
||||
|
||||
type SalsaStruct<'db> = InternedData<'db>;
|
||||
|
||||
type Input<'db> = u32;
|
||||
|
||||
type Output<'db> = u32;
|
||||
|
||||
const CYCLE_STRATEGY: zalsa::CycleRecoveryStrategy = zalsa::CycleRecoveryStrategy::Panic;
|
||||
|
||||
fn should_backdate_value(
|
||||
old_value: &Self::Output<'_>,
|
||||
new_value: &Self::Output<'_>,
|
||||
) -> bool {
|
||||
old_value == new_value
|
||||
}
|
||||
|
||||
fn execute<'db>(db: &'db Self::DbView, input: u32) -> Self::Output<'db> {
|
||||
fn inner(db: &dyn Db, input: u32) -> u32 {
|
||||
db.push_log(format!("final_result({:?})", input));
|
||||
input
|
||||
}
|
||||
|
||||
inner(db, input)
|
||||
}
|
||||
|
||||
fn recover_from_cycle<'db>(
|
||||
_db: &'db Self::DbView,
|
||||
_cycle: &zalsa::Cycle,
|
||||
_key: zalsa::Id,
|
||||
) -> Self::Output<'db> {
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn id_to_input<'db>(db: &'db Self::DbView, key: salsa::Id) -> Self::Input<'db> {
|
||||
let ingredient = INTERN_CACHE.get_or_create(db.as_salsa_database(), || {
|
||||
db.add_or_lookup_jar_by_type(&Configuration) + 1
|
||||
});
|
||||
ingredient.data(key).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl zalsa::interned::Configuration for Configuration {
|
||||
const DEBUG_NAME: &'static str = "Configuration";
|
||||
|
||||
type Data<'db> = u32;
|
||||
|
||||
type Struct<'db> = InternedData<'db>;
|
||||
|
||||
unsafe fn struct_from_raw<'db>(
|
||||
ptr: std::ptr::NonNull<zalsa::interned::ValueStruct<Self>>,
|
||||
) -> Self::Struct<'db> {
|
||||
InternedData(ptr, std::marker::PhantomData)
|
||||
}
|
||||
|
||||
fn deref_struct(s: Self::Struct<'_>) -> &zalsa::interned::ValueStruct<Self> {
|
||||
unsafe { s.0.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl zalsa::Jar for Configuration {
|
||||
fn create_ingredients(
|
||||
&self,
|
||||
first_index: zalsa::IngredientIndex,
|
||||
) -> Vec<Box<dyn zalsa::Ingredient>> {
|
||||
vec![
|
||||
Box::new(<zalsa::function::IngredientImpl<Configuration>>::new(
|
||||
first_index,
|
||||
)),
|
||||
Box::new(<zalsa::interned::IngredientImpl<Configuration>>::new(
|
||||
first_index + 1,
|
||||
)),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
let fn_ingredient = FN_CACHE.get_or_create(db.as_salsa_database(), || {
|
||||
db.add_or_lookup_jar_by_type(&Configuration)
|
||||
});
|
||||
let intern_ingredient = INTERN_CACHE.get_or_create(db.as_salsa_database(), || {
|
||||
db.add_or_lookup_jar_by_type(&Configuration) + 1
|
||||
});
|
||||
|
||||
let key = intern_ingredient.intern_id(db.runtime(), input);
|
||||
|
||||
fn_ingredient.fetch(db, key).clone()
|
||||
}
|
||||
salsa::plumbing::setup_interned_fn!(
|
||||
vis: ,
|
||||
fn_name: identity,
|
||||
db_lt: 'db,
|
||||
Db: Db,
|
||||
db: dbx,
|
||||
input_ids: [input1, input2],
|
||||
input_pats: [a, (b, c)],
|
||||
input_tys: [i32, (i32, i32)],
|
||||
output_ty: i32,
|
||||
body: {
|
||||
dbx.push_log(format!("final_result({a}, {b}, {c})"));
|
||||
a + b * c
|
||||
},
|
||||
cycle_recovery_fn: (salsa::plumbing::unexpected_cycle_recovery!),
|
||||
cycle_recovery_strategy: Panic,
|
||||
unused_names: [
|
||||
zalsa1,
|
||||
Configuration1,
|
||||
InternedData1,
|
||||
FN_CACHE1,
|
||||
INTERN_CACHE1,
|
||||
inner1,
|
||||
]
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue