Merge pull request #481 from nikomatsakis/struct-field-ingredients

Struct field ingredients
This commit is contained in:
Niko Matsakis 2024-04-02 22:06:51 +00:00 committed by GitHub
commit b5aa4294bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 795 additions and 300 deletions

View file

@ -31,6 +31,7 @@
- [Plumbing](./plumbing.md)
- [Jars and ingredients](./plumbing/jars_and_ingredients.md)
- [Databases and runtime](./plumbing/database_and_runtime.md)
- [Tracked structures](./plumbing/tracked_structs.md)
- [Query operations](./plumbing/query_ops.md)
- [maybe changed after](./plumbing/maybe_changed_after.md)
- [Fetch](./plumbing/fetch.md)

View file

@ -0,0 +1,50 @@
# Tracked structs
Tracked structs are stored in a special way to reduce their costs.
Tracked structs are created via a `new` operation.
## The tracked struct and tracked field ingredients
For a single tracked struct we create multiple ingredients.
The **tracked struct ingredient** is the ingredient created first.
It offers methods to create new instances of the struct and therefore
has unique access to the interner and hashtables used to create the struct id.
It also shares access to a hashtable that stores the `TrackedStructValue` that
contains the field data.
For each field, we create a **tracked field ingredient** that moderates access
to a particular field. All of these ingredients use that same shared hashtable
to access the `TrackedStructValue` instance for a given id. The `TrackedStructValue`
contains both the field values but also the revisions when they last changed value.
## Each tracked struct has a globally unique id
This will begin by creating a *globally unique, 32-bit id* for the tracked struct. It is created by interning a combination of
* the currently executing query;
* a u64 hash of the `#[id]` fields;
* a *disambiguator* that makes this hash unique within the current query. i.e., when a query starts executing, it creates an empty map, and the first time a tracked struct with a given hash is created, it gets disambiguator 0. The next one will be given 1, etc.
## Each tracked struct has a `TrackedStructValue` storing its data
The struct and field ingredients share access to a hashmap that maps
each field id to a value struct:
```rust,ignore
{{#include ../../../components/salsa-2022/src/tracked_struct.rs:TrackedStructValue}}
```
The value struct stores the values of the fields but also the revisions when
that field last changed. Each time the struct is recreated in a new revision,
the old and new values for its fields are compared and a new revision is created.
## The macro generates the tracked struct `Configuration`
The "configuration" for a tracked struct defines not only the types of the fields,
but also various important operations such as extracting the hashable id fields
and updating the "revisions" to track when a field last changed:
```rust,ignore
{{#include ../../../components/salsa-2022/src/tracked_struct.rs:Configuration}}
```

View file

@ -25,12 +25,8 @@
//! * data method `impl Foo { fn data(&self, db: &dyn crate::Db) -> FooData { FooData { f: self.f(db), ... } } }`
//! * this could be optimized, particularly for interned fields
use crate::{
configuration,
options::{AllowedOptions, Options},
};
use heck::ToUpperCamelCase;
use proc_macro2::{Ident, Literal, Span, TokenStream};
use crate::options::{AllowedOptions, Options};
use proc_macro2::{Ident, Span, TokenStream};
use syn::spanned::Spanned;
pub(crate) enum SalsaStructKind {
@ -217,69 +213,6 @@ impl<A: AllowedOptions> SalsaStruct<A> {
}
}
/// For each of the fields passed as an argument,
/// generate a struct named `Ident_Field` and an impl
/// of `salsa::function::Configuration` for that struct.
pub(crate) fn field_config_structs_and_impls<'a>(
&self,
fields: impl Iterator<Item = &'a SalsaField>,
) -> (Vec<syn::ItemStruct>, Vec<syn::ItemImpl>) {
let ident = &self.id_ident();
let jar_ty = self.jar_ty();
let visibility = self.visibility();
fields
.map(|ef| {
let value_field_name = ef.name();
let value_field_ty = ef.ty();
let value_field_backdate = ef.is_backdate_field();
let config_name = syn::Ident::new(
&format!(
"__{}",
format!("{}_{}", ident, value_field_name).to_upper_camel_case()
),
value_field_name.span(),
);
let item_struct: syn::ItemStruct = parse_quote! {
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
#visibility struct #config_name(std::convert::Infallible);
};
let execute_string = Literal::string(&format!("`execute` method for field `{}::{}` invoked",
ident,
ef.name(),
));
let recover_from_cycle_string = Literal::string(&format!("`execute` method for field `{}::{}` invoked",
ident,
ef.name(),
));
let should_backdate_value_fn = configuration::should_backdate_value_fn(value_field_backdate);
let item_impl: syn::ItemImpl = parse_quote! {
impl salsa::function::Configuration for #config_name {
type Jar = #jar_ty;
type SalsaStruct = #ident;
type Key = #ident;
type Value = #value_field_ty;
const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = salsa::cycle::CycleRecoveryStrategy::Panic;
#should_backdate_value_fn
fn execute(db: &salsa::function::DynDb<Self>, key: Self::Key) -> Self::Value {
panic!(#execute_string)
}
fn recover_from_cycle(db: &salsa::function::DynDb<Self>, cycle: &salsa::Cycle, key: Self::Key) -> Self::Value {
panic!(#recover_from_cycle_string)
}
}
};
(item_struct, item_impl)
})
.unzip()
}
/// Generate `impl salsa::AsId for Foo`
pub(crate) fn as_id_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
@ -307,7 +240,6 @@ impl<A: AllowedOptions> SalsaStruct<A> {
// `::salsa::debug::helper::SalsaDebug` will use `DebugWithDb` or fallbak to `Debug`
let fields = self
.all_fields()
.into_iter()
.map(|field| -> TokenStream {
let field_name_string = field.name().to_string();
let field_getter = field.get_name();
@ -405,7 +337,7 @@ impl SalsaField {
if BANNED_FIELD_NAMES.iter().any(|n| *n == field_name_str) {
return Err(syn::Error::new(
field_name.span(),
&format!(
format!(
"the field name `{}` is disallowed in salsa structs",
field_name_str
),
@ -435,6 +367,10 @@ impl SalsaField {
Ok(result)
}
pub(crate) fn span(&self) -> Span {
self.field.span()
}
/// The name of this field (all `SalsaField` instances are named).
pub(crate) fn name(&self) -> &syn::Ident {
self.field.ident.as_ref().unwrap()

View file

@ -105,6 +105,7 @@ pub(crate) fn tracked_impl(
),
None => format!("{}", self_type_name),
};
#[allow(clippy::manual_try_fold)] // we accumulate errors
let extra_impls = item_impl
.items
.iter_mut()

View file

@ -1,4 +1,4 @@
use proc_macro2::{Literal, TokenStream};
use proc_macro2::{Literal, Span, TokenStream};
use crate::salsa_struct::{SalsaField, SalsaStruct, SalsaStructKind};
@ -51,18 +51,18 @@ impl TrackedStruct {
fn generate_tracked(&self) -> syn::Result<TokenStream> {
self.validate_tracked()?;
let (config_structs, config_impls) =
self.field_config_structs_and_impls(self.value_fields());
let id_struct = self.id_struct();
let config_struct = self.config_struct();
let config_impl = self.config_impl(&config_struct);
let inherent_impl = self.tracked_inherent_impl();
let ingredients_for_impl = self.tracked_struct_ingredients(&config_structs);
let ingredients_for_impl = self.tracked_struct_ingredients(&config_struct);
let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl();
let tracked_struct_in_db_impl = self.tracked_struct_in_db_impl();
let as_id_impl = self.as_id_impl();
let as_debug_with_db_impl = self.as_debug_with_db_impl();
Ok(quote! {
#(#config_structs)*
#config_struct
#config_impl
#id_struct
#inherent_impl
#ingredients_for_impl
@ -70,7 +70,6 @@ impl TrackedStruct {
#tracked_struct_in_db_impl
#as_id_impl
#as_debug_with_db_impl
#(#config_impls)*
})
}
@ -78,27 +77,104 @@ impl TrackedStruct {
Ok(())
}
fn config_struct(&self) -> syn::ItemStruct {
let config_ident = syn::Ident::new(
&format!("__{}Config", self.id_ident()),
self.id_ident().span(),
);
let visibility = self.visibility();
parse_quote! {
#visibility struct #config_ident {
_uninhabited: std::convert::Infallible,
}
}
}
fn config_impl(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl {
let id_ident = self.id_ident();
let config_ident = &config_struct.ident;
let field_tys: Vec<_> = self.all_fields().map(SalsaField::ty).collect();
let id_field_indices = self.id_field_indices();
let arity = self.all_field_count();
// Create the function body that will update the revisions for each field.
// If a field is a "backdate field" (the default), then we first check if
// the new value is `==` to the old value. If so, we leave the revision unchanged.
let old_value = syn::Ident::new("old_value_", Span::call_site());
let new_value = syn::Ident::new("new_value_", Span::call_site());
let revisions = syn::Ident::new("revisions_", Span::call_site());
let current_revision = syn::Ident::new("current_revision_", Span::call_site());
let update_revisions: TokenStream = self
.all_fields()
.zip(0..)
.map(|(field, i)| {
let field_index = Literal::u32_unsuffixed(i);
if field.is_backdate_field() {
quote_spanned! { field.span() =>
if #old_value.#field_index != #new_value.#field_index {
#revisions[#field_index] = #current_revision;
}
}
} else {
quote_spanned! { field.span() =>
#revisions[#field_index] = #current_revision;
}
}
})
.collect();
parse_quote! {
impl salsa::tracked_struct::Configuration for #config_ident {
type Id = #id_ident;
type Fields = ( #(#field_tys,)* );
type Revisions = [salsa::Revision; #arity];
#[allow(clippy::unused_unit)]
fn id_fields(fields: &Self::Fields) -> impl std::hash::Hash {
( #( &fields.#id_field_indices ),* )
}
fn revision(revisions: &Self::Revisions, field_index: u32) -> salsa::Revision {
revisions[field_index as usize]
}
fn new_revisions(current_revision: salsa::Revision) -> Self::Revisions {
[current_revision; #arity]
}
fn update_revisions(
#current_revision: salsa::Revision,
#old_value: &Self::Fields,
#new_value: &Self::Fields,
#revisions: &mut Self::Revisions,
) {
#update_revisions
}
}
}
}
/// Generate an inherent impl with methods on the tracked type.
fn tracked_inherent_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let db_dyn_ty = self.db_dyn_ty();
let struct_index = self.tracked_struct_index();
let tracked_field_ingredients: Literal = self.tracked_field_ingredients_index();
let id_field_indices: Vec<_> = self.id_field_indices();
let id_field_names: Vec<_> = self.id_fields().map(SalsaField::name).collect();
let id_field_get_names: Vec<_> = self.id_fields().map(SalsaField::get_name).collect();
let id_field_tys: Vec<_> = self.id_fields().map(SalsaField::ty).collect();
let id_field_vises: Vec<_> = self.id_fields().map(SalsaField::vis).collect();
let id_field_clones: Vec<_> = self.id_fields().map(SalsaField::is_clone_field).collect();
let id_field_getters: Vec<syn::ImplItemMethod> = id_field_indices.iter().zip(&id_field_get_names).zip(&id_field_tys).zip(&id_field_vises).zip(&id_field_clones).map(|((((field_index, field_get_name), field_ty), field_vis), is_clone_field)|
let field_indices = self.all_field_indices();
let field_vises: Vec<_> = self.all_fields().map(SalsaField::vis).collect();
let field_tys: Vec<_> = self.all_fields().map(SalsaField::ty).collect();
let field_get_names: Vec<_> = self.all_fields().map(SalsaField::get_name).collect();
let field_clones: Vec<_> = self.all_fields().map(SalsaField::is_clone_field).collect();
let field_getters: Vec<syn::ImplItemMethod> = field_indices.iter().zip(&field_get_names).zip(&field_tys).zip(&field_vises).zip(&field_clones).map(|((((field_index, field_get_name), field_ty), field_vis), is_clone_field)|
if !*is_clone_field {
parse_quote! {
#field_vis fn #field_get_name<'db>(self, __db: &'db #db_dyn_ty) -> &'db #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
&__ingredients.#struct_index.tracked_struct_data(__runtime, self).#field_index
&__ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index
}
}
} else {
@ -107,65 +183,32 @@ impl TrackedStruct {
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#struct_index.tracked_struct_data(__runtime, self).#field_index.clone()
__ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index.clone()
}
}
}
)
.collect();
let value_field_indices = self.value_field_indices();
let value_field_names: Vec<_> = self.value_fields().map(SalsaField::name).collect();
let value_field_vises: Vec<_> = self.value_fields().map(SalsaField::vis).collect();
let value_field_tys: Vec<_> = self.value_fields().map(SalsaField::ty).collect();
let value_field_get_names: Vec<_> = self.value_fields().map(SalsaField::get_name).collect();
let value_field_clones: Vec<_> = self
.value_fields()
.map(SalsaField::is_clone_field)
.collect();
let value_field_getters: Vec<syn::ImplItemMethod> = value_field_indices.iter().zip(&value_field_get_names).zip(&value_field_tys).zip(&value_field_vises).zip(&value_field_clones).map(|((((field_index, field_get_name), field_ty), field_vis), is_clone_field)|
if !*is_clone_field {
parse_quote! {
#field_vis fn #field_get_name<'db>(self, __db: &'db #db_dyn_ty) -> &'db #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#field_index.fetch(__db, self)
}
}
} else {
parse_quote! {
#field_vis fn #field_get_name<'db>(self, __db: &'db #db_dyn_ty) -> #field_ty
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#field_index.fetch(__db, self).clone()
}
}
}
)
.collect();
let all_field_names = self.all_field_names();
let all_field_tys = self.all_field_tys();
let field_names = self.all_field_names();
let field_tys = self.all_field_tys();
let constructor_name = self.constructor_name();
parse_quote! {
#[allow(clippy::too_many_arguments)]
impl #ident {
pub fn #constructor_name(__db: &#db_dyn_ty, #(#all_field_names: #all_field_tys,)*) -> Self
pub fn #constructor_name(__db: &#db_dyn_ty, #(#field_names: #field_tys,)*) -> Self
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
let __id = __ingredients.#struct_index.new_struct(__runtime, (#(#id_field_names,)*));
#(
__ingredients.#value_field_indices.specify_and_record(__db, __id, #value_field_names);
)*
let __id = __ingredients.0.new_struct(
__runtime,
(#(#field_names,)*),
);
__id
}
#(#id_field_getters)*
#(#value_field_getters)*
#(#field_getters)*
}
}
}
@ -174,14 +217,15 @@ impl TrackedStruct {
///
/// The tracked struct's ingredients include both the main tracked struct ingredient along with a
/// function ingredient for each of the value fields.
fn tracked_struct_ingredients(&self, config_structs: &[syn::ItemStruct]) -> syn::ItemImpl {
fn tracked_struct_ingredients(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl {
use crate::literal;
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let id_field_tys: Vec<&syn::Type> = self.id_fields().map(SalsaField::ty).collect();
let value_field_indices: Vec<Literal> = self.value_field_indices();
let tracked_struct_index: Literal = self.tracked_struct_index();
let config_struct_names = config_structs.iter().map(|s| &s.ident);
let config_struct_name = &config_struct.ident;
let field_indices: Vec<Literal> = self.all_field_indices();
let arity = self.all_field_count();
let tracked_struct_ingredient: Literal = self.tracked_struct_ingredient_index();
let tracked_fields_ingredients: Literal = self.tracked_field_ingredients_index();
let debug_name_struct = literal(self.id_ident());
let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect();
@ -189,10 +233,8 @@ impl TrackedStruct {
impl salsa::storage::IngredientsFor for #ident {
type Jar = #jar_ty;
type Ingredients = (
#(
salsa::function::FunctionIngredient<#config_struct_names>,
)*
salsa::tracked_struct::TrackedStructIngredient<#ident, (#(#id_field_tys,)*)>,
salsa::tracked_struct::TrackedStructIngredient<#config_struct_name>,
[salsa::tracked_struct::TrackedFieldIngredient<#config_struct_name>; #arity],
);
fn create_ingredients<DB>(
@ -201,40 +243,43 @@ impl TrackedStruct {
where
DB: salsa::DbWithJar<Self::Jar> + salsa::storage::JarFromJars<Self::Jar>,
{
(
let struct_ingredient = {
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
&ingredients.#tracked_struct_ingredient
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
&mut ingredients.#tracked_struct_ingredient
},
);
salsa::tracked_struct::TrackedStructIngredient::new(index, #debug_name_struct)
};
let field_ingredients = [
#(
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
&ingredients.#value_field_indices
&ingredients.#tracked_fields_ingredients[#field_indices]
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
&mut ingredients.#value_field_indices
&mut ingredients.#tracked_fields_ingredients[#field_indices]
},
);
salsa::function::FunctionIngredient::new(index, #debug_name_fields)
struct_ingredient.new_field_ingredient(index, #field_indices, #debug_name_fields)
},
)*
{
let index = routes.push(
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient(jar);
&ingredients.#tracked_struct_index
},
|jars| {
let jar = <DB as salsa::storage::JarFromJars<Self::Jar>>::jar_from_jars_mut(jars);
let ingredients = <_ as salsa::storage::HasIngredientsFor<Self>>::ingredient_mut(jar);
&mut ingredients.#tracked_struct_index
},
);
salsa::tracked_struct::TrackedStructIngredient::new(index, #debug_name_struct)
},
)
];
(struct_ingredient, field_ingredients)
}
}
}
@ -244,7 +289,7 @@ impl TrackedStruct {
fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let tracked_struct_index: Literal = self.tracked_struct_index();
let tracked_struct_ingredient = self.tracked_struct_ingredient_index();
parse_quote! {
impl<DB> salsa::salsa_struct::SalsaStructInDb<DB> for #ident
where
@ -253,7 +298,7 @@ impl TrackedStruct {
fn register_dependent_fn(db: &DB, index: salsa::routes::IngredientIndex) {
let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident>>::ingredient(jar);
ingredients.#tracked_struct_index.register_dependent_fn(index)
ingredients.#tracked_struct_ingredient.register_dependent_fn(index)
}
}
}
@ -263,7 +308,7 @@ impl TrackedStruct {
fn tracked_struct_in_db_impl(&self) -> syn::ItemImpl {
let ident = self.id_ident();
let jar_ty = self.jar_ty();
let tracked_struct_index = self.tracked_struct_index();
let tracked_struct_ingredient = self.tracked_struct_ingredient_index();
parse_quote! {
impl<DB> salsa::tracked_struct::TrackedStructInDb<DB> for #ident
where
@ -272,46 +317,44 @@ impl TrackedStruct {
fn database_key_index(self, db: &DB) -> salsa::DatabaseKeyIndex {
let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db);
let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident>>::ingredient(jar);
ingredients.#tracked_struct_index.database_key_index(self)
ingredients.#tracked_struct_ingredient.database_key_index(self)
}
}
}
}
/// List of id fields (fields that are part of the tracked struct's identity across revisions).
///
/// If this is an enum, empty iterator.
fn id_fields(&self) -> impl Iterator<Item = &SalsaField> {
self.all_fields().filter(|ef| ef.is_id_field())
/// The index of the tracked struct ingredient in the ingredient tuple.
fn tracked_struct_ingredient_index(&self) -> Literal {
Literal::usize_unsuffixed(0)
}
/// List of value fields (fields that are not part of the tracked struct's identity across revisions).
///
/// If this is an enum, empty iterator.
fn value_fields(&self) -> impl Iterator<Item = &SalsaField> {
self.all_fields().filter(|ef| !ef.is_id_field())
/// The index of the tracked field ingredients array in the ingredient tuple.
fn tracked_field_ingredients_index(&self) -> Literal {
Literal::usize_unsuffixed(1)
}
/// For this struct, we create a tuple that contains the function ingredients
/// for each "other" field and the tracked-struct ingredient. This is the index of
/// the entity ingredient within that tuple.
fn tracked_struct_index(&self) -> Literal {
Literal::usize_unsuffixed(self.value_fields().count())
/// for each field and the tracked-struct ingredient. These are the indices
/// of the function ingredients within that tuple.
fn all_field_indices(&self) -> Vec<Literal> {
(0..self.all_fields().count())
.map(Literal::usize_unsuffixed)
.collect()
}
/// For this struct, we create a tuple that contains the function ingredients
/// for each "other" field and the tracked-struct ingredient. These are the indices
/// of the function ingredients within that tuple.
fn value_field_indices(&self) -> Vec<Literal> {
(0..self.value_fields().count())
.map(Literal::usize_unsuffixed)
.collect()
fn all_field_count(&self) -> Literal {
Literal::usize_unsuffixed(self.all_fields().count())
}
/// Indices of each of the id fields
fn id_field_indices(&self) -> Vec<Literal> {
(0..self.id_fields().count())
.map(Literal::usize_unsuffixed)
self.all_fields()
.zip(0..)
.filter(|(field, _)| field.is_id_field())
.map(|(_, index)| Literal::usize_unsuffixed(index))
.collect()
}
}

View file

@ -221,11 +221,7 @@ pub mod helper {
use std::{fmt, marker::PhantomData};
pub trait Fallback<T: fmt::Debug, Db: ?Sized> {
fn salsa_debug<'a, 'b>(
a: &'a T,
_db: &'b Db,
_include_all_fields: bool,
) -> &'a dyn fmt::Debug {
fn salsa_debug<'a>(a: &'a T, _db: &Db, _include_all_fields: bool) -> &'a dyn fmt::Debug {
a
}
}

View file

@ -168,6 +168,12 @@ where
// then we would have updated the `verified_at` field already.
// So the fact that we are here means that it was not specified
// during this revision or is otherwise stale.
//
// Example of how this can happen:
//
// Conditionally specified queries
// where the value is specified
// in rev 1 but not in rev 2.
return false;
}
QueryOrigin::BaseInput => {

View file

@ -1,4 +1,4 @@
use std::hash::{BuildHasher, Hash, Hasher};
use std::hash::{BuildHasher, Hash};
pub(crate) type FxHasher = std::hash::BuildHasherDefault<rustc_hash::FxHasher>;
pub(crate) type FxIndexSet<K> = indexmap::IndexSet<K, FxHasher>;
@ -8,7 +8,5 @@ pub(crate) type FxLinkedHashSet<K> = hashlink::LinkedHashSet<K, FxHasher>;
pub(crate) type FxHashSet<K> = std::collections::HashSet<K, FxHasher>;
pub(crate) fn hash<T: Hash>(t: &T) -> u64 {
let mut hasher = FxHasher::default().build_hasher();
t.hash(&mut hasher);
hasher.finish()
FxHasher::default().hash_one(t)
}

View file

@ -1,6 +1,7 @@
use crate::cycle::CycleRecoveryStrategy;
use crate::ingredient::{fmt_index, Ingredient, IngredientRequiresReset};
use crate::key::DependencyIndex;
use crate::plumbing::transmute_lifetime;
use crate::runtime::local_state::QueryOrigin;
use crate::runtime::StampedValue;
use crate::{AsId, DatabaseKeyIndex, Durability, Id, IngredientIndex, Revision, Runtime};
@ -104,14 +105,6 @@ where
}
}
// Returns `u` but with the lifetime of `t`.
//
// Safe if you know that data at `u` will remain shared
// until the reference `t` expires.
unsafe fn transmute_lifetime<'t, 'u, T, U>(_t: &'t T, u: &'u U) -> &'t U {
std::mem::transmute(u)
}
impl<DB: ?Sized, K, F> Ingredient<DB> for InputFieldIngredient<K, F>
where
K: AsId,

View file

@ -8,6 +8,7 @@ use crate::durability::Durability;
use crate::id::AsId;
use crate::ingredient::{fmt_index, IngredientRequiresReset};
use crate::key::DependencyIndex;
use crate::plumbing::transmute_lifetime;
use crate::runtime::local_state::QueryOrigin;
use crate::runtime::Runtime;
use crate::DatabaseKeyIndex;
@ -76,7 +77,12 @@ where
}
}
pub fn intern(&self, runtime: &Runtime, data: Data) -> Id {
/// Intern `data` and return `(id, b`) where
///
/// * `id` is the interned id
/// * `b` is a boolean, `true` indicates this fn call added `data` to the interning table;
/// `false` indicates it was already present
pub(crate) fn intern_full(&self, runtime: &Runtime, data: Data) -> (Id, bool) {
runtime.report_tracked_read(
DependencyIndex::for_table(self.ingredient_index),
Durability::MAX,
@ -86,12 +92,12 @@ where
// Optimisation to only get read lock on the map if the data has already
// been interned.
if let Some(id) = self.key_map.get(&data) {
return *id;
return (*id, false);
}
match self.key_map.entry(data.clone()) {
// Data has been interned by a racing call, use that ID instead
dashmap::mapref::entry::Entry::Occupied(entry) => *entry.get(),
dashmap::mapref::entry::Entry::Occupied(entry) => (*entry.get(), false),
// We won any races so should intern the data
dashmap::mapref::entry::Entry::Vacant(entry) => {
let next_id = self.counter.fetch_add(1);
@ -102,11 +108,15 @@ where
"next_id is guaranteed to be unique, bar overflow"
);
entry.insert(next_id);
next_id
(next_id, true)
}
}
}
pub fn intern(&self, runtime: &Runtime, data: Data) -> Id {
self.intern_full(runtime, data).0
}
pub(crate) fn reset_at(&self) -> Revision {
self.reset_at
}
@ -181,14 +191,6 @@ where
}
}
// Returns `u` but with the lifetime of `t`.
//
// Safe if you know that data at `u` will remain shared
// until the reference `t` expires.
unsafe fn transmute_lifetime<'t, 'u, T, U>(_t: &'t T, u: &'u U) -> &'t U {
std::mem::transmute(u)
}
impl<DB: ?Sized, Id, Data> Ingredient<DB> for InternedIngredient<Id, Data>
where
Id: InternedId,

View file

@ -22,7 +22,6 @@ pub mod runtime;
pub mod salsa_struct;
pub mod setter;
pub mod storage;
#[doc(hidden)]
pub mod tracked_struct;
pub use self::cancelled::Cancelled;
@ -43,8 +42,6 @@ pub use self::routes::IngredientIndex;
pub use self::runtime::Runtime;
pub use self::storage::DbWithJar;
pub use self::storage::Storage;
pub use self::tracked_struct::TrackedStructData;
pub use self::tracked_struct::TrackedStructId;
pub use salsa_2022_macros::accumulator;
pub use salsa_2022_macros::db;
pub use salsa_2022_macros::input;

View file

@ -4,7 +4,7 @@ use crate::storage::HasJars;
/// Initializes the `DB`'s jars in-place
///
/// # Safety:
/// # Safety
///
/// `init` must fully initialize all of jars fields
pub unsafe fn create_jars_inplace<DB: HasJars>(init: impl FnOnce(*mut DB::Jars)) -> Box<DB::Jars> {
@ -26,3 +26,12 @@ pub unsafe fn create_jars_inplace<DB: HasJars>(init: impl FnOnce(*mut DB::Jars))
unsafe { Box::from_raw(place) }
}
}
// Returns `u` but with the lifetime of `t`.
//
// Safe if you know that data at `u` will remain shared
// until the reference `t` expires.
#[allow(clippy::needless_lifetimes)]
pub(crate) unsafe fn transmute_lifetime<'t, 'u, T, U>(_t: &'t T, u: &'u U) -> &'t U {
std::mem::transmute(u)
}

View file

@ -166,13 +166,16 @@ impl Runtime {
/// * Add a query read on `DatabaseKeyIndex::for_table(entity_index)`
/// * Identify a unique disambiguator for the hash within the current query,
/// adding the hash to the current query's disambiguator table.
/// * Return that hash + id of the current query.
/// * Returns a tuple of:
/// * the id of the current query
/// * the current dependencies (durability, changed_at) of current query
/// * the disambiguator index
pub(crate) fn disambiguate_entity(
&self,
entity_index: IngredientIndex,
reset_at: Revision,
data_hash: u64,
) -> (DatabaseKeyIndex, Disambiguator) {
) -> (DatabaseKeyIndex, StampedValue<()>, Disambiguator) {
self.report_tracked_read(
DependencyIndex::for_table(entity_index),
Durability::MAX,

View file

@ -123,7 +123,7 @@ impl ActiveQuery {
pub(super) fn remove_cycle_participants(&mut self, cycle: &Cycle) {
for p in cycle.participant_keys() {
let p: DependencyIndex = p.into();
self.input_outputs.remove(&(EdgeKind::Input, p));
self.input_outputs.shift_remove(&(EdgeKind::Input, p));
}
}

View file

@ -290,7 +290,10 @@ impl LocalState {
}
#[track_caller]
pub(crate) fn disambiguate(&self, data_hash: u64) -> (DatabaseKeyIndex, Disambiguator) {
pub(crate) fn disambiguate(
&self,
data_hash: u64,
) -> (DatabaseKeyIndex, StampedValue<()>, Disambiguator) {
assert!(
self.query_in_progress(),
"cannot create a tracked struct disambiguator outside of a tracked function"
@ -298,7 +301,15 @@ impl LocalState {
self.with_query_stack(|stack| {
let top_query = stack.last_mut().unwrap();
let disambiguator = top_query.disambiguate(data_hash);
(top_query.database_key_index, disambiguator)
(
top_query.database_key_index,
StampedValue {
value: (),
durability: top_query.durability,
changed_at: top_query.changed_at,
},
disambiguator,
)
})
}
}

View file

@ -1,21 +1,65 @@
use std::fmt;
use std::{fmt, hash::Hash, sync::Arc};
use crossbeam::queue::SegQueue;
use crate::{
cycle::CycleRecoveryStrategy,
hash::FxDashMap,
id::AsId,
ingredient::{fmt_index, Ingredient, IngredientRequiresReset},
ingredient_list::IngredientList,
interned::{InternedData, InternedId, InternedIngredient},
interned::{InternedId, InternedIngredient},
key::{DatabaseKeyIndex, DependencyIndex},
runtime::{local_state::QueryOrigin, Runtime},
salsa_struct::SalsaStructInDb,
Database, Event, IngredientIndex, Revision,
Database, Durability, Event, IngredientIndex, Revision,
};
pub trait TrackedStructId: InternedId {}
impl<T: InternedId> TrackedStructId for T {}
pub use self::tracked_field::TrackedFieldIngredient;
pub trait TrackedStructData: InternedData {}
impl<T: InternedData> TrackedStructData for T {}
mod tracked_field;
// ANCHOR: Configuration
/// Trait that defines the key properties of a tracked struct.
/// Implemented by the `#[salsa::tracked]` macro when applied
/// to a struct.
pub trait Configuration {
/// The id type used to define instances of this struct.
/// The [`TrackedStructIngredient`][] contains the interner
/// that will create the id values.
type Id: InternedId;
/// A (possibly empty) tuple of the fields for this struct.
type Fields;
/// A array of [`Revision`][] values, one per each of the value fields.
/// When a struct is re-recreated in a new revision, the corresponding
/// entries for each field are updated to the new revision if their
/// values have changed (or if the field is marked as `#[no_eq]`).
type Revisions;
fn id_fields(fields: &Self::Fields) -> impl Hash;
/// Access the revision of a given value field.
/// `field_index` will be between 0 and the number of value fields.
fn revision(revisions: &Self::Revisions, field_index: u32) -> Revision;
/// Create a new value revision array where each element is set to `current_revision`.
fn new_revisions(current_revision: Revision) -> Self::Revisions;
/// Update an existing value revision array `revisions`,
/// given the tuple of the old values (`old_value`)
/// and the tuple of the values (`new_value`).
/// If a value has changed, then its element is
/// updated to `current_revision`.
fn update_revisions(
current_revision: Revision,
old_value: &Self::Fields,
new_value: &Self::Fields,
revisions: &mut Self::Revisions,
);
}
// ANCHOR_END: Configuration
pub trait TrackedStructInDb<DB: ?Sized + Database>: SalsaStructInDb<DB> {
/// Converts the identifier for this tracked struct into a `DatabaseKeyIndex`.
@ -30,12 +74,13 @@ pub trait TrackedStructInDb<DB: ?Sized + Database>: SalsaStructInDb<DB> {
/// Unlike normal interners, tracked struct indices can be deleted and reused aggressively:
/// when a tracked function re-executes,
/// any tracked structs that it created before but did not create this time can be deleted.
pub struct TrackedStructIngredient<Id, Data>
pub struct TrackedStructIngredient<C>
where
Id: TrackedStructId,
Data: TrackedStructData,
C: Configuration,
{
interned: InternedIngredient<Id, TrackedStructKey<Data>>,
interned: InternedIngredient<C::Id, TrackedStructKey>,
entity_data: Arc<FxDashMap<C::Id, Box<TrackedStructValue<C>>>>,
/// A list of each tracked function `f` whose key is this
/// tracked struct.
@ -45,58 +90,142 @@ where
/// so they can remove any data tied to that instance.
dependent_fns: IngredientList,
/// When specific entities are deleted, their data is added
/// to this vector rather than being immediately freed. This is because we may` have
/// references to that data floating about that are tied to the lifetime of some
/// `&db` reference. This queue itself is not freed until we have an `&mut db` reference,
/// guaranteeing that there are no more references to it.
deleted_entries: SegQueue<Box<TrackedStructValue<C>>>,
debug_name: &'static str,
}
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
struct TrackedStructKey<Data> {
query_key: Option<DatabaseKeyIndex>,
struct TrackedStructKey {
query_key: DatabaseKeyIndex,
disambiguator: Disambiguator,
data: Data,
data_hash: u64,
}
// ANCHOR: TrackedStructValue
#[derive(Debug)]
struct TrackedStructValue<C>
where
C: Configuration,
{
/// The durability minimum durability of all inputs consumed
/// by the creator query prior to creating this tracked struct.
/// If any of those inputs changes, then the creator query may
/// create this struct with different values.
durability: Durability,
/// The revision when this entity was most recently created.
/// Typically the current revision.
/// Used to detect "leaks" outside of the salsa system -- i.e.,
/// access to tracked structs that have not (yet?) been created in the
/// current revision. This should be impossible within salsa queries
/// but it can happen through "leaks" like thread-local data or storing
/// values outside of the root salsa query.
created_at: Revision,
/// Fields of this tracked struct. They can change across revisions,
/// but they do not change within a particular revision.
fields: C::Fields,
/// The revision information for each field: when did this field last change.
/// When tracked structs are re-created, this revision may be updated to the
/// current revision if the value is different.
revisions: C::Revisions,
}
// ANCHOR_END: TrackedStructValue
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
pub struct Disambiguator(pub u32);
impl<Id, Data> TrackedStructIngredient<Id, Data>
impl<C> TrackedStructIngredient<C>
where
Id: TrackedStructId,
Data: TrackedStructData,
C: Configuration,
{
pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self {
Self {
interned: InternedIngredient::new(index, debug_name),
entity_data: Default::default(),
dependent_fns: IngredientList::new(),
deleted_entries: SegQueue::default(),
debug_name,
}
}
pub fn database_key_index(&self, id: Id) -> DatabaseKeyIndex {
pub fn new_field_ingredient(
&self,
field_ingredient_index: IngredientIndex,
field_index: u32,
field_debug_name: &'static str,
) -> TrackedFieldIngredient<C> {
TrackedFieldIngredient {
ingredient_index: field_ingredient_index,
field_index,
entity_data: self.entity_data.clone(),
struct_debug_name: self.debug_name,
field_debug_name,
}
}
pub fn database_key_index(&self, id: C::Id) -> DatabaseKeyIndex {
DatabaseKeyIndex {
ingredient_index: self.interned.ingredient_index(),
key_index: id.as_id(),
}
}
pub fn new_struct(&self, runtime: &Runtime, data: Data) -> Id {
let data_hash = crate::hash::hash(&data);
let (query_key, disambiguator) = runtime.disambiguate_entity(
pub fn new_struct(&self, runtime: &Runtime, fields: C::Fields) -> C::Id {
let data_hash = crate::hash::hash(&C::id_fields(&fields));
let (query_key, current_deps, disambiguator) = runtime.disambiguate_entity(
self.interned.ingredient_index(),
self.interned.reset_at(),
data_hash,
);
let entity_key = TrackedStructKey {
query_key: Some(query_key),
disambiguator,
data,
};
let result = self.interned.intern(runtime, entity_key);
runtime.add_output(self.database_key_index(result).into());
result
}
pub fn tracked_struct_data<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db Data {
&self.interned.data(runtime, id).data
let entity_key = TrackedStructKey {
query_key,
disambiguator,
data_hash,
};
let (id, new_id) = self.interned.intern_full(runtime, entity_key);
runtime.add_output(self.database_key_index(id).into());
let current_revision = runtime.current_revision();
if new_id {
let old_value = self.entity_data.insert(
id,
Box::new(TrackedStructValue {
created_at: current_revision,
durability: current_deps.durability,
fields,
revisions: C::new_revisions(current_deps.changed_at),
}),
);
assert!(old_value.is_none());
} else {
let mut data = self.entity_data.get_mut(&id).unwrap();
let data = &mut *data;
if current_deps.durability < data.durability {
data.revisions = C::new_revisions(current_revision);
} else {
C::update_revisions(current_revision, &data.fields, &fields, &mut data.revisions);
}
data.created_at = current_revision;
data.durability = current_deps.durability;
// Subtle but important: we *always* update the values of the fields,
// even if they are `==` to the old values. This is because the `==`
// operation might not mean tha tthe fields are bitwise equal, and we
// want to take the new value.
data.fields = fields;
}
id
}
/// Deletes the given entities. This is used after a query `Q` executes and we can compare
@ -109,7 +238,7 @@ where
/// Using this method on an entity id that MAY be used in the current revision will lead to
/// unspecified results (but not UB). See [`InternedIngredient::delete_index`] for more
/// discussion and important considerations.
pub(crate) fn delete_entity(&self, db: &dyn crate::Database, id: Id) {
pub(crate) fn delete_entity(&self, db: &dyn crate::Database, id: C::Id) {
db.salsa_event(Event {
runtime_id: db.runtime().id(),
kind: crate::EventKind::DidDiscard {
@ -118,6 +247,10 @@ where
});
self.interned.delete_index(id);
if let Some((_, data)) = self.entity_data.remove(&id) {
self.deleted_entries.push(data);
}
for dependent_fn in self.dependent_fns.iter() {
db.salsa_struct_deleted(dependent_fn, id.as_id());
}
@ -131,11 +264,10 @@ where
}
}
impl<DB: ?Sized, Id, Data> Ingredient<DB> for TrackedStructIngredient<Id, Data>
impl<DB: ?Sized, C> Ingredient<DB> for TrackedStructIngredient<C>
where
Id: TrackedStructId,
Data: TrackedStructData,
DB: crate::Database,
DB: Database,
C: Configuration,
{
fn ingredient_index(&self) -> IngredientIndex {
self.interned.ingredient_index()
@ -172,12 +304,13 @@ where
// `executor` creates a tracked struct `salsa_output_key`,
// but it did not in the current revision.
// In that case, we can delete `stale_output_key` and any data associated with it.
let stale_output_key: Id = Id::from_id(stale_output_key.unwrap());
let stale_output_key: C::Id = <C::Id>::from_id(stale_output_key.unwrap());
self.delete_entity(db.as_salsa_database(), stale_output_key);
}
fn reset_for_new_revision(&mut self) {
self.interned.clear_deleted_indices();
std::mem::take(&mut self.deleted_entries);
}
fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) {
@ -189,10 +322,9 @@ where
}
}
impl<Id, Data> IngredientRequiresReset for TrackedStructIngredient<Id, Data>
impl<C> IngredientRequiresReset for TrackedStructIngredient<C>
where
Id: TrackedStructId,
Data: TrackedStructData,
C: Configuration,
{
const RESET_ON_NEW_REVISION: bool = true;
}

View file

@ -0,0 +1,153 @@
use std::sync::Arc;
use crate::{
hash::FxDashMap,
id::AsId,
ingredient::{Ingredient, IngredientRequiresReset},
key::DependencyIndex,
plumbing::transmute_lifetime,
tracked_struct::TrackedStructValue,
IngredientIndex, Runtime,
};
use super::Configuration;
/// Created for each tracked struct.
/// This ingredient only stores the "id" fields.
/// It is a kind of "dressed up" interner;
/// the active query + values of id fields are hashed to create the tracked struct id.
/// The value fields are stored in [`crate::function::FunctionIngredient`] instances keyed by the tracked struct id.
/// Unlike normal interners, tracked struct indices can be deleted and reused aggressively:
/// when a tracked function re-executes,
/// any tracked structs that it created before but did not create this time can be deleted.
pub struct TrackedFieldIngredient<C>
where
C: Configuration,
{
/// Index of this ingredient in the database (used to construct database-ids, etc).
pub(super) ingredient_index: IngredientIndex,
pub(super) field_index: u32,
pub(super) entity_data: Arc<FxDashMap<C::Id, Box<TrackedStructValue<C>>>>,
pub(super) struct_debug_name: &'static str,
pub(super) field_debug_name: &'static str,
}
impl<C> TrackedFieldIngredient<C>
where
C: Configuration,
{
/// Access to this value field.
/// Note that this function returns the entire tuple of value fields.
/// The caller is responible for selecting the appropriate element.
pub fn field<'db>(&'db self, runtime: &'db Runtime, id: C::Id) -> &'db C::Fields {
let Some(data) = self.entity_data.get(&id) else {
panic!("no data found for entity id {id:?}");
};
let current_revision = runtime.current_revision();
let created_at = data.created_at;
assert!(
created_at == current_revision,
"access to tracked struct from previous revision"
);
let changed_at = C::revision(&data.revisions, self.field_index);
runtime.report_tracked_read(
DependencyIndex {
ingredient_index: self.ingredient_index,
key_index: Some(id.as_id()),
},
data.durability,
changed_at,
);
// Unsafety clause:
//
// * Values are only removed or altered when we have `&mut self`
unsafe { transmute_lifetime(self, &data.fields) }
}
}
impl<DB: ?Sized, C> Ingredient<DB> for TrackedFieldIngredient<C>
where
C: Configuration,
{
fn ingredient_index(&self) -> IngredientIndex {
self.ingredient_index
}
fn cycle_recovery_strategy(&self) -> crate::cycle::CycleRecoveryStrategy {
crate::cycle::CycleRecoveryStrategy::Panic
}
fn maybe_changed_after(
&self,
_db: &DB,
input: crate::key::DependencyIndex,
revision: crate::Revision,
) -> bool {
let id = <C::Id>::from_id(input.key_index.unwrap());
eprintln!("maybe_changed_after({id:?}, {revision:?})");
match self.entity_data.get(&id) {
Some(data) => {
let field_changed_at = C::revision(&data.revisions, self.field_index);
field_changed_at > revision
}
None => {
panic!("no data found for field `{id:?}`");
}
}
}
fn origin(&self, _key_index: crate::Id) -> Option<crate::runtime::local_state::QueryOrigin> {
None
}
fn mark_validated_output(
&self,
_db: &DB,
_executor: crate::DatabaseKeyIndex,
_output_key: Option<crate::Id>,
) {
panic!("tracked field ingredients have no outputs")
}
fn remove_stale_output(
&self,
_db: &DB,
_executor: crate::DatabaseKeyIndex,
_stale_output_key: Option<crate::Id>,
) {
panic!("tracked field ingredients have no outputs")
}
fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) {
panic!("tracked field ingredients are not registered as dependent")
}
fn reset_for_new_revision(&mut self) {
panic!("tracked field ingredients do not require reset")
}
fn fmt_index(
&self,
index: Option<crate::Id>,
fmt: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
write!(
fmt,
"{}.{}({:?})",
self.struct_debug_name,
self.field_debug_name,
index.unwrap()
)
}
}
impl<C> IngredientRequiresReset for TrackedFieldIngredient<C>
where
C: Configuration,
{
const RESET_ON_NEW_REVISION: bool = false;
}

View file

@ -9,6 +9,7 @@ edition = "2021"
derive-new = "0.5.9"
salsa = { path = "../../components/salsa-2022", package = "salsa-2022" }
ordered-float = "3.0"
test-log = { version = "0.2.15", features = ["trace"] }
[dev-dependencies]
expect-test = "1.4.0"
expect-test = "1.4.0"

View file

@ -38,6 +38,7 @@ impl Database {
// ANCHOR: db_impl
impl salsa::Database for Database {
fn salsa_event(&self, event: salsa::Event) {
eprintln!("Event: {event:?}");
// Log interesting events, if logging is enabled
if let Some(logs) = &self.logs {
// don't log boring events

View file

@ -5,6 +5,8 @@ use crate::ir::{
use derive_new::new;
#[cfg(test)]
use expect_test::expect;
#[cfg(test)]
use test_log::test;
// ANCHOR: parse_statements
#[salsa::tracked]

View file

@ -54,11 +54,3 @@ error[E0412]: cannot find type `tracked_fn_with_receiver_not_applied_to_impl_blo
|
2 | ...r, tracked_fn_with_one_input, tracked_fn_with_receiver_not_applied_to_impl_block);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
error[E0308]: mismatched types
--> tests/compile-fail/tracked_fn_incompatibles.rs:29:46
|
29 | fn tracked_fn_with_one_input(db: &dyn Db) -> u32 {
| ------------------------- ^^^ expected `u32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

View file

@ -31,5 +31,6 @@ fn ensure_init(place: *const <Database as HasJars>::Jars) {
// SAFETY: Intentionally tries to access potentially uninitialized memory,
// so that miri can catch if we accidentally forget to initialize the memory.
#[allow(clippy::forget_non_drop)]
forget(unsafe { addr_of!((*place).0).read() });
}

View file

@ -120,12 +120,9 @@ fn basic() {
"intermediate_result(MyInput(Id { value: 1 }))",
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: MyTracked(2) })",
"salsa_event(DidDiscard { key: MyTracked(2) })",
"salsa_event(DidDiscard { key: field(2) })",
"salsa_event(DidDiscard { key: contribution_from_struct(2) })",
"salsa_event(DidDiscard { key: MyTracked(5) })",
"salsa_event(DidDiscard { key: field(5) })",
"salsa_event(DidDiscard { key: copy_field(5) })",
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: field(2) })",
"final_result(MyInput(Id { value: 1 }))",
]"#]]);
}

View file

@ -106,9 +106,7 @@ fn basic() {
"intermediate_result(MyInput(Id { value: 1 }))",
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: MyTracked(2) })",
"salsa_event(DidDiscard { key: MyTracked(2) })",
"salsa_event(DidDiscard { key: field(2) })",
"salsa_event(DidDiscard { key: contribution_from_struct(2) })",
"salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: field(2) })",
"final_result(MyInput(Id { value: 1 }))",
]"#]]);
}

View file

@ -19,7 +19,7 @@ trait Db: salsa::DbWithJar<Jar> + HasLogger {}
struct HotPotato(u32);
thread_local! {
static N_POTATOES: AtomicUsize = AtomicUsize::new(0)
static N_POTATOES: AtomicUsize = const { AtomicUsize::new(0) }
}
impl HotPotato {

View file

@ -31,7 +31,7 @@ impl salsa::Database for Database {}
impl Db for Database {}
#[test]
#[should_panic(expected = "`execute` method for field")]
#[should_panic(expected = "access to tracked struct from previous revision")]
fn execute() {
let mut db = Database::default();

View file

@ -147,7 +147,6 @@ fn test_run_10() {
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
}
@ -171,7 +170,6 @@ fn test_run_20() {
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
]"#]]);
}
@ -209,7 +207,6 @@ fn test_run_0_then_5_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
@ -229,7 +226,6 @@ fn test_run_0_then_5_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }",
@ -237,7 +233,6 @@ fn test_run_0_then_5_then_20() {
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
@ -282,7 +277,6 @@ fn test_run_0_then_5_then_10_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
@ -302,7 +296,6 @@ fn test_run_0_then_5_then_10_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }",
@ -310,7 +303,6 @@ fn test_run_0_then_5_then_10_then_20() {
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: read_maybe_specified(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: final_result(0) } }",
]"#]]);
@ -324,15 +316,12 @@ fn test_run_0_then_5_then_10_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
@ -369,7 +358,6 @@ fn test_run_5_then_20() {
[
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: input(0) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }",
"create_tracked(MyInput(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }",
@ -377,7 +365,6 @@ fn test_run_5_then_20() {
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: maybe_specified(0) } }",
"maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }",
"read_maybe_specified(MyTracked(Id { value: 1 }))",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }",

View file

@ -0,0 +1,65 @@
//! Test a field whose `PartialEq` impl is always true.
//! This can our "last changed" data to be wrong
//! but we *should* always reflect the final values.
use test_log::test;
#[salsa::jar(db = Db)]
struct Jar(MyInput, MyTracked, the_fn);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::input]
struct MyInput {
field: bool,
}
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(Eq, Hash, Debug, Clone)]
struct BadEq {
field: bool,
}
impl PartialEq for BadEq {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl From<bool> for BadEq {
fn from(value: bool) -> Self {
Self { field: value }
}
}
#[salsa::tracked]
struct MyTracked {
#[id]
field: BadEq,
}
#[salsa::tracked]
fn the_fn(db: &dyn Db, input: MyInput) {
let tracked0 = MyTracked::new(db, BadEq::from(input.field(db)));
assert_eq!(tracked0.field(db).field, input.field(db));
}
#[salsa::db(Jar)]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
}
impl salsa::Database for Database {}
impl Db for Database {}
#[test]
fn execute() {
let mut db = Database::default();
let input = MyInput::new(&db, true);
the_fn(&db, input);
input.set_field(&mut db).to(false);
the_fn(&db, input);
}

View file

@ -0,0 +1,58 @@
//! Test a field whose `PartialEq` impl is always true.
//! This can our "last changed" data to be wrong
//! but we *should* always reflect the final values.
use test_log::test;
#[salsa::jar(db = Db)]
struct Jar(MyInput, MyTracked, the_fn);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::input]
struct MyInput {
field: bool,
}
#[derive(Hash, Debug, Clone)]
struct NotEq {
field: bool,
}
impl From<bool> for NotEq {
fn from(value: bool) -> Self {
Self { field: value }
}
}
#[salsa::tracked]
struct MyTracked {
#[no_eq]
field: NotEq,
}
#[salsa::tracked]
fn the_fn(db: &dyn Db, input: MyInput) {
let tracked0 = MyTracked::new(db, NotEq::from(input.field(db)));
assert_eq!(tracked0.field(db).field, input.field(db));
}
#[salsa::db(Jar)]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
}
impl salsa::Database for Database {}
impl Db for Database {}
#[test]
fn execute() {
let mut db = Database::default();
let input = MyInput::new(&db, true);
the_fn(&db, input);
input.set_field(&mut db).to(false);
the_fn(&db, input);
}

View file

@ -0,0 +1,67 @@
//! Test for a tracked struct where the id field has a
//! very poorly chosen hash impl (always returns 0).
//! This demonstrates that the `#[id]` fields on a struct
//! can change values and yet the struct can have the same
//! id (because struct ids are based on the *hash* of the
//! `#[id]` fields).
use test_log::test;
#[salsa::jar(db = Db)]
struct Jar(MyInput, MyTracked, the_fn);
trait Db: salsa::DbWithJar<Jar> {}
#[salsa::input]
struct MyInput {
field: bool,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)]
struct BadHash {
field: bool,
}
impl From<bool> for BadHash {
fn from(value: bool) -> Self {
Self { field: value }
}
}
impl std::hash::Hash for BadHash {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_i16(0);
}
}
#[salsa::tracked]
struct MyTracked {
#[id]
field: BadHash,
}
#[salsa::tracked]
fn the_fn(db: &dyn Db, input: MyInput) {
let tracked0 = MyTracked::new(db, BadHash::from(input.field(db)));
assert_eq!(tracked0.field(db).field, input.field(db));
}
#[salsa::db(Jar)]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
}
impl salsa::Database for Database {}
impl Db for Database {}
#[test]
fn execute() {
let mut db = Database::default();
let input = MyInput::new(&db, true);
the_fn(&db, input);
input.set_field(&mut db).to(false);
the_fn(&db, input);
}

View file

@ -618,7 +618,7 @@ impl ActiveQuery {
fn remove_cycle_participants(&mut self, cycle: &Cycle) {
if let Some(my_dependencies) = &mut self.dependencies {
for p in cycle.participant_keys() {
my_dependencies.remove(&p);
my_dependencies.shift_remove(&p);
}
}
}

View file

@ -17,7 +17,7 @@ trait LruPeek {
struct HotPotato(u32);
thread_local! {
static N_POTATOES: AtomicUsize = AtomicUsize::new(0)
static N_POTATOES: AtomicUsize = const { AtomicUsize::new(0) }
}
impl HotPotato {

View file

@ -57,18 +57,13 @@ impl<T> WithValue<T> for Cell<T> {
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum CancellationFlag {
#[default]
Down,
Panic,
}
impl Default for CancellationFlag {
fn default() -> CancellationFlag {
CancellationFlag::Down
}
}
/// Various "knobs" that can be used to customize how the queries
/// behave on one specific thread. Note that this state is
/// intentionally thread-local (apart from `signal`).