mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-01-15 01:39:25 +00:00
rework interning to have a Configuration
This will permit GATs so that interned values can carry lifetimes.
This commit is contained in:
parent
54c9586b45
commit
97fc6a0920
4 changed files with 91 additions and 38 deletions
|
@ -55,8 +55,10 @@ impl InternedStruct {
|
||||||
fn generate_interned(&self) -> syn::Result<TokenStream> {
|
fn generate_interned(&self) -> syn::Result<TokenStream> {
|
||||||
self.validate_interned()?;
|
self.validate_interned()?;
|
||||||
let id_struct = self.the_struct_id();
|
let id_struct = self.the_struct_id();
|
||||||
|
let config_struct = self.config_struct();
|
||||||
let data_struct = self.data_struct();
|
let data_struct = self.data_struct();
|
||||||
let ingredients_for_impl = self.ingredients_for_impl();
|
let configuration_impl = self.configuration_impl(&data_struct.ident, &config_struct.ident);
|
||||||
|
let ingredients_for_impl = self.ingredients_for_impl(&config_struct.ident);
|
||||||
let as_id_impl = self.as_id_impl();
|
let as_id_impl = self.as_id_impl();
|
||||||
let named_fields_impl = self.inherent_impl_for_named_fields();
|
let named_fields_impl = self.inherent_impl_for_named_fields();
|
||||||
let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl();
|
let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl();
|
||||||
|
@ -64,6 +66,8 @@ impl InternedStruct {
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#id_struct
|
#id_struct
|
||||||
|
#config_struct
|
||||||
|
#configuration_impl
|
||||||
#data_struct
|
#data_struct
|
||||||
#ingredients_for_impl
|
#ingredients_for_impl
|
||||||
#as_id_impl
|
#as_id_impl
|
||||||
|
@ -113,6 +117,20 @@ impl InternedStruct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn configuration_impl(
|
||||||
|
&self,
|
||||||
|
data_struct: &syn::Ident,
|
||||||
|
config_struct: &syn::Ident,
|
||||||
|
) -> syn::ItemImpl {
|
||||||
|
parse_quote_spanned!(
|
||||||
|
config_struct.span() =>
|
||||||
|
|
||||||
|
impl salsa::interned::Configuration for #config_struct {
|
||||||
|
type Data = #data_struct;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// If this is an interned struct, then generate methods to access each field,
|
/// If this is an interned struct, then generate methods to access each field,
|
||||||
/// as well as a `new` method.
|
/// as well as a `new` method.
|
||||||
fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl {
|
fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl {
|
||||||
|
@ -186,15 +204,14 @@ impl InternedStruct {
|
||||||
/// Generates an impl of `salsa::storage::IngredientsFor`.
|
/// Generates an impl of `salsa::storage::IngredientsFor`.
|
||||||
///
|
///
|
||||||
/// For a memoized type, the only ingredient is an `InternedIngredient`.
|
/// For a memoized type, the only ingredient is an `InternedIngredient`.
|
||||||
fn ingredients_for_impl(&self) -> syn::ItemImpl {
|
fn ingredients_for_impl(&self, config_struct: &syn::Ident) -> syn::ItemImpl {
|
||||||
let id_ident = self.the_ident();
|
let id_ident = self.the_ident();
|
||||||
let debug_name = crate::literal(id_ident);
|
let debug_name = crate::literal(id_ident);
|
||||||
let jar_ty = self.jar_ty();
|
let jar_ty = self.jar_ty();
|
||||||
let data_ident = self.data_ident();
|
|
||||||
parse_quote! {
|
parse_quote! {
|
||||||
impl salsa::storage::IngredientsFor for #id_ident {
|
impl salsa::storage::IngredientsFor for #id_ident {
|
||||||
type Jar = #jar_ty;
|
type Jar = #jar_ty;
|
||||||
type Ingredients = salsa::interned::InternedIngredient<#data_ident>;
|
type Ingredients = salsa::interned::InternedIngredient<#config_struct>;
|
||||||
|
|
||||||
fn create_ingredients<DB>(
|
fn create_ingredients<DB>(
|
||||||
routes: &mut salsa::routes::Routes<DB>,
|
routes: &mut salsa::routes::Routes<DB>,
|
||||||
|
|
|
@ -293,6 +293,7 @@ fn fn_struct(args: &FnArgs, item_fn: &syn::ItemFn) -> syn::Result<(syn::Type, To
|
||||||
let struct_item_ident = &struct_item.ident;
|
let struct_item_ident = &struct_item.ident;
|
||||||
let config_ty: syn::Type = parse_quote!(#struct_item_ident);
|
let config_ty: syn::Type = parse_quote!(#struct_item_ident);
|
||||||
let configuration_impl = configuration.to_impl(&config_ty);
|
let configuration_impl = configuration.to_impl(&config_ty);
|
||||||
|
let interned_configuration_impl = interned_configuration_impl(item_fn, &config_ty);
|
||||||
let ingredients_for_impl = ingredients_for_impl(args, item_fn, &config_ty);
|
let ingredients_for_impl = ingredients_for_impl(args, item_fn, &config_ty);
|
||||||
let item_impl = setter_impl(args, item_fn, &config_ty)?;
|
let item_impl = setter_impl(args, item_fn, &config_ty)?;
|
||||||
|
|
||||||
|
@ -301,22 +302,27 @@ fn fn_struct(args: &FnArgs, item_fn: &syn::ItemFn) -> syn::Result<(syn::Type, To
|
||||||
quote! {
|
quote! {
|
||||||
#struct_item
|
#struct_item
|
||||||
#configuration_impl
|
#configuration_impl
|
||||||
|
#interned_configuration_impl
|
||||||
#ingredients_for_impl
|
#ingredients_for_impl
|
||||||
#item_impl
|
#item_impl
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the key type for this tracked function.
|
fn interned_configuration_impl(item_fn: &syn::ItemFn, config_ty: &syn::Type) -> syn::ItemImpl {
|
||||||
/// This is a tuple of all the argument types (apart from the database).
|
|
||||||
fn key_tuple_ty(item_fn: &syn::ItemFn) -> syn::Type {
|
|
||||||
let arg_tys = item_fn.sig.inputs.iter().skip(1).map(|arg| match arg {
|
let arg_tys = item_fn.sig.inputs.iter().skip(1).map(|arg| match arg {
|
||||||
syn::FnArg::Receiver(_) => unreachable!(),
|
syn::FnArg::Receiver(_) => unreachable!(),
|
||||||
syn::FnArg::Typed(pat_ty) => pat_ty.ty.clone(),
|
syn::FnArg::Typed(pat_ty) => pat_ty.ty.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let intern_data_ty: syn::Type = parse_quote!(
|
||||||
|
(#(#arg_tys),*)
|
||||||
|
);
|
||||||
|
|
||||||
parse_quote!(
|
parse_quote!(
|
||||||
(#(#arg_tys,)*)
|
impl salsa::interned::Configuration for #config_ty {
|
||||||
|
type Data = #intern_data_ty;
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,17 +330,15 @@ fn configuration_struct(item_fn: &syn::ItemFn) -> syn::ItemStruct {
|
||||||
let fn_name = item_fn.sig.ident.clone();
|
let fn_name = item_fn.sig.ident.clone();
|
||||||
let visibility = &item_fn.vis;
|
let visibility = &item_fn.vis;
|
||||||
|
|
||||||
let salsa_struct_ty = salsa_struct_ty(item_fn);
|
|
||||||
let intern_map: syn::Type = match function_type(item_fn) {
|
let intern_map: syn::Type = match function_type(item_fn) {
|
||||||
FunctionType::Constant => {
|
FunctionType::Constant => {
|
||||||
parse_quote! { salsa::interned::IdentityInterner<()> }
|
parse_quote! { salsa::interned::IdentityInterner<Self> }
|
||||||
}
|
}
|
||||||
FunctionType::SalsaStruct => {
|
FunctionType::SalsaStruct => {
|
||||||
parse_quote! { salsa::interned::IdentityInterner<#salsa_struct_ty> }
|
parse_quote! { salsa::interned::IdentityInterner<Self> }
|
||||||
}
|
}
|
||||||
FunctionType::RequiresInterning => {
|
FunctionType::RequiresInterning => {
|
||||||
let key_ty = key_tuple_ty(item_fn);
|
parse_quote! { salsa::interned::InternedIngredient<Self> }
|
||||||
parse_quote! { salsa::interned::InternedIngredient<#key_ty> }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -389,7 +393,24 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration {
|
||||||
|
|
||||||
let fn_ty = item_fn.sig.ident.clone();
|
let fn_ty = item_fn.sig.ident.clone();
|
||||||
|
|
||||||
let indices = (0..item_fn.sig.inputs.len() - 1).map(Literal::usize_unsuffixed);
|
// During recovery or execution, we are invoked with a `salsa::Id`
|
||||||
|
// that represents the interned value. We convert it back to a key
|
||||||
|
// which is either a single value (if there is one argument)
|
||||||
|
// or a tuple of values (if multiple arugments). `key_var` is the variable
|
||||||
|
// name that will store this result, and `key_splat` is a set of tokens
|
||||||
|
// that will convert it into one or multiple arguments (e.g., `key_var` if there
|
||||||
|
// is one argument or `key_var.0, key_var.1` if 2) that can be pasted into a function call.
|
||||||
|
let key_var = syn::Ident::new("__key", item_fn.span());
|
||||||
|
let key_fields = item_fn.sig.inputs.len() - 1;
|
||||||
|
let key_splat = if key_fields == 1 {
|
||||||
|
quote!(#key_var)
|
||||||
|
} else {
|
||||||
|
let indices = (0..key_fields)
|
||||||
|
.map(Literal::usize_unsuffixed)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
quote!(#(__key.#indices),*)
|
||||||
|
};
|
||||||
|
|
||||||
let (cycle_strategy, recover_fn) = if let Some(recovery_fn) = &args.recovery_fn {
|
let (cycle_strategy, recover_fn) = if let Some(recovery_fn) = &args.recovery_fn {
|
||||||
// Create the `recover_from_cycle` function, which (a) maps from the interned id to the actual
|
// Create the `recover_from_cycle` function, which (a) maps from the interned id to the actual
|
||||||
// keys and then (b) invokes the recover function itself.
|
// keys and then (b) invokes the recover function itself.
|
||||||
|
@ -400,8 +421,8 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration {
|
||||||
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
|
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
|
||||||
let __ingredients =
|
let __ingredients =
|
||||||
<_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar);
|
<_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar);
|
||||||
let __key = __ingredients.intern_map.data(__runtime, __id).clone();
|
let #key_var = __ingredients.intern_map.data(__runtime, __id).clone();
|
||||||
#recovery_fn(__db, __cycle, #(__key.#indices),*)
|
#recovery_fn(__db, __cycle, #key_splat)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(cycle_strategy, cycle_fullback)
|
(cycle_strategy, cycle_fullback)
|
||||||
|
@ -425,7 +446,6 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration {
|
||||||
|
|
||||||
// Create the `execute` function, which (a) maps from the interned id to the actual
|
// Create the `execute` function, which (a) maps from the interned id to the actual
|
||||||
// keys and then (b) invokes the function itself (which we embed within).
|
// keys and then (b) invokes the function itself (which we embed within).
|
||||||
let indices = (0..item_fn.sig.inputs.len() - 1).map(Literal::usize_unsuffixed);
|
|
||||||
let execute_fn = parse_quote! {
|
let execute_fn = parse_quote! {
|
||||||
fn execute<'db>(__db: &'db salsa::function::DynDb<'db, Self>, __id: salsa::Id) -> Self::Value<'db> {
|
fn execute<'db>(__db: &'db salsa::function::DynDb<'db, Self>, __id: salsa::Id) -> Self::Value<'db> {
|
||||||
#inner_fn
|
#inner_fn
|
||||||
|
@ -433,8 +453,8 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration {
|
||||||
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
|
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
|
||||||
let __ingredients =
|
let __ingredients =
|
||||||
<_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar);
|
<_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar);
|
||||||
let __key = __ingredients.intern_map.data(__runtime, __id).clone();
|
let #key_var = __ingredients.intern_map.data(__runtime, __id).clone();
|
||||||
#inner_fn_name(__db, #(__key.#indices),*)
|
#inner_fn_name(__db, #key_splat)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,25 +18,29 @@ use super::ingredient::Ingredient;
|
||||||
use super::routes::IngredientIndex;
|
use super::routes::IngredientIndex;
|
||||||
use super::Revision;
|
use super::Revision;
|
||||||
|
|
||||||
|
pub trait Configuration {
|
||||||
|
type Data: InternedData;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait InternedData: Sized + Eq + Hash + Clone {}
|
pub trait InternedData: Sized + Eq + Hash + Clone {}
|
||||||
impl<T: Eq + Hash + Clone> InternedData for T {}
|
impl<T: Eq + Hash + Clone> InternedData for T {}
|
||||||
|
|
||||||
/// The interned ingredient has the job of hashing values of type `Data` to produce an `Id`.
|
/// The interned ingredient has the job of hashing values of type `Data` to produce an `Id`.
|
||||||
/// It used to store interned structs but also to store the id fields of a tracked struct.
|
/// It used to store interned structs but also to store the id fields of a tracked struct.
|
||||||
/// Interned values endure until they are explicitly removed in some way.
|
/// Interned values endure until they are explicitly removed in some way.
|
||||||
pub struct InternedIngredient<Data: InternedData> {
|
pub struct InternedIngredient<C: Configuration> {
|
||||||
/// Index of this ingredient in the database (used to construct database-ids, etc).
|
/// Index of this ingredient in the database (used to construct database-ids, etc).
|
||||||
ingredient_index: IngredientIndex,
|
ingredient_index: IngredientIndex,
|
||||||
|
|
||||||
/// Maps from data to the existing interned id for that data.
|
/// Maps from data to the existing interned id for that data.
|
||||||
///
|
///
|
||||||
/// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa.
|
/// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa.
|
||||||
key_map: FxDashMap<Data, Id>,
|
key_map: FxDashMap<C::Data, Id>,
|
||||||
|
|
||||||
/// Maps from an interned id to its data.
|
/// Maps from an interned id to its data.
|
||||||
///
|
///
|
||||||
/// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa.
|
/// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa.
|
||||||
value_map: FxDashMap<Id, Box<Data>>,
|
value_map: FxDashMap<Id, Box<C::Data>>,
|
||||||
|
|
||||||
/// counter for the next id.
|
/// counter for the next id.
|
||||||
counter: AtomicCell<u32>,
|
counter: AtomicCell<u32>,
|
||||||
|
@ -52,14 +56,14 @@ pub struct InternedIngredient<Data: InternedData> {
|
||||||
/// references to that data floating about that are tied to the lifetime of some
|
/// references to that data floating about that are tied to the lifetime of some
|
||||||
/// `&db` reference. This queue itself is not freed until we have an `&mut db` reference,
|
/// `&db` reference. This queue itself is not freed until we have an `&mut db` reference,
|
||||||
/// guaranteeing that there are no more references to it.
|
/// guaranteeing that there are no more references to it.
|
||||||
deleted_entries: SegQueue<Box<Data>>,
|
deleted_entries: SegQueue<Box<C::Data>>,
|
||||||
|
|
||||||
debug_name: &'static str,
|
debug_name: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Data> InternedIngredient<Data>
|
impl<C> InternedIngredient<C>
|
||||||
where
|
where
|
||||||
Data: InternedData,
|
C: Configuration,
|
||||||
{
|
{
|
||||||
pub fn new(ingredient_index: IngredientIndex, debug_name: &'static str) -> Self {
|
pub fn new(ingredient_index: IngredientIndex, debug_name: &'static str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -78,7 +82,7 @@ where
|
||||||
/// * `id` is the interned id
|
/// * `id` is the interned id
|
||||||
/// * `b` is a boolean, `true` indicates this fn call added `data` to the interning table;
|
/// * `b` is a boolean, `true` indicates this fn call added `data` to the interning table;
|
||||||
/// `false` indicates it was already present
|
/// `false` indicates it was already present
|
||||||
pub(crate) fn intern_full(&self, runtime: &Runtime, data: Data) -> (Id, bool) {
|
pub(crate) fn intern_full(&self, runtime: &Runtime, data: C::Data) -> (Id, bool) {
|
||||||
runtime.report_tracked_read(
|
runtime.report_tracked_read(
|
||||||
DependencyIndex::for_table(self.ingredient_index),
|
DependencyIndex::for_table(self.ingredient_index),
|
||||||
Durability::MAX,
|
Durability::MAX,
|
||||||
|
@ -109,7 +113,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn intern(&self, runtime: &Runtime, data: Data) -> Id {
|
pub fn intern(&self, runtime: &Runtime, data: C::Data) -> Id {
|
||||||
self.intern_full(runtime, data).0
|
self.intern_full(runtime, data).0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +129,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn data<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db Data {
|
pub fn data<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db C::Data {
|
||||||
runtime.report_tracked_read(
|
runtime.report_tracked_read(
|
||||||
DependencyIndex::for_table(self.ingredient_index),
|
DependencyIndex::for_table(self.ingredient_index),
|
||||||
Durability::MAX,
|
Durability::MAX,
|
||||||
|
@ -187,9 +191,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<DB: ?Sized, Data> Ingredient<DB> for InternedIngredient<Data>
|
impl<DB: ?Sized, C> Ingredient<DB> for InternedIngredient<C>
|
||||||
where
|
where
|
||||||
Data: InternedData,
|
C: Configuration,
|
||||||
{
|
{
|
||||||
fn ingredient_index(&self) -> IngredientIndex {
|
fn ingredient_index(&self) -> IngredientIndex {
|
||||||
self.ingredient_index
|
self.ingredient_index
|
||||||
|
@ -247,28 +251,36 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Data> IngredientRequiresReset for InternedIngredient<Data>
|
impl<C> IngredientRequiresReset for InternedIngredient<C>
|
||||||
where
|
where
|
||||||
Data: InternedData,
|
C: Configuration,
|
||||||
{
|
{
|
||||||
const RESET_ON_NEW_REVISION: bool = false;
|
const RESET_ON_NEW_REVISION: bool = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IdentityInterner<Id: AsId> {
|
pub struct IdentityInterner<C>
|
||||||
data: PhantomData<Id>,
|
where
|
||||||
|
C: Configuration,
|
||||||
|
C::Data: AsId,
|
||||||
|
{
|
||||||
|
data: PhantomData<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Id: AsId> IdentityInterner<Id> {
|
impl<C> IdentityInterner<C>
|
||||||
|
where
|
||||||
|
C: Configuration,
|
||||||
|
C::Data: AsId,
|
||||||
|
{
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
IdentityInterner { data: PhantomData }
|
IdentityInterner { data: PhantomData }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn intern(&self, _runtime: &Runtime, id: Id) -> crate::Id {
|
pub fn intern(&self, _runtime: &Runtime, id: C::Data) -> crate::Id {
|
||||||
id.as_id()
|
id.as_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data(&self, _runtime: &Runtime, id: crate::Id) -> (Id,) {
|
pub fn data(&self, _runtime: &Runtime, id: crate::Id) -> C::Data {
|
||||||
(Id::from_id(id),)
|
<C::Data>::from_id(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,10 @@ struct TrackedStructKey {
|
||||||
data_hash: u64,
|
data_hash: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::interned::Configuration for TrackedStructKey {
|
||||||
|
type Data = TrackedStructKey;
|
||||||
|
}
|
||||||
|
|
||||||
// ANCHOR: TrackedStructValue
|
// ANCHOR: TrackedStructValue
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TrackedStructValue<C>
|
pub struct TrackedStructValue<C>
|
||||||
|
|
Loading…
Reference in a new issue