From 225a81ae8ffd03087ba050636486a72919134889 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 8 Apr 2024 05:49:46 -0400 Subject: [PATCH 01/61] update docs to mention durability --- book/src/SUMMARY.md | 1 + book/src/overview.md | 12 ++++++++---- book/src/reference/durability.md | 13 +++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 book/src/reference/durability.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index a4fdeee9e..2818279a7 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -16,6 +16,7 @@ - [Defining the checker](./tutorial/checker.md) - [Defining the interpreter](./tutorial/interpreter.md) - [Reference](./reference.md) + - [Durability](./reference/durability.md) - [Algorithm](./reference/algorithm.md) - [Common patterns](./common_patterns.md) - [Selection](./common_patterns/selection.md) diff --git a/book/src/overview.md b/book/src/overview.md index 95638ce9c..2909aed5d 100644 --- a/book/src/overview.md +++ b/book/src/overview.md @@ -114,9 +114,12 @@ Finally, you can also modify the value of an input field by using the setter met Since this is modifying the input, the setter takes an `&mut`-reference to the database: ```rust -file.set_contents(&mut db, String::from("fn foo() { /* add a comment */ }")); +file.set_contents(&mut db).to(String::from("fn foo() { /* add a comment */ }")); ``` +Note that the setter method `set_contents` returns a "builder". +This gives the ability to set the [durability](./reference/durability.md) and other advanced concepts. + ## Tracked functions Once you've defined your inputs, the next thing to define are **tracked functions**: @@ -147,12 +150,13 @@ Tracked functions can return any clone-able type. A clone is required since, whe **Tracked structs** are intermediate structs created during your computation. Like inputs, their fields are stored inside the database, and the struct itself just wraps an id. -Unlike inputs, they can only be created inside a tracked function, and their fields can never change once they are created. -Getter methods are provided to read the fields, but there are no setter methods[^specify]. Example: +Unlike inputs, they can only be created inside a tracked function, and their fields can never change once they are created (until the next revision, at least). +Getter methods are provided to read the fields, but there are no setter methods. +Example: ```rust #[salsa::tracked] -struct Ast { +struct Ast<'db> { #[return_ref] top_level_items: Vec, } diff --git a/book/src/reference/durability.md b/book/src/reference/durability.md new file mode 100644 index 000000000..80c34e3b4 --- /dev/null +++ b/book/src/reference/durability.md @@ -0,0 +1,13 @@ +# Durability + +"Durability" is an optimization that can greatly improve the performance of your salsa programs. +Durability specifies the probably that an input's value will change. +The default is "low durability". +But when you set the value of an input, you can manually specify a higher durability, +typically `Durability::HIGH`. +Salsa tracks when tracked functions only consume values of high durability +and, if no high durability input has changed, it can skip traversing their +dependencies. + +Typically "high durability" values are things like data read from the standard library +or other inputs that aren't actively being edited by the end user. \ No newline at end of file From 4533cd9e4b18934aef61daf86856cb3031926ef5 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 10 Apr 2024 06:34:36 -0400 Subject: [PATCH 02/61] adopt the Salsa 3.0 `Update`` trait Right now, this doesn't change much except the behavior in the event that `Eq` is not properly implemented. In the future, it will enable the use of references and slices and things. --- .../salsa-2022-macros/src/tracked_struct.rs | 28 +- components/salsa-2022/src/lib.rs | 1 + components/salsa-2022/src/tracked_struct.rs | 54 ++-- components/salsa-2022/src/update.rs | 242 ++++++++++++++++++ ...q.rs => tracked-struct-id-field-bad-eq.rs} | 4 +- .../tracked-struct-value-field-bad-eq.rs | 122 +++++++++ ...s => tracked-struct-value-field-not-eq.rs} | 0 7 files changed, 423 insertions(+), 28 deletions(-) create mode 100644 components/salsa-2022/src/update.rs rename salsa-2022-tests/tests/{tracked-struct-field-bad-eq.rs => tracked-struct-id-field-bad-eq.rs} (87%) create mode 100644 salsa-2022-tests/tests/tracked-struct-value-field-bad-eq.rs rename salsa-2022-tests/tests/{tracked-struct-field-not-eq.rs => tracked-struct-value-field-not-eq.rs} (100%) diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 85afbbe04..5ec21af9a 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -100,24 +100,33 @@ impl TrackedStruct { // 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 old_fields = syn::Ident::new("old_fields_", Span::call_site()); + let new_fields = syn::Ident::new("new_fields_", 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 + let update_fields: TokenStream = self .all_fields() .zip(0..) .map(|(field, i)| { + let field_ty = field.ty(); 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 { + if salsa::update::helper::Dispatch::<#field_ty>::maybe_update( + std::ptr::addr_of_mut!((*#old_fields).#field_index), + #new_fields.#field_index, + ) { #revisions[#field_index] = #current_revision; } } } else { quote_spanned! { field.span() => - #revisions[#field_index] = #current_revision; + salsa::update::always_update( + &mut #revisions[#field_index], + #current_revision, + unsafe { &mut (*#old_fields).#field_index }, + #new_fields.#field_index, + ); } } }) @@ -142,13 +151,14 @@ impl TrackedStruct { [current_revision; #arity] } - fn update_revisions( + unsafe fn update_fields( #current_revision: salsa::Revision, - #old_value: &Self::Fields, - #new_value: &Self::Fields, #revisions: &mut Self::Revisions, + #old_fields: *mut Self::Fields, + #new_fields: Self::Fields, ) { - #update_revisions + use salsa::update::helper::Fallback as _; + #update_fields } } } diff --git a/components/salsa-2022/src/lib.rs b/components/salsa-2022/src/lib.rs index 2a96a9c84..b21b47d21 100644 --- a/components/salsa-2022/src/lib.rs +++ b/components/salsa-2022/src/lib.rs @@ -23,6 +23,7 @@ pub mod salsa_struct; pub mod setter; pub mod storage; pub mod tracked_struct; +pub mod update; pub use self::cancelled::Cancelled; pub use self::cycle::Cycle; diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 189e5c5ce..012dd11da 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -47,16 +47,32 @@ pub trait Configuration { /// 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( + /// Update the field data and, if the value has changed, + /// the appropriate entry in the `revisions` array. + /// + /// # Safety requirements and conditions + /// + /// Requires the same conditions as the `maybe_update` + /// method on [the `Update` trait](`crate::update::Update`). + /// + /// In short, requires that `old_fields` be a pointer into + /// storage from a previous revision. + /// It must meet its validity invariant. + /// Owned content must meet safety invariant. + /// `*mut` here is not strictly needed; + /// it is used to signal that the content + /// is not guaranteed to recursively meet + /// its safety invariant and + /// hence this must be dereferenced with caution. + /// + /// Ensures that `old_fields` is fully updated and valid + /// after it returns and that `revisions` has been updated + /// for any field that changed. + unsafe fn update_fields( current_revision: Revision, - old_value: &Self::Fields, - new_value: &Self::Fields, revisions: &mut Self::Revisions, + old_fields: *mut Self::Fields, + new_fields: Self::Fields, ); } // ANCHOR_END: Configuration @@ -210,19 +226,25 @@ where } else { let mut data = self.entity_data.get_mut(&id).unwrap(); let data = &mut *data; + + // SAFETY: We assert that the pointer to `data.revisions` + // is a pointer into the database referencing a value + // from a previous revision. As such, it continues to meet + // its validity invariant and any owned content also continues + // to meet its safety invariant. + unsafe { + C::update_fields( + current_revision, + &mut data.revisions, + std::ptr::addr_of_mut!(data.fields), + fields, + ); + } 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 diff --git a/components/salsa-2022/src/update.rs b/components/salsa-2022/src/update.rs new file mode 100644 index 000000000..ad5b6d3e2 --- /dev/null +++ b/components/salsa-2022/src/update.rs @@ -0,0 +1,242 @@ +use std::path::PathBuf; + +use crate::Revision; + +/// This is used by the macro generated code. +/// If possible, uses `Update` trait, but else requires `'static`. +/// +/// To use: +/// +/// ```rust,ignore +/// use crate::update::helper::Fallback; +/// update::helper::Dispatch::<$ty>::maybe_update(pointer, new_value); +/// ``` +/// +/// It is important that you specify the `$ty` explicitly. +/// +/// This uses the ["method dispatch hack"](https://github.com/nvzqz/impls#how-it-works) +/// to use the `Update` trait if it is available and else fallback to `'static`. +pub mod helper { + use std::marker::PhantomData; + + use super::{update_fallback, Update}; + + pub struct Dispatch(PhantomData); + + impl Dispatch { + pub fn new() -> Self { + Dispatch(PhantomData) + } + } + + impl Dispatch + where + D: Update, + { + pub unsafe fn maybe_update(old_pointer: *mut D, new_value: D) -> bool { + unsafe { D::maybe_update(old_pointer, new_value) } + } + } + + pub unsafe trait Fallback { + /// Same safety conditions as `Update::maybe_update` + unsafe fn maybe_update(old_pointer: *mut T, new_value: T) -> bool; + } + + unsafe impl Fallback for Dispatch { + unsafe fn maybe_update(old_pointer: *mut T, new_value: T) -> bool { + unsafe { update_fallback(old_pointer, new_value) } + } + } +} + +/// "Fallback" for maybe-update that is suitable for fully owned T +/// that implement `Eq`. In this version, we update only if the new value +/// is not `Eq` to the old one. Note that given `Eq` impls that are not just +/// structurally comparing fields, this may cause us not to update even if +/// the value has changed (presumably because this change is not semantically +/// significant). +/// +/// # Safety +/// +/// See `Update::maybe_update` +pub unsafe fn update_fallback(old_pointer: *mut T, new_value: T) -> bool +where + T: 'static + PartialEq, +{ + // Because everything is owned, this ref is simply a valid `&mut` + let old_ref: &mut T = unsafe { &mut *old_pointer }; + + if *old_ref != new_value { + *old_ref = new_value; + true + } else { + // Subtle but important: Eq impls can be buggy or define equality + // in surprising ways. If it says that the value has not changed, + // we do not modify the existing value, and thus do not have to + // update the revision, as downstream code will not see the new value. + false + } +} + +/// Helper for generated code. Updates `*old_pointer` with `new_value` +/// and updates `*old_revision` with `new_revision.` Used for fields +/// tagged with `#[no_eq]` +pub fn always_update( + old_revision: &mut Revision, + new_revision: Revision, + old_pointer: &mut T, + new_value: T, +) where + T: 'static, +{ + *old_revision = new_revision; + *old_pointer = new_value; +} + +/// The `unsafe` on the trait is to assert that `maybe_update` ensures +/// the properties it is intended to ensure. +pub unsafe trait Update { + /// # Returns + /// + /// True if the value should be considered to have changed in the new revision. + /// + /// # Unsafe contract + /// + /// ## Requires + /// + /// Informally, requires that `old_value` points to a value in the + /// database that is potentially from a previous revision and `new_value` + /// points to a value produced in this revision. + /// + /// More formally, requires that + /// + /// * all parameters meet the [validity and safety invariants][i] for their type + /// * `old_value` further points to allocated memory that meets the [validity invariant][i] for `Self` + /// * all data *owned* by `old_value` further meets its safety invariant + /// * not that borrowed data in `old_value` only meets its validity invariant + /// and hence cannot be dereferenced; essentially, a `&T` may point to memory + /// in the database which has been modified or even freed in the newer revision. + /// + /// [i]: https://www.ralfj.de/blog/2018/08/22/two-kinds-of-invariants.html + /// + /// ## Ensures + /// + /// That `old_value` is updated with + unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool; +} + +unsafe impl Update for &T { + unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { + let old_value: *const T = unsafe { *old_pointer }; + if old_value != (new_value as *const T) { + unsafe { + *old_pointer = new_value; + } + true + } else { + false + } + } +} + +unsafe impl Update for Vec +where + T: Update, +{ + unsafe fn maybe_update(old_pointer: *mut Self, new_vec: Self) -> bool { + let old_vec: &mut Vec = unsafe { &mut *old_pointer }; + + if old_vec.len() != new_vec.len() { + old_vec.clear(); + old_vec.extend(new_vec); + return true; + } + + let mut changed = false; + for (old_element, new_element) in old_vec.iter_mut().zip(new_vec) { + changed |= T::maybe_update(old_element, new_element); + } + + changed + } +} + +unsafe impl Update for [T; N] +where + T: Update, +{ + unsafe fn maybe_update(old_pointer: *mut Self, new_vec: Self) -> bool { + let old_pointer: *mut T = std::ptr::addr_of_mut!((*old_pointer)[0]); + let mut changed = false; + for (new_element, i) in new_vec.into_iter().zip(0..) { + changed |= T::maybe_update(old_pointer.add(i), new_element); + } + changed + } +} + +macro_rules! fallback_impl { + ($($t:ty,)*) => { + $( + unsafe impl Update for $t { + unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { + update_fallback(old_pointer, new_value) + } + } + )* + } +} + +fallback_impl! { + String, + i64, + u64, + i32, + u32, + i16, + u16, + i8, + u8, + bool, + f32, + f64, + usize, + isize, + PathBuf, +} + +macro_rules! tuple_impl { + ($($t:ident),*; $($u:ident),*) => { + unsafe impl<$($t),*> Update for ($($t,)*) + where + $($t: Update,)* + { + #[allow(non_snake_case)] + unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { + let ($($t,)*) = new_value; + let ($($u,)*) = unsafe { &mut *old_pointer }; + + let mut changed = false; + $( + unsafe { changed |= Update::maybe_update($u, $t); } + )* + changed + } + } + } +} + +// Create implementations for tuples up to arity 12 +tuple_impl!(A; a); +tuple_impl!(A, B; a, b); +tuple_impl!(A, B, C; a, b, c); +tuple_impl!(A, B, C, D; a, b, c, d); +tuple_impl!(A, B, C, D, E; a, b, c, d, e); +tuple_impl!(A, B, C, D, E, F; a, b, c, d, e, f); +tuple_impl!(A, B, C, D, E, F, G; a, b, c, d, e, f, g); +tuple_impl!(A, B, C, D, E, F, G, H; a, b, c, d, e, f, g, h); +tuple_impl!(A, B, C, D, E, F, G, H, I; a, b, c, d, e, f, g, h, i); +tuple_impl!(A, B, C, D, E, F, G, H, I, J; a, b, c, d, e, f, g, h, i, j); +tuple_impl!(A, B, C, D, E, F, G, H, I, J, K; a, b, c, d, e, f, g, h, i, j, k); +tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L; a, b, c, d, e, f, g, h, i, j, k, l); diff --git a/salsa-2022-tests/tests/tracked-struct-field-bad-eq.rs b/salsa-2022-tests/tests/tracked-struct-id-field-bad-eq.rs similarity index 87% rename from salsa-2022-tests/tests/tracked-struct-field-bad-eq.rs rename to salsa-2022-tests/tests/tracked-struct-id-field-bad-eq.rs index bd487a8fb..fd9c38a28 100644 --- a/salsa-2022-tests/tests/tracked-struct-field-bad-eq.rs +++ b/salsa-2022-tests/tests/tracked-struct-id-field-bad-eq.rs @@ -1,6 +1,4 @@ -//! 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. +//! Test an id field whose `PartialEq` impl is always true. use test_log::test; diff --git a/salsa-2022-tests/tests/tracked-struct-value-field-bad-eq.rs b/salsa-2022-tests/tests/tracked-struct-value-field-bad-eq.rs new file mode 100644 index 000000000..94651b0f9 --- /dev/null +++ b/salsa-2022-tests/tests/tracked-struct-value-field-bad-eq.rs @@ -0,0 +1,122 @@ +//! Test a field whose `PartialEq` impl is always true. +//! This can result in us getting different results than +//! if we were to execute from scratch. + +use expect_test::expect; +use salsa::DebugWithDb; +use salsa_2022_tests::{HasLogger, Logger}; +use test_log::test; + +#[salsa::jar(db = Db)] +struct Jar( + MyInput, + MyTracked, + the_fn, + make_tracked_struct, + read_tracked_struct, +); + +trait Db: salsa::DbWithJar {} + +#[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 for BadEq { + fn from(value: bool) -> Self { + Self { field: value } + } +} + +#[salsa::tracked] +struct MyTracked { + field: BadEq, +} + +#[salsa::tracked] +fn the_fn(db: &dyn Db, input: MyInput) -> bool { + let tracked = make_tracked_struct(db, input); + read_tracked_struct(db, tracked) +} + +#[salsa::tracked] +fn make_tracked_struct(db: &dyn Db, input: MyInput) -> MyTracked { + MyTracked::new(db, BadEq::from(input.field(db))) +} + +#[salsa::tracked] +fn read_tracked_struct(db: &dyn Db, tracked: MyTracked) -> bool { + tracked.field(db).field +} + +#[salsa::db(Jar)] +#[derive(Default)] +struct Database { + storage: salsa::Storage, + logger: Logger, +} + +impl salsa::Database for Database { + fn salsa_event(&self, event: salsa::Event) { + match event.kind { + salsa::EventKind::WillExecute { .. } + | salsa::EventKind::DidValidateMemoizedValue { .. } => { + self.push_log(format!("salsa_event({:?})", event.kind.debug(self))); + } + _ => {} + } + } +} + +impl Db for Database {} + +impl HasLogger for Database { + fn logger(&self) -> &Logger { + &self.logger + } +} + +#[test] +fn execute() { + let mut db = Database::default(); + + let input = MyInput::new(&db, true); + let result = the_fn(&db, input); + assert!(result); + + db.assert_logs(expect![[r#" + [ + "salsa_event(WillExecute { database_key: the_fn(0) })", + "salsa_event(WillExecute { database_key: make_tracked_struct(0) })", + "salsa_event(WillExecute { database_key: read_tracked_struct(0) })", + ]"#]]); + + // Update the input to `false` and re-execute. + input.set_field(&mut db).to(false); + let result = the_fn(&db, input); + + // If the `Eq` impl were working properly, we would + // now return `false`. But because the `Eq` is considered + // equal we re-use memoized results and so we get true. + assert!(result); + + db.assert_logs(expect![[r#" + [ + "salsa_event(WillExecute { database_key: make_tracked_struct(0) })", + "salsa_event(DidValidateMemoizedValue { database_key: read_tracked_struct(0) })", + "salsa_event(DidValidateMemoizedValue { database_key: the_fn(0) })", + ]"#]]); +} diff --git a/salsa-2022-tests/tests/tracked-struct-field-not-eq.rs b/salsa-2022-tests/tests/tracked-struct-value-field-not-eq.rs similarity index 100% rename from salsa-2022-tests/tests/tracked-struct-field-not-eq.rs rename to salsa-2022-tests/tests/tracked-struct-value-field-not-eq.rs From e24ace24eb339d0d8c1597ac0cf9f7e4973374fe Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 12 Apr 2024 05:12:38 -0400 Subject: [PATCH 03/61] return `&TrackedStructValue` from `new_struct` This is a step towards the goal of keep a pointer in the structs themselves. --- .../salsa-2022-macros/src/tracked_struct.rs | 22 ++++++++- components/salsa-2022/src/tracked_struct.rs | 49 ++++++++++++++----- components/salsa-2022/src/update.rs | 14 ------ 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 5ec21af9a..48022c9ae 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -57,6 +57,7 @@ impl TrackedStruct { 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 update_impl = self.update_impl(); let as_id_impl = self.as_id_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); Ok(quote! { @@ -67,6 +68,7 @@ impl TrackedStruct { #ingredients_for_impl #salsa_struct_in_db_impl #tracked_struct_in_db_impl + #update_impl #as_id_impl #as_debug_with_db_impl }) @@ -210,11 +212,11 @@ impl TrackedStruct { { 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.0.new_struct( + let __data = __ingredients.0.new_struct( __runtime, (#(#field_names,)*), ); - __id + __data.id() } #(#field_getters)* @@ -332,6 +334,22 @@ impl TrackedStruct { } } + /// Implementation of `Update`. + fn update_impl(&self) -> syn::ItemImpl { + let ident = self.id_ident(); + parse_quote! { + unsafe impl salsa::update::Update for #ident { + unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { + if unsafe { *old_pointer } != new_value { + unsafe { *old_pointer = new_value }; + true + } else { + false + } + } + } + } + } /// The index of the tracked struct ingredient in the ingredient tuple. fn tracked_struct_ingredient_index(&self) -> Literal { Literal::usize_unsuffixed(0) diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 012dd11da..322147169 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -10,6 +10,7 @@ use crate::{ ingredient_list::IngredientList, interned::{InternedId, InternedIngredient}, key::{DatabaseKeyIndex, DependencyIndex}, + plumbing::transmute_lifetime, runtime::{local_state::QueryOrigin, Runtime}, salsa_struct::SalsaStructInDb, Database, Durability, Event, IngredientIndex, Revision, @@ -125,10 +126,13 @@ struct TrackedStructKey { // ANCHOR: TrackedStructValue #[derive(Debug)] -struct TrackedStructValue +pub struct TrackedStructValue where C: Configuration, { + /// The id of this struct in the ingredient. + id: C::Id, + /// 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 @@ -155,6 +159,16 @@ where } // ANCHOR_END: TrackedStructValue +impl TrackedStructValue +where + C: Configuration, +{ + /// The id of this struct in the ingredient. + pub fn id(&self) -> C::Id { + self.id + } +} + #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] pub struct Disambiguator(pub u32); @@ -194,7 +208,7 @@ where } } - pub fn new_struct(&self, runtime: &Runtime, fields: C::Fields) -> C::Id { + pub fn new_struct(&self, runtime: &Runtime, fields: C::Fields) -> &TrackedStructValue { let data_hash = crate::hash::hash(&C::id_fields(&fields)); let (query_key, current_deps, disambiguator) = runtime.disambiguate_entity( @@ -211,22 +225,29 @@ where let (id, new_id) = self.interned.intern_full(runtime, entity_key); runtime.add_output(self.database_key_index(id).into()); + let pointer: *const TrackedStructValue; let current_revision = runtime.current_revision(); if new_id { - let old_value = self.entity_data.insert( + let data = Box::new(TrackedStructValue { id, - Box::new(TrackedStructValue { - created_at: current_revision, - durability: current_deps.durability, - fields, - revisions: C::new_revisions(current_deps.changed_at), - }), - ); + created_at: current_revision, + durability: current_deps.durability, + fields, + revisions: C::new_revisions(current_deps.changed_at), + }); + + // Keep a pointer into the box for later + pointer = &*data; + + let old_value = self.entity_data.insert(id, data); assert!(old_value.is_none()); } else { let mut data = self.entity_data.get_mut(&id).unwrap(); let data = &mut *data; + // Keep a pointer into the box for later + pointer = &**data; + // SAFETY: We assert that the pointer to `data.revisions` // is a pointer into the database referencing a value // from a previous revision. As such, it continues to meet @@ -247,7 +268,13 @@ where data.durability = current_deps.durability; } - id + // Unsafety clause: + // + // * The box is owned by self and, although the box has been moved, + // the pointer is to the contents of the box, which have a stable + // address. + // * Values are only removed or altered when we have `&mut self`. + unsafe { transmute_lifetime(self, &*pointer) } } /// Deletes the given entities. This is used after a query `Q` executes and we can compare diff --git a/components/salsa-2022/src/update.rs b/components/salsa-2022/src/update.rs index ad5b6d3e2..4a3d369af 100644 --- a/components/salsa-2022/src/update.rs +++ b/components/salsa-2022/src/update.rs @@ -126,20 +126,6 @@ pub unsafe trait Update { unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool; } -unsafe impl Update for &T { - unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { - let old_value: *const T = unsafe { *old_pointer }; - if old_value != (new_value as *const T) { - unsafe { - *old_pointer = new_value; - } - true - } else { - false - } - } -} - unsafe impl Update for Vec where T: Update, From a32078153b14c5008fa20b91828d7c1e4821caf7 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 13 Apr 2024 06:43:52 -0400 Subject: [PATCH 04/61] separate marking the outputs as verified There are 3 call-sites to this function: * One of them has already marked the outputs * One of them has no outputs * The third does need to mark the outputs --- components/salsa-2022/src/function/maybe_changed_after.rs | 4 +++- components/salsa-2022/src/function/memo.rs | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/components/salsa-2022/src/function/maybe_changed_after.rs b/components/salsa-2022/src/function/maybe_changed_after.rs index f8f9d8fd3..3b4304140 100644 --- a/components/salsa-2022/src/function/maybe_changed_after.rs +++ b/components/salsa-2022/src/function/maybe_changed_after.rs @@ -127,7 +127,9 @@ where if memo.check_durability(runtime) { // No input of the suitable durability has changed since last verified. - memo.mark_as_verified(db.as_salsa_database(), runtime, database_key_index); + let db = db.as_salsa_database(); + memo.mark_as_verified(db, runtime, database_key_index); + memo.mark_outputs_as_verified(db, database_key_index); return true; } diff --git a/components/salsa-2022/src/function/memo.rs b/components/salsa-2022/src/function/memo.rs index 7c77ce8c2..200029499 100644 --- a/components/salsa-2022/src/function/memo.rs +++ b/components/salsa-2022/src/function/memo.rs @@ -126,8 +126,13 @@ impl Memo { }); self.verified_at.store(runtime.current_revision()); + } - // Also mark the outputs as verified + pub(super) fn mark_outputs_as_verified( + &self, + db: &dyn crate::Database, + database_key_index: DatabaseKeyIndex, + ) { for output in self.revisions.origin.outputs() { db.mark_validated_output(database_key_index, output); } From 20cb307301288434a270485471d5ecb8ac722e3f Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 13 Apr 2024 06:44:33 -0400 Subject: [PATCH 05/61] give trait more info about lifetime relationships In particular, the ingredient and the database have the same lifetime. This will be useful later for safety conditions. --- components/salsa-2022/src/ingredient.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/components/salsa-2022/src/ingredient.rs b/components/salsa-2022/src/ingredient.rs index dc8f2b908..9dfcaab43 100644 --- a/components/salsa-2022/src/ingredient.rs +++ b/components/salsa-2022/src/ingredient.rs @@ -26,7 +26,12 @@ pub trait Ingredient { fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy; /// Has the value for `input` in this ingredient changed after `revision`? - fn maybe_changed_after(&self, db: &DB, input: DependencyIndex, revision: Revision) -> bool; + fn maybe_changed_after<'db>( + &'db self, + db: &'db DB, + input: DependencyIndex, + revision: Revision, + ) -> bool; /// What were the inputs (if any) that were used to create the value at `key_index`. fn origin(&self, key_index: Id) -> Option; @@ -34,7 +39,12 @@ pub trait Ingredient { /// Invoked when the value `output_key` should be marked as valid in the current revision. /// This occurs because the value for `executor`, which generated it, was marked as valid /// in the current revision. - fn mark_validated_output(&self, db: &DB, executor: DatabaseKeyIndex, output_key: Option); + fn mark_validated_output<'db>( + &'db self, + db: &'db DB, + executor: DatabaseKeyIndex, + output_key: Option, + ); /// Invoked when the value `stale_output` was output by `executor` in a previous /// revision, but was NOT output in the current revision. From ea1d452143a011af76faadb4dbc16e83a7f6fbf1 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 13 Apr 2024 07:34:25 -0400 Subject: [PATCH 06/61] create a `struct_map` that encapsulates access The internal API is now based around providing references to the `TrackedStructValue`. Documenting the invariants led to one interesting case, which is that we sometimes verify a tracked struct as not having changed (and even create `&`-ref to it!) but then re-execute the function around it. We now guarantee that, in this case, the data does not change, even if it has leaked values. This is required to ensure soundness. Add a test case about it. --- components/salsa-2022/src/tracked_struct.rs | 154 +++++------ .../src/tracked_struct/struct_map.rs | 259 ++++++++++++++++++ .../src/tracked_struct/tracked_field.rs | 47 +--- .../preverify-struct-with-leaked-data.rs | 103 +++++++ 4 files changed, 448 insertions(+), 115 deletions(-) create mode 100644 components/salsa-2022/src/tracked_struct/struct_map.rs create mode 100644 salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 322147169..0a5480601 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -1,23 +1,21 @@ -use std::{fmt, hash::Hash, sync::Arc}; - -use crossbeam::queue::SegQueue; +use std::{fmt, hash::Hash}; use crate::{ cycle::CycleRecoveryStrategy, - hash::FxDashMap, id::AsId, ingredient::{fmt_index, Ingredient, IngredientRequiresReset}, ingredient_list::IngredientList, interned::{InternedId, InternedIngredient}, key::{DatabaseKeyIndex, DependencyIndex}, - plumbing::transmute_lifetime, runtime::{local_state::QueryOrigin, Runtime}, salsa_struct::SalsaStructInDb, Database, Durability, Event, IngredientIndex, Revision, }; +use self::struct_map::{StructMap, Update}; pub use self::tracked_field::TrackedFieldIngredient; +mod struct_map; mod tracked_field; // ANCHOR: Configuration @@ -97,7 +95,7 @@ where { interned: InternedIngredient, - entity_data: Arc>>>, + struct_map: struct_map::StructMap, /// A list of each tracked function `f` whose key is this /// tracked struct. @@ -107,13 +105,6 @@ 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>>, - debug_name: &'static str, } @@ -159,16 +150,6 @@ where } // ANCHOR_END: TrackedStructValue -impl TrackedStructValue -where - C: Configuration, -{ - /// The id of this struct in the ingredient. - pub fn id(&self) -> C::Id { - self.id - } -} - #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] pub struct Disambiguator(pub u32); @@ -179,9 +160,8 @@ where pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self { Self { interned: InternedIngredient::new(index, debug_name), - entity_data: Default::default(), + struct_map: StructMap::new(), dependent_fns: IngredientList::new(), - deleted_entries: SegQueue::default(), debug_name, } } @@ -195,7 +175,7 @@ where TrackedFieldIngredient { ingredient_index: field_ingredient_index, field_index, - entity_data: self.entity_data.clone(), + struct_map: self.struct_map.view(), struct_debug_name: self.debug_name, field_debug_name, } @@ -208,7 +188,11 @@ where } } - pub fn new_struct(&self, runtime: &Runtime, fields: C::Fields) -> &TrackedStructValue { + pub fn new_struct<'db>( + &'db self, + runtime: &'db Runtime, + fields: C::Fields, + ) -> &'db TrackedStructValue { let data_hash = crate::hash::hash(&C::id_fields(&fields)); let (query_key, current_deps, disambiguator) = runtime.disambiguate_entity( @@ -225,56 +209,56 @@ where let (id, new_id) = self.interned.intern_full(runtime, entity_key); runtime.add_output(self.database_key_index(id).into()); - let pointer: *const TrackedStructValue; let current_revision = runtime.current_revision(); if new_id { - let data = Box::new(TrackedStructValue { - id, - created_at: current_revision, - durability: current_deps.durability, - fields, - revisions: C::new_revisions(current_deps.changed_at), - }); - - // Keep a pointer into the box for later - pointer = &*data; - - let old_value = self.entity_data.insert(id, data); - assert!(old_value.is_none()); - } else { - let mut data = self.entity_data.get_mut(&id).unwrap(); - let data = &mut *data; - - // Keep a pointer into the box for later - pointer = &**data; - - // SAFETY: We assert that the pointer to `data.revisions` - // is a pointer into the database referencing a value - // from a previous revision. As such, it continues to meet - // its validity invariant and any owned content also continues - // to meet its safety invariant. - unsafe { - C::update_fields( - current_revision, - &mut data.revisions, - std::ptr::addr_of_mut!(data.fields), + self.struct_map.insert( + runtime, + TrackedStructValue { + id, + created_at: current_revision, + durability: current_deps.durability, fields, - ); - } - if current_deps.durability < data.durability { - data.revisions = C::new_revisions(current_revision); + revisions: C::new_revisions(current_deps.changed_at), + }, + ) + } else { + match self.struct_map.update(runtime, id) { + Update::Current(r) => { + // All inputs up to this point were previously + // observed to be green and this struct was already + // verified. Therefore, the durability ought not to have + // changed (nor the field values, but the user could've + // done something stupid, so we can't *assert* this is true). + assert!(r.durability == current_deps.durability); + + r + } + Update::Outdated(mut data_ref) => { + let data = &mut *data_ref; + + // SAFETY: We assert that the pointer to `data.revisions` + // is a pointer into the database referencing a value + // from a previous revision. As such, it continues to meet + // its validity invariant and any owned content also continues + // to meet its safety invariant. + unsafe { + C::update_fields( + current_revision, + &mut data.revisions, + std::ptr::addr_of_mut!(data.fields), + fields, + ); + } + if current_deps.durability < data.durability { + data.revisions = C::new_revisions(current_revision); + } + data.created_at = current_revision; + data.durability = current_deps.durability; + + data_ref.freeze() + } } - data.created_at = current_revision; - data.durability = current_deps.durability; } - - // Unsafety clause: - // - // * The box is owned by self and, although the box has been moved, - // the pointer is to the contents of the box, which have a stable - // address. - // * Values are only removed or altered when we have `&mut self`. - unsafe { transmute_lifetime(self, &*pointer) } } /// Deletes the given entities. This is used after a query `Q` executes and we can compare @@ -296,9 +280,7 @@ where }); self.interned.delete_index(id); - if let Some((_, data)) = self.entity_data.remove(&id) { - self.deleted_entries.push(data); - } + self.struct_map.delete(id); for dependent_fn in self.dependent_fns.iter() { db.salsa_struct_deleted(dependent_fn, id.as_id()); @@ -334,16 +316,16 @@ where None } - fn mark_validated_output( - &self, - db: &DB, + fn mark_validated_output<'db>( + &'db self, + db: &'db DB, _executor: DatabaseKeyIndex, output_key: Option, ) { + let runtime = db.runtime(); let output_key = output_key.unwrap(); let output_key: C::Id = ::from_id(output_key); - let mut entity = self.entity_data.get_mut(&output_key).unwrap(); - entity.created_at = db.runtime().current_revision(); + self.struct_map.validate(runtime, output_key); } fn remove_stale_output( @@ -362,7 +344,7 @@ where fn reset_for_new_revision(&mut self) { self.interned.clear_deleted_indices(); - std::mem::take(&mut self.deleted_entries); + self.struct_map.drop_deleted_entries(); } fn salsa_struct_deleted(&self, _db: &DB, _id: crate::Id) { @@ -380,3 +362,13 @@ where { const RESET_ON_NEW_REVISION: bool = true; } + +impl TrackedStructValue +where + C: Configuration, +{ + /// The id of this struct in the ingredient. + pub fn id(&self) -> C::Id { + self.id + } +} diff --git a/components/salsa-2022/src/tracked_struct/struct_map.rs b/components/salsa-2022/src/tracked_struct/struct_map.rs new file mode 100644 index 000000000..9c26a4600 --- /dev/null +++ b/components/salsa-2022/src/tracked_struct/struct_map.rs @@ -0,0 +1,259 @@ +use std::{ + ops::{Deref, DerefMut}, + sync::Arc, +}; + +use crossbeam::queue::SegQueue; +use dashmap::mapref::one::RefMut; + +use crate::{ + hash::{FxDashMap, FxHasher}, + plumbing::transmute_lifetime, + Runtime, +}; + +use super::{Configuration, TrackedStructValue}; + +pub(crate) struct StructMap +where + C: Configuration, +{ + map: Arc>>>, + + /// 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>>, +} + +pub(crate) struct StructMapView +where + C: Configuration, +{ + map: Arc>>>, +} + +/// Return value for [`StructMap`][]'s `update` method. +pub(crate) enum Update<'db, C> +where + C: Configuration, +{ + /// Indicates that the given struct has not yet been verified in this revision. + /// The [`UpdateRef`][] gives mutable access to the field contents so that + /// its fields can be compared and updated. + Outdated(UpdateRef<'db, C>), + + /// Indicates that we have already verified that all the inputs accessed prior + /// to this struct creation were up-to-date, and therefore the field contents + /// ought not to have changed (barring user error). Returns a shared reference + /// because caller cannot safely modify fields at this point. + Current(&'db TrackedStructValue), +} + +impl StructMap +where + C: Configuration, +{ + pub fn new() -> Self { + Self { + map: Arc::new(FxDashMap::default()), + deleted_entries: SegQueue::new(), + } + } + + /// Get a secondary view onto this struct-map that can be used to fetch entries. + pub fn view(&self) -> StructMapView { + StructMapView { + map: self.map.clone(), + } + } + + /// Insert the given tracked struct value into the map. + /// + /// # Panics + /// + /// * If value with same `value.id` is already present in the map. + /// * If value not created in current revision. + pub fn insert<'db>( + &'db self, + runtime: &'db Runtime, + value: TrackedStructValue, + ) -> &TrackedStructValue { + assert_eq!(value.created_at, runtime.current_revision()); + + let boxed_value = Box::new(value); + let pointer = std::ptr::addr_of!(*boxed_value); + + let old_value = self.map.insert(boxed_value.id, boxed_value); + assert!(old_value.is_none()); // ...strictly speaking we probably need to abort here + + // Unsafety clause: + // + // * The box is owned by self and, although the box has been moved, + // the pointer is to the contents of the box, which have a stable + // address. + // * Values are only removed or altered when we have `&mut self`. + unsafe { transmute_lifetime(self, &*pointer) } + } + + pub fn validate<'db>(&'db self, runtime: &'db Runtime, id: C::Id) { + let mut data = self.map.get_mut(&id).unwrap(); + + // Never update a struct twice in the same revision. + let current_revision = runtime.current_revision(); + assert!(data.created_at < current_revision); + data.created_at = current_revision; + } + + /// Get mutable access to the data for `id` -- this holds a write lock for the duration + /// of the returned value. + /// + /// # Panics + /// + /// * If the value is not present in the map. + /// * If the value is already updated in this revision. + pub fn update<'db>(&'db self, runtime: &'db Runtime, id: C::Id) -> Update<'db, C> { + let mut data = self.map.get_mut(&id).unwrap(); + + // Never update a struct twice in the same revision. + let current_revision = runtime.current_revision(); + + // Subtle: it's possible that this struct was already validated + // in this revision. What can happen (e.g., in the test + // `test_run_5_then_20` in `specify_tracked_fn_in_rev_1_but_not_2.rs`) + // is that + // + // * Revision 1: + // * Tracked function F creates tracked struct S + // * F reads input I + // + // In Revision 2, I is changed, and F is re-executed. + // We try to validate F's inputs/outputs, which is the list [output: S, input: I]. + // As no inputs have changed by the time we reach S, we mark it as verified. + // But then input I is seen to hvae changed, and so we re-execute F. + // Note that we *know* that S will have the same value (barring program bugs). + // + // Further complicating things: it is possible that F calls F2 + // and gives it (e.g.) S as one of its arguments. Validating F2 may cause F2 to + // re-execute which means that it may indeed have read from S's fields + // during the current revision and thus obtained an `&` reference to those fields + // that is still live. + // + // For this reason, we just return `None` in this case, ensuring that the calling + // code cannot violate that `&`-reference. + if data.created_at == current_revision { + drop(data); + return Update::Current(&Self::get_from_map(&self.map, runtime, id)); + } + + data.created_at = current_revision; + Update::Outdated(UpdateRef { guard: data }) + } + + /// Helper function, provides shared functionality for [`StructMapView`][] + /// + /// # Panics + /// + /// * If the value is not present in the map. + /// * If the value has not been updated in this revision. + fn get_from_map<'db>( + map: &'db FxDashMap>>, + runtime: &'db Runtime, + id: C::Id, + ) -> &'db TrackedStructValue { + let data = map.get(&id).unwrap(); + let data: &TrackedStructValue = &**data; + + // Before we drop the lock, check that the value has + // been updated in this revision. This is what allows us to return a `` + let current_revision = runtime.current_revision(); + let created_at = data.created_at; + assert!( + created_at == current_revision, + "access to tracked struct from previous revision" + ); + + // Unsafety clause: + // + // * Value will not be updated again in this revision, + // and revision will not change so long as runtime is shared + // * We only remove values from the map when we have `&mut self` + unsafe { transmute_lifetime(map, data) } + } + + /// Remove the entry for `id` from the map. + /// + /// NB. the data won't actually be freed until `drop_deleted_entries` is called. + pub fn delete(&self, id: C::Id) { + if let Some((_, data)) = self.map.remove(&id) { + self.deleted_entries.push(data); + } + } + + /// Drop all entries deleted until now. + pub fn drop_deleted_entries(&mut self) { + std::mem::take(&mut self.deleted_entries); + } +} + +impl StructMapView +where + C: Configuration, +{ + /// Get a pointer to the data for the given `id`. + /// + /// # Panics + /// + /// * If the value is not present in the map. + /// * If the value has not been updated in this revision. + pub fn get<'db>(&'db self, runtime: &'db Runtime, id: C::Id) -> &'db TrackedStructValue { + StructMap::get_from_map(&self.map, runtime, id) + } +} + +/// A mutable reference to the data for a single struct. +/// Can be "frozen" to yield an `&` that will remain valid +/// until the end of the revision. +pub(crate) struct UpdateRef<'db, C> +where + C: Configuration, +{ + guard: RefMut<'db, C::Id, Box>, FxHasher>, +} + +impl<'db, C> UpdateRef<'db, C> +where + C: Configuration, +{ + /// Finalize this update, freezing the value for the rest of the revision. + pub fn freeze(self) -> &'db TrackedStructValue { + // Unsafety clause: + // + // see `get` above + let data: &TrackedStructValue = &*self.guard; + let dummy: &'db () = &(); + unsafe { transmute_lifetime(dummy, data) } + } +} + +impl Deref for UpdateRef<'_, C> +where + C: Configuration, +{ + type Target = TrackedStructValue; + + fn deref(&self) -> &Self::Target { + &self.guard + } +} + +impl DerefMut for UpdateRef<'_, C> +where + C: Configuration, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.guard + } +} diff --git a/components/salsa-2022/src/tracked_struct/tracked_field.rs b/components/salsa-2022/src/tracked_struct/tracked_field.rs index 0568e819c..2ef4d8b70 100644 --- a/components/salsa-2022/src/tracked_struct/tracked_field.rs +++ b/components/salsa-2022/src/tracked_struct/tracked_field.rs @@ -1,16 +1,11 @@ -use std::sync::Arc; - use crate::{ - hash::FxDashMap, id::AsId, ingredient::{Ingredient, IngredientRequiresReset}, key::DependencyIndex, - plumbing::transmute_lifetime, - tracked_struct::TrackedStructValue, - IngredientIndex, Runtime, + Database, IngredientIndex, Runtime, }; -use super::Configuration; +use super::{struct_map::StructMapView, Configuration}; /// Created for each tracked struct. /// This ingredient only stores the "id" fields. @@ -27,7 +22,7 @@ where /// 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>>>, + pub(super) struct_map: StructMapView, pub(super) struct_debug_name: &'static str, pub(super) field_debug_name: &'static str, } @@ -40,16 +35,7 @@ where /// 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 data = self.struct_map.get(runtime, id); let changed_at = C::revision(&data.revisions, self.field_index); @@ -62,15 +48,13 @@ where changed_at, ); - // Unsafety clause: - // - // * Values are only removed or altered when we have `&mut self` - unsafe { transmute_lifetime(self, &data.fields) } + &data.fields } } impl Ingredient for TrackedFieldIngredient where + DB: Database, C: Configuration, { fn ingredient_index(&self) -> IngredientIndex { @@ -81,22 +65,17 @@ where crate::cycle::CycleRecoveryStrategy::Panic } - fn maybe_changed_after( - &self, - _db: &DB, + fn maybe_changed_after<'db>( + &'db self, + db: &'db DB, input: crate::key::DependencyIndex, revision: crate::Revision, ) -> bool { + let runtime = db.runtime(); let id = ::from_id(input.key_index.unwrap()); - 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:?}`"); - } - } + let data = self.struct_map.get(runtime, id); + let field_changed_at = C::revision(&data.revisions, self.field_index); + field_changed_at > revision } fn origin(&self, _key_index: crate::Id) -> Option { diff --git a/salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs b/salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs new file mode 100644 index 000000000..1add83d7b --- /dev/null +++ b/salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs @@ -0,0 +1,103 @@ +//! Test that a `tracked` fn on a `salsa::input` +//! compiles and executes successfully. + +use std::cell::Cell; + +use expect_test::expect; +use salsa::DebugWithDb; +use salsa_2022_tests::{HasLogger, Logger}; +use test_log::test; + +thread_local! { + static COUNTER: Cell = Cell::new(0); +} + +#[salsa::jar(db = Db)] +struct Jar(MyInput, MyTracked, function); + +trait Db: salsa::DbWithJar + HasLogger {} + +#[salsa::db(Jar)] +#[derive(Default)] +struct Database { + storage: salsa::Storage, + logger: Logger, +} + +impl salsa::Database for Database { + fn salsa_event(&self, event: salsa::Event) { + self.push_log(format!("{:?}", event.debug(self))); + } +} + +impl Db for Database {} + +impl HasLogger for Database { + fn logger(&self) -> &Logger { + &self.logger + } +} + +#[salsa::input] +struct MyInput { + field1: u32, + field2: u32, +} + +#[salsa::tracked] +struct MyTracked { + counter: usize, +} + +#[salsa::tracked] +fn function(db: &dyn Db, input: MyInput) -> usize { + // Read input 1 + let _field1 = input.field1(db); + + // **BAD:** Leak in the value of the counter non-deterministically + let counter = COUNTER.with(|c| c.get()); + + // Create the tracked struct, which (from salsa's POV), only depends on field1; + // but which actually depends on the leaked value. + let tracked = MyTracked::new(db, counter); + + // Read input 2. This will cause us to re-execute on revision 2. + let _field2 = input.field2(db); + + tracked.counter(db) +} + +#[test] +fn test_leaked_inputs_ignored() { + let mut db = Database::default(); + + let input = MyInput::new(&db, 10, 20); + let result_in_rev_1 = function(&db, input); + db.assert_logs(expect![[r#" + [ + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: function(0) } }", + ]"#]]); + + assert_eq!(result_in_rev_1, 0); + + // Modify field2 so that `function` is seen to have changed -- + // but only *after* the tracked struct is created. + input.set_field2(&mut db).to(30); + + // Also modify the thread-local counter + COUNTER.with(|c| c.set(100)); + + let result_in_rev_2 = function(&db, input); + db.assert_logs(expect![[r#" + [ + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", + "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: function(0) } }", + ]"#]]); + + // Because salsa did not see any way for the tracked + // struct to have changed, its field values will not have + // been updated, even though in theory they would have + // the leaked value from the counter. + assert_eq!(result_in_rev_2, 0); +} From 79d24e0ad7c635ff395fbe499e7d8855226be102 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 16 Apr 2024 11:33:49 -0400 Subject: [PATCH 07/61] allow (but don't test) lifetime parameters --- .../salsa-2022-macros/src/salsa_struct.rs | 89 ++++++++++++++++++- .../salsa-2022-macros/src/tracked_struct.rs | 35 +++++--- 2 files changed, 106 insertions(+), 18 deletions(-) diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index 001af7baf..b57b4e1e8 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -27,7 +27,10 @@ use crate::options::{AllowedOptions, Options}; use proc_macro2::{Ident, Span, TokenStream}; -use syn::spanned::Spanned; +use syn::{ + punctuated::Punctuated, spanned::Spanned, token::Comma, GenericParam, ImplGenerics, + TypeGenerics, WhereClause, +}; pub(crate) struct SalsaStruct { args: Options, @@ -144,6 +147,31 @@ impl SalsaStruct { &self.struct_item.ident } + /// Name of the struct the user gave plus: + /// + /// * its list of generic parameters + /// * the generics "split for impl". + pub(crate) fn id_ident_and_generics( + &self, + ) -> ( + &syn::Ident, + &Punctuated, + ImplGenerics<'_>, + TypeGenerics<'_>, + Option<&WhereClause>, + ) { + let ident = &self.struct_item.ident; + let (impl_generics, type_generics, where_clause) = + self.struct_item.generics.split_for_impl(); + ( + ident, + &self.struct_item.generics.params, + impl_generics, + type_generics, + where_clause, + ) + } + /// Type of the jar for this struct pub(crate) fn jar_ty(&self) -> syn::Type { self.args.jar_ty() @@ -173,7 +201,9 @@ impl SalsaStruct { } } - /// Generate `struct Foo(Id)` + /// Create a struct that wraps the id. + /// This is the struct the user will refernece, but only if there + /// are no lifetimes. pub(crate) fn id_struct(&self) -> syn::ItemStruct { let ident = self.id_ident(); let visibility = &self.struct_item.vis; @@ -195,6 +225,49 @@ impl SalsaStruct { } } + /// Create the struct that the user will reference. + /// If + pub(crate) fn id_or_ptr_struct( + &self, + config_ident: &syn::Ident, + ) -> syn::Result { + if self.struct_item.generics.params.is_empty() { + Ok(self.id_struct()) + } else { + let ident = self.id_ident(); + let visibility = &self.struct_item.vis; + + let generics = &self.struct_item.generics; + if generics.params.len() != 1 || generics.lifetimes().count() != 1 { + return Err(syn::Error::new_spanned( + &self.struct_item.generics, + "must have exactly one lifetime parameter", + )); + } + + let lifetime = generics.lifetimes().next().unwrap(); + + // Extract the attributes the user gave, but screen out derive, since we are adding our own, + // and the customize attribute that we use for our own purposes. + let attrs: Vec<_> = self + .struct_item + .attrs + .iter() + .filter(|attr| !attr.path.is_ident("derive")) + .filter(|attr| !attr.path.is_ident("customize")) + .collect(); + + Ok(parse_quote_spanned! { ident.span() => + #(#attrs)* + #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] + #visibility struct #ident #generics ( + *const salsa::tracked_struct::TrackedStructValue < #config_ident >, + std::marker::PhantomData < & #lifetime salsa::tracked_struct::TrackedStructValue < #config_ident > > + ); + }) + } + } + /// Generates the `struct FooData` struct (or enum). /// This type inherits all the attributes written by the user. /// @@ -233,8 +306,12 @@ impl SalsaStruct { /// Generate `impl salsa::AsId for Foo` pub(crate) fn as_id_impl(&self) -> syn::ItemImpl { let ident = self.id_ident(); + let (impl_generics, type_generics, where_clause) = + self.struct_item.generics.split_for_impl(); parse_quote_spanned! { ident.span() => - impl salsa::AsId for #ident { + impl #impl_generics salsa::AsId for #ident #type_generics + #where_clause + { fn as_id(self) -> salsa::Id { self.0 } @@ -254,6 +331,8 @@ impl SalsaStruct { } let ident = self.id_ident(); + let (impl_generics, type_generics, where_clause) = + self.struct_item.generics.split_for_impl(); let db_type = self.db_dyn_ty(); let ident_string = ident.to_string(); @@ -281,7 +360,9 @@ impl SalsaStruct { // `use ::salsa::debug::helper::Fallback` is needed for the fallback to `Debug` impl Some(parse_quote_spanned! {ident.span()=> - impl ::salsa::DebugWithDb<#db_type> for #ident { + impl #impl_generics ::salsa::DebugWithDb<#db_type> for #ident #type_generics + #where_clause + { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>, _db: &#db_type) -> ::std::fmt::Result { #[allow(unused_imports)] use ::salsa::debug::helper::Fallback; diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 48022c9ae..0a4bb9b9d 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -50,8 +50,8 @@ impl TrackedStruct { fn generate_tracked(&self) -> syn::Result { self.validate_tracked()?; - let id_struct = self.id_struct(); let config_struct = self.config_struct(); + let the_struct = self.id_or_ptr_struct(&config_struct.ident)?; let config_impl = self.config_impl(&config_struct); let inherent_impl = self.tracked_inherent_impl(); let ingredients_for_impl = self.tracked_struct_ingredients(&config_struct); @@ -63,7 +63,7 @@ impl TrackedStruct { Ok(quote! { #config_struct #config_impl - #id_struct + #the_struct #inherent_impl #ingredients_for_impl #salsa_struct_in_db_impl @@ -168,7 +168,8 @@ impl TrackedStruct { /// Generate an inherent impl with methods on the tracked type. fn tracked_inherent_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); + let (ident, _, impl_generics, type_generics, where_clause) = self.id_ident_and_generics(); + let jar_ty = self.jar_ty(); let db_dyn_ty = self.db_dyn_ty(); let tracked_field_ingredients: Literal = self.tracked_field_ingredients_index(); @@ -207,7 +208,8 @@ impl TrackedStruct { parse_quote! { #[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] - impl #ident { + impl #impl_generics #ident #type_generics + #where_clause { pub fn #constructor_name(__db: &#db_dyn_ty, #(#field_names: #field_tys,)*) -> Self { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); @@ -230,7 +232,7 @@ impl TrackedStruct { /// function ingredient for each of the value fields. fn tracked_struct_ingredients(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl { use crate::literal; - let ident = self.id_ident(); + let (ident, _, impl_generics, type_generics, where_clause) = self.id_ident_and_generics(); let jar_ty = self.jar_ty(); let config_struct_name = &config_struct.ident; let field_indices: Vec = self.all_field_indices(); @@ -241,7 +243,8 @@ impl TrackedStruct { let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect(); parse_quote! { - impl salsa::storage::IngredientsFor for #ident { + impl #impl_generics salsa::storage::IngredientsFor for #ident #type_generics + #where_clause { type Jar = #jar_ty; type Ingredients = ( salsa::tracked_struct::TrackedStructIngredient<#config_struct_name>, @@ -298,15 +301,17 @@ impl TrackedStruct { /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); + let (ident, parameters, _, type_generics, where_clause) = self.id_ident_and_generics(); + let db = syn::Ident::new("DB", ident.span()); let jar_ty = self.jar_ty(); let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); parse_quote! { - impl salsa::salsa_struct::SalsaStructInDb for #ident + impl<#db, #parameters> salsa::salsa_struct::SalsaStructInDb<#db> for #ident #type_generics where - DB: ?Sized + salsa::DbWithJar<#jar_ty>, + #db: ?Sized + salsa::DbWithJar<#jar_ty>, + #where_clause { - fn register_dependent_fn(db: &DB, index: salsa::routes::IngredientIndex) { + 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_ingredient.register_dependent_fn(index) @@ -317,15 +322,17 @@ impl TrackedStruct { /// Implementation of `TrackedStructInDb`. fn tracked_struct_in_db_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); + let (ident, parameters, _, type_generics, where_clause) = self.id_ident_and_generics(); + let db = syn::Ident::new("DB", ident.span()); let jar_ty = self.jar_ty(); let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); parse_quote! { - impl salsa::tracked_struct::TrackedStructInDb for #ident + impl<#db, #parameters> salsa::tracked_struct::TrackedStructInDb<#db> for #ident #type_generics where - DB: ?Sized + salsa::DbWithJar<#jar_ty>, + #db: ?Sized + salsa::DbWithJar<#jar_ty>, + #where_clause { - fn database_key_index(self, db: &DB) -> salsa::DatabaseKeyIndex { + 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_ingredient.database_key_index(self) From 5ce5e3c37425e370e61ec1e04ff1cfdbbe375c41 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 17 Apr 2024 17:41:16 -0400 Subject: [PATCH 08/61] track and assert struct ingredient indices We need a cheap way to compute field indices. --- components/salsa-2022-macros/src/tracked_struct.rs | 2 +- components/salsa-2022/src/routes.rs | 8 ++++++-- components/salsa-2022/src/tracked_struct.rs | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 0a4bb9b9d..9d0ec4f54 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -257,7 +257,7 @@ impl TrackedStruct { where DB: salsa::DbWithJar + salsa::storage::JarFromJars, { - let struct_ingredient = { + let struct_ingredient = { let index = routes.push( |jars| { let jar = >::jar_from_jars(jars); diff --git a/components/salsa-2022/src/routes.rs b/components/salsa-2022/src/routes.rs index 1226bc0aa..d8e663c10 100644 --- a/components/salsa-2022/src/routes.rs +++ b/components/salsa-2022/src/routes.rs @@ -10,13 +10,17 @@ pub struct IngredientIndex(u32); impl IngredientIndex { /// Create an ingredient index from a usize. - fn from(v: usize) -> Self { + pub(crate) fn from(v: usize) -> Self { assert!(v < (std::u32::MAX as usize)); Self(v as u32) } + pub(crate) fn as_u32(self) -> u32 { + self.0 + } + /// Convert the ingredient index back into a usize. - fn as_usize(self) -> usize { + pub(crate) fn as_usize(self) -> usize { self.0 as usize } } diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 0a5480601..15f8d08ea 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -121,6 +121,10 @@ pub struct TrackedStructValue where C: Configuration, { + /// Index of the struct ingredient. + #[allow(dead_code)] + struct_ingredient_index: IngredientIndex, + /// The id of this struct in the ingredient. id: C::Id, @@ -166,12 +170,21 @@ where } } + fn struct_ingredient_index(&self) -> IngredientIndex { + self.interned.ingredient_index() + } + pub fn new_field_ingredient( &self, field_ingredient_index: IngredientIndex, field_index: u32, field_debug_name: &'static str, ) -> TrackedFieldIngredient { + assert_eq!( + field_ingredient_index.as_u32() - self.struct_ingredient_index().as_u32() - 1, + field_index, + ); + TrackedFieldIngredient { ingredient_index: field_ingredient_index, field_index, @@ -215,6 +228,7 @@ where runtime, TrackedStructValue { id, + struct_ingredient_index: self.struct_ingredient_index(), created_at: current_revision, durability: current_deps.durability, fields, From b6311d8102cc2bb6d2ae7eee75ed44dc2a64d151 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 18 Apr 2024 07:17:18 -0400 Subject: [PATCH 09/61] WIP permit 'db on tracked struct definitions (opt) --- components/salsa-2022-macros/src/input.rs | 10 +- components/salsa-2022-macros/src/interned.rs | 8 +- .../salsa-2022-macros/src/salsa_struct.rs | 101 +++++++++----- .../salsa-2022-macros/src/tracked_struct.rs | 126 +++++++++++++----- components/salsa-2022/src/function.rs | 6 +- components/salsa-2022/src/tracked_struct.rs | 21 ++- salsa-2022-tests/tests/hello_world.rs | 11 +- 7 files changed, 199 insertions(+), 84 deletions(-) diff --git a/components/salsa-2022-macros/src/input.rs b/components/salsa-2022-macros/src/input.rs index c3a0276bd..6a30099ba 100644 --- a/components/salsa-2022-macros/src/input.rs +++ b/components/salsa-2022-macros/src/input.rs @@ -49,7 +49,7 @@ impl crate::options::AllowedOptions for InputStruct { impl InputStruct { fn generate_input(&self) -> syn::Result { - let id_struct = self.id_struct(); + let id_struct = self.the_struct_id(); let inherent_impl = self.input_inherent_impl(); let ingredients_for_impl = self.input_ingredients(); let as_id_impl = self.as_id_impl(); @@ -68,7 +68,7 @@ impl InputStruct { /// Generate an inherent impl with methods on the entity type. fn input_inherent_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); + let ident = self.the_ident(); let jar_ty = self.jar_ty(); let db_dyn_ty = self.db_dyn_ty(); let input_index = self.input_index(); @@ -212,12 +212,12 @@ impl InputStruct { /// function ingredient for each of the value fields. fn input_ingredients(&self) -> syn::ItemImpl { use crate::literal; - let ident = self.id_ident(); + let ident = self.the_ident(); let field_ty = self.all_field_tys(); let jar_ty = self.jar_ty(); let all_field_indices: Vec = self.all_field_indices(); let input_index: Literal = self.input_index(); - let debug_name_struct = literal(self.id_ident()); + let debug_name_struct = literal(self.the_ident()); let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect(); parse_quote! { @@ -304,7 +304,7 @@ impl InputStruct { /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); + let ident = self.the_ident(); let jar_ty = self.jar_ty(); parse_quote! { impl salsa::salsa_struct::SalsaStructInDb for #ident diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index c0bcbf0fe..3a95f816a 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -54,7 +54,7 @@ impl crate::options::AllowedOptions for InternedStruct { impl InternedStruct { fn generate_interned(&self) -> syn::Result { self.validate_interned()?; - let id_struct = self.id_struct(); + let id_struct = self.the_struct_id(); let data_struct = self.data_struct(); let ingredients_for_impl = self.ingredients_for_impl(); let as_id_impl = self.as_id_impl(); @@ -82,7 +82,7 @@ impl InternedStruct { /// as well as a `new` method. fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl { let vis = self.visibility(); - let id_ident = self.id_ident(); + let id_ident = self.the_ident(); let db_dyn_ty = self.db_dyn_ty(); let jar_ty = self.jar_ty(); @@ -144,7 +144,7 @@ impl InternedStruct { /// /// For a memoized type, the only ingredient is an `InternedIngredient`. fn ingredients_for_impl(&self) -> syn::ItemImpl { - let id_ident = self.id_ident(); + let id_ident = self.the_ident(); let debug_name = crate::literal(id_ident); let jar_ty = self.jar_ty(); let data_ident = self.data_ident(); @@ -177,7 +177,7 @@ impl InternedStruct { /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); + let ident = self.the_ident(); let jar_ty = self.jar_ty(); parse_quote! { impl salsa::salsa_struct::SalsaStructInDb for #ident diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index b57b4e1e8..1838cc8da 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -46,6 +46,17 @@ pub enum Customization { const BANNED_FIELD_NAMES: &[&str] = &["from", "new"]; +/// Classifies the kind of field stored in this salsa +/// struct. +#[derive(Debug, PartialEq, Eq)] +pub enum TheStructKind { + /// Stores an "id" + Id, + + /// Stores a "pointer" + Pointer, +} + impl SalsaStruct { pub(crate) fn new( args: proc_macro::TokenStream, @@ -70,6 +81,14 @@ impl SalsaStruct { }) } + pub(crate) fn the_struct_kind(&self) -> TheStructKind { + if self.struct_item.generics.params.is_empty() { + TheStructKind::Id + } else { + TheStructKind::Pointer + } + } + fn extract_customizations(struct_item: &syn::ItemStruct) -> syn::Result> { Ok(struct_item .attrs @@ -142,8 +161,8 @@ impl SalsaStruct { self.all_fields().map(|ef| ef.ty()).collect() } - /// The name of the "identity" struct (this is the name the user gave, e.g., `Foo`). - pub(crate) fn id_ident(&self) -> &syn::Ident { + /// The name of "the struct" (this is the name the user gave, e.g., `Foo`). + pub(crate) fn the_ident(&self) -> &syn::Ident { &self.struct_item.ident } @@ -151,7 +170,7 @@ impl SalsaStruct { /// /// * its list of generic parameters /// * the generics "split for impl". - pub(crate) fn id_ident_and_generics( + pub(crate) fn the_ident_and_generics( &self, ) -> ( &syn::Ident, @@ -195,17 +214,31 @@ impl SalsaStruct { match &self.args.data { Some(d) => d.clone(), None => syn::Ident::new( - &format!("__{}Data", self.id_ident()), - self.id_ident().span(), + &format!("__{}Data", self.the_ident()), + self.the_ident().span(), ), } } - /// Create a struct that wraps the id. + /// The type used for `id` values -- this is sometimes + /// the struct type or sometimes `salsa::Id`. + pub(crate) fn id_ty(&self) -> syn::Type { + match self.the_struct_kind() { + TheStructKind::Pointer => parse_quote!(salsa::Id), + TheStructKind::Id => { + let ident = &self.struct_item.ident; + parse_quote!(#ident) + } + } + } + + /// Create "the struct" whose field is an id. /// This is the struct the user will refernece, but only if there /// are no lifetimes. - pub(crate) fn id_struct(&self) -> syn::ItemStruct { - let ident = self.id_ident(); + pub(crate) fn the_struct_id(&self) -> syn::ItemStruct { + assert_eq!(self.the_struct_kind(), TheStructKind::Id); + + let ident = self.the_ident(); let visibility = &self.struct_item.vis; // Extract the attributes the user gave, but screen out derive, since we are adding our own, @@ -227,14 +260,11 @@ impl SalsaStruct { /// Create the struct that the user will reference. /// If - pub(crate) fn id_or_ptr_struct( - &self, - config_ident: &syn::Ident, - ) -> syn::Result { + pub(crate) fn the_struct(&self, config_ident: &syn::Ident) -> syn::Result { if self.struct_item.generics.params.is_empty() { - Ok(self.id_struct()) + Ok(self.the_struct_id()) } else { - let ident = self.id_ident(); + let ident = self.the_ident(); let visibility = &self.struct_item.vis; let generics = &self.struct_item.generics; @@ -299,38 +329,43 @@ impl SalsaStruct { pub(crate) fn constructor_name(&self) -> syn::Ident { match self.args.constructor_name.clone() { Some(name) => name, - None => Ident::new("new", self.id_ident().span()), + None => Ident::new("new", self.the_ident().span()), } } /// Generate `impl salsa::AsId for Foo` - pub(crate) fn as_id_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); - let (impl_generics, type_generics, where_clause) = - self.struct_item.generics.split_for_impl(); - parse_quote_spanned! { ident.span() => - impl #impl_generics salsa::AsId for #ident #type_generics - #where_clause - { - fn as_id(self) -> salsa::Id { - self.0 - } + pub(crate) fn as_id_impl(&self) -> Option { + match self.the_struct_kind() { + TheStructKind::Id => { + let ident = self.the_ident(); + let (impl_generics, type_generics, where_clause) = + self.struct_item.generics.split_for_impl(); + Some(parse_quote_spanned! { ident.span() => + impl #impl_generics salsa::AsId for #ident #type_generics + #where_clause + { + fn as_id(self) -> salsa::Id { + self.0 + } + + fn from_id(id: salsa::Id) -> Self { + #ident(id) + } + } - fn from_id(id: salsa::Id) -> Self { - #ident(id) - } + }) } - + TheStructKind::Pointer => None, } } - /// Generate `impl salsa::DebugWithDb for Foo` + /// Generate `impl salsa::DebugWithDb for Foo`, but only if this is an id struct. pub(crate) fn as_debug_with_db_impl(&self) -> Option { if self.customizations.contains(&Customization::DebugWithDb) { return None; } - let ident = self.id_ident(); + let ident = self.the_ident(); let (impl_generics, type_generics, where_clause) = self.struct_item.generics.split_for_impl(); @@ -367,7 +402,7 @@ impl SalsaStruct { #[allow(unused_imports)] use ::salsa::debug::helper::Fallback; let mut debug_struct = &mut f.debug_struct(#ident_string); - debug_struct = debug_struct.field("[salsa id]", &self.0.as_u32()); + // debug_struct = debug_struct.field("[salsa id]", &self.0.as_u32()); #fields debug_struct.finish() } diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 9d0ec4f54..27beaf39b 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -1,6 +1,6 @@ use proc_macro2::{Literal, Span, TokenStream}; -use crate::salsa_struct::{SalsaField, SalsaStruct}; +use crate::salsa_struct::{SalsaField, SalsaStruct, TheStructKind}; /// For an tracked struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate... /// @@ -11,7 +11,14 @@ pub(crate) fn tracked( args: proc_macro::TokenStream, struct_item: syn::ItemStruct, ) -> syn::Result { - SalsaStruct::with_struct(args, struct_item).and_then(|el| TrackedStruct(el).generate_tracked()) + let tokens = SalsaStruct::with_struct(args, struct_item) + .and_then(|el| TrackedStruct(el).generate_tracked())?; + + if std::env::var("NDM").is_ok() { + eprintln!("{}", tokens); + } + + Ok(tokens) } struct TrackedStruct(SalsaStruct); @@ -51,7 +58,7 @@ impl TrackedStruct { self.validate_tracked()?; let config_struct = self.config_struct(); - let the_struct = self.id_or_ptr_struct(&config_struct.ident)?; + let the_struct = self.the_struct(&config_struct.ident)?; let config_impl = self.config_impl(&config_struct); let inherent_impl = self.tracked_inherent_impl(); let ingredients_for_impl = self.tracked_struct_ingredients(&config_struct); @@ -80,8 +87,8 @@ impl TrackedStruct { fn config_struct(&self) -> syn::ItemStruct { let config_ident = syn::Ident::new( - &format!("__{}Config", self.id_ident()), - self.id_ident().span(), + &format!("__{}Config", self.the_ident()), + self.the_ident().span(), ); let visibility = self.visibility(); @@ -93,7 +100,7 @@ impl TrackedStruct { } fn config_impl(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl { - let id_ident = self.id_ident(); + let id_ty = self.id_ty(); 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(); @@ -136,7 +143,7 @@ impl TrackedStruct { parse_quote! { impl salsa::tracked_struct::Configuration for #config_ident { - type Id = #id_ident; + type Id = #id_ty; type Fields = ( #(#field_tys,)* ); type Revisions = [salsa::Revision; #arity]; @@ -168,7 +175,10 @@ impl TrackedStruct { /// Generate an inherent impl with methods on the tracked type. fn tracked_inherent_impl(&self) -> syn::ItemImpl { - let (ident, _, impl_generics, type_generics, where_clause) = self.id_ident_and_generics(); + let (ident, parameters, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); + + let id_ty = self.id_ty(); + let lt_db = parameters.iter().next(); let jar_ty = self.jar_ty(); let db_dyn_ty = self.db_dyn_ty(); @@ -180,25 +190,53 @@ impl TrackedStruct { 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 = 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_spanned! { field_get_name.span() => - #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.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index + match self.the_struct_kind() { + TheStructKind::Id => { + if !*is_clone_field { + parse_quote_spanned! { field_get_name.span() => + #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 #type_generics >>::ingredient(__jar); + &__ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index + } + } + } else { + parse_quote_spanned! { field_get_name.span() => + #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 #type_generics >>::ingredient(__jar); + __ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index.clone() + } + } } } - } else { - parse_quote_spanned! { field_get_name.span() => - #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.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index.clone() + + TheStructKind::Pointer => { + let lt_db = lt_db.unwrap(); + + if !*is_clone_field { + parse_quote_spanned! { field_get_name.span() => + #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> & #lt_db #field_ty + { + let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); + let fields = unsafe { &*self.0 }.field(__runtime, #field_index); + &fields.#field_index + } + } + } else { + parse_quote_spanned! { field_get_name.span() => + #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> #field_ty + { + let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); + let fields = unsafe { &*self.0 }.field(__runtime, #field_index); + fields.#field_index.clone() + } + } } } - } + } ) .collect(); @@ -206,6 +244,18 @@ impl TrackedStruct { let field_tys = self.all_field_tys(); let constructor_name = self.constructor_name(); + let data = syn::Ident::new("__data", Span::call_site()); + + let salsa_id = match self.the_struct_kind() { + TheStructKind::Id => quote!(self.0), + TheStructKind::Pointer => quote!(unsafe { &*self.0 }.id()), + }; + + let ctor = match self.the_struct_kind() { + TheStructKind::Id => quote!(salsa::AsId::from_id(#data.id())), + TheStructKind::Pointer => quote!(Self(#data, std::marker::PhantomData)), + }; + parse_quote! { #[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] impl #impl_generics #ident #type_generics @@ -213,12 +263,16 @@ impl TrackedStruct { 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 __data = __ingredients.0.new_struct( + let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< Self >>::ingredient(__jar); + let #data = __ingredients.0.new_struct( __runtime, (#(#field_names,)*), ); - __data.id() + #ctor + } + + pub fn salsa_id(&self) -> #id_ty { + #salsa_id } #(#field_getters)* @@ -232,14 +286,14 @@ impl TrackedStruct { /// function ingredient for each of the value fields. fn tracked_struct_ingredients(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl { use crate::literal; - let (ident, _, impl_generics, type_generics, where_clause) = self.id_ident_and_generics(); + let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); let jar_ty = self.jar_ty(); let config_struct_name = &config_struct.ident; let field_indices: Vec = 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_struct = literal(self.the_ident()); let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect(); parse_quote! { @@ -301,7 +355,7 @@ impl TrackedStruct { /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { - let (ident, parameters, _, type_generics, where_clause) = self.id_ident_and_generics(); + let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics(); let db = syn::Ident::new("DB", ident.span()); let jar_ty = self.jar_ty(); let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); @@ -313,7 +367,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); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); ingredients.#tracked_struct_ingredient.register_dependent_fn(index) } } @@ -322,7 +376,7 @@ impl TrackedStruct { /// Implementation of `TrackedStructInDb`. fn tracked_struct_in_db_impl(&self) -> syn::ItemImpl { - let (ident, parameters, _, type_generics, where_clause) = self.id_ident_and_generics(); + let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics(); let db = syn::Ident::new("DB", ident.span()); let jar_ty = self.jar_ty(); let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); @@ -334,8 +388,8 @@ 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_ingredient.database_key_index(self) + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); + ingredients.#tracked_struct_ingredient.database_key_index(self.salsa_id()) } } } @@ -343,9 +397,11 @@ impl TrackedStruct { /// Implementation of `Update`. fn update_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); + let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); parse_quote! { - unsafe impl salsa::update::Update for #ident { + unsafe impl #impl_generics salsa::update::Update for #ident #type_generics + #where_clause + { unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { if unsafe { *old_pointer } != new_value { unsafe { *old_pointer = new_value }; diff --git a/components/salsa-2022/src/function.rs b/components/salsa-2022/src/function.rs index 41b25c0ae..4d76f2ded 100644 --- a/components/salsa-2022/src/function.rs +++ b/components/salsa-2022/src/function.rs @@ -89,7 +89,7 @@ pub trait Configuration { type Key: AsId; /// The value computed by the function. - type Value: fmt::Debug; + type Value<'db>: fmt::Debug; /// Determines whether this function can recover from being a participant in a cycle /// (and, if so, how). @@ -101,13 +101,13 @@ pub trait Configuration { /// even though it was recomputed). /// /// This invokes user's code in form of the `Eq` impl. - fn should_backdate_value(old_value: &Self::Value, new_value: &Self::Value) -> bool; + fn should_backdate_value(old_value: &Self::Value<'_>, new_value: &Self::Value<'_>) -> bool; /// Invoked when we need to compute the value for the given key, either because we've never /// computed it before or because the old one relied on inputs that have changed. /// /// This invokes the function the user wrote. - fn execute(db: &DynDb, key: Self::Key) -> Self::Value; + fn execute<'db>(db: &'db DynDb, key: Self::Key) -> Self::Value<'db>; /// If the cycle strategy is `Recover`, then invoked when `key` is a participant /// in a cycle to find out what value it should have. diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 15f8d08ea..515864f33 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -122,7 +122,6 @@ where C: Configuration, { /// Index of the struct ingredient. - #[allow(dead_code)] struct_ingredient_index: IngredientIndex, /// The id of this struct in the ingredient. @@ -385,4 +384,24 @@ where pub fn id(&self) -> C::Id { self.id } + + /// 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, field_index: u32) -> &'db C::Fields { + let field_ingredient_index = + IngredientIndex::from(self.struct_ingredient_index.as_usize() + field_index as usize); + let changed_at = C::revision(&self.revisions, field_index); + + runtime.report_tracked_read( + DependencyIndex { + ingredient_index: field_ingredient_index, + key_index: Some(self.id.as_id()), + }, + self.durability, + changed_at, + ); + + &self.fields + } } diff --git a/salsa-2022-tests/tests/hello_world.rs b/salsa-2022-tests/tests/hello_world.rs index 507e36cc3..4cf14eb15 100644 --- a/salsa-2022-tests/tests/hello_world.rs +++ b/salsa-2022-tests/tests/hello_world.rs @@ -7,7 +7,12 @@ use expect_test::expect; use test_log::test; #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, final_result, intermediate_result); +struct Jar( + MyInput, + MyTracked<'static>, + final_result, + intermediate_result, +); trait Db: salsa::DbWithJar + HasLogger {} @@ -23,12 +28,12 @@ fn final_result(db: &dyn Db, input: MyInput) -> u32 { } #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked(jar = Jar)] -fn intermediate_result(db: &dyn Db, input: MyInput) -> MyTracked { +fn intermediate_result<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { db.push_log(format!("intermediate_result({:?})", input)); MyTracked::new(db, input.field(db) / 2) } From cb1a2bb75bcfdaf3ad578926b756f5b98ef16679 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 23 Apr 2024 05:42:30 -0400 Subject: [PATCH 10/61] Revert "WIP permit 'db on tracked struct definitions (opt)" This reverts commit 43b1b8ef3f98213279b9d0768847603299162f1f. --- components/salsa-2022-macros/src/input.rs | 10 +- components/salsa-2022-macros/src/interned.rs | 8 +- .../salsa-2022-macros/src/salsa_struct.rs | 101 +++++--------- .../salsa-2022-macros/src/tracked_struct.rs | 126 +++++------------- components/salsa-2022/src/function.rs | 6 +- components/salsa-2022/src/tracked_struct.rs | 21 +-- salsa-2022-tests/tests/hello_world.rs | 11 +- 7 files changed, 84 insertions(+), 199 deletions(-) diff --git a/components/salsa-2022-macros/src/input.rs b/components/salsa-2022-macros/src/input.rs index 6a30099ba..c3a0276bd 100644 --- a/components/salsa-2022-macros/src/input.rs +++ b/components/salsa-2022-macros/src/input.rs @@ -49,7 +49,7 @@ impl crate::options::AllowedOptions for InputStruct { impl InputStruct { fn generate_input(&self) -> syn::Result { - let id_struct = self.the_struct_id(); + let id_struct = self.id_struct(); let inherent_impl = self.input_inherent_impl(); let ingredients_for_impl = self.input_ingredients(); let as_id_impl = self.as_id_impl(); @@ -68,7 +68,7 @@ impl InputStruct { /// Generate an inherent impl with methods on the entity type. fn input_inherent_impl(&self) -> syn::ItemImpl { - let ident = self.the_ident(); + let ident = self.id_ident(); let jar_ty = self.jar_ty(); let db_dyn_ty = self.db_dyn_ty(); let input_index = self.input_index(); @@ -212,12 +212,12 @@ impl InputStruct { /// function ingredient for each of the value fields. fn input_ingredients(&self) -> syn::ItemImpl { use crate::literal; - let ident = self.the_ident(); + let ident = self.id_ident(); let field_ty = self.all_field_tys(); let jar_ty = self.jar_ty(); let all_field_indices: Vec = self.all_field_indices(); let input_index: Literal = self.input_index(); - let debug_name_struct = literal(self.the_ident()); + let debug_name_struct = literal(self.id_ident()); let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect(); parse_quote! { @@ -304,7 +304,7 @@ impl InputStruct { /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { - let ident = self.the_ident(); + let ident = self.id_ident(); let jar_ty = self.jar_ty(); parse_quote! { impl salsa::salsa_struct::SalsaStructInDb for #ident diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 3a95f816a..c0bcbf0fe 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -54,7 +54,7 @@ impl crate::options::AllowedOptions for InternedStruct { impl InternedStruct { fn generate_interned(&self) -> syn::Result { self.validate_interned()?; - let id_struct = self.the_struct_id(); + let id_struct = self.id_struct(); let data_struct = self.data_struct(); let ingredients_for_impl = self.ingredients_for_impl(); let as_id_impl = self.as_id_impl(); @@ -82,7 +82,7 @@ impl InternedStruct { /// as well as a `new` method. fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl { let vis = self.visibility(); - let id_ident = self.the_ident(); + let id_ident = self.id_ident(); let db_dyn_ty = self.db_dyn_ty(); let jar_ty = self.jar_ty(); @@ -144,7 +144,7 @@ impl InternedStruct { /// /// For a memoized type, the only ingredient is an `InternedIngredient`. fn ingredients_for_impl(&self) -> syn::ItemImpl { - let id_ident = self.the_ident(); + let id_ident = self.id_ident(); let debug_name = crate::literal(id_ident); let jar_ty = self.jar_ty(); let data_ident = self.data_ident(); @@ -177,7 +177,7 @@ impl InternedStruct { /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { - let ident = self.the_ident(); + let ident = self.id_ident(); let jar_ty = self.jar_ty(); parse_quote! { impl salsa::salsa_struct::SalsaStructInDb for #ident diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index 1838cc8da..b57b4e1e8 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -46,17 +46,6 @@ pub enum Customization { const BANNED_FIELD_NAMES: &[&str] = &["from", "new"]; -/// Classifies the kind of field stored in this salsa -/// struct. -#[derive(Debug, PartialEq, Eq)] -pub enum TheStructKind { - /// Stores an "id" - Id, - - /// Stores a "pointer" - Pointer, -} - impl SalsaStruct { pub(crate) fn new( args: proc_macro::TokenStream, @@ -81,14 +70,6 @@ impl SalsaStruct { }) } - pub(crate) fn the_struct_kind(&self) -> TheStructKind { - if self.struct_item.generics.params.is_empty() { - TheStructKind::Id - } else { - TheStructKind::Pointer - } - } - fn extract_customizations(struct_item: &syn::ItemStruct) -> syn::Result> { Ok(struct_item .attrs @@ -161,8 +142,8 @@ impl SalsaStruct { self.all_fields().map(|ef| ef.ty()).collect() } - /// The name of "the struct" (this is the name the user gave, e.g., `Foo`). - pub(crate) fn the_ident(&self) -> &syn::Ident { + /// The name of the "identity" struct (this is the name the user gave, e.g., `Foo`). + pub(crate) fn id_ident(&self) -> &syn::Ident { &self.struct_item.ident } @@ -170,7 +151,7 @@ impl SalsaStruct { /// /// * its list of generic parameters /// * the generics "split for impl". - pub(crate) fn the_ident_and_generics( + pub(crate) fn id_ident_and_generics( &self, ) -> ( &syn::Ident, @@ -214,31 +195,17 @@ impl SalsaStruct { match &self.args.data { Some(d) => d.clone(), None => syn::Ident::new( - &format!("__{}Data", self.the_ident()), - self.the_ident().span(), + &format!("__{}Data", self.id_ident()), + self.id_ident().span(), ), } } - /// The type used for `id` values -- this is sometimes - /// the struct type or sometimes `salsa::Id`. - pub(crate) fn id_ty(&self) -> syn::Type { - match self.the_struct_kind() { - TheStructKind::Pointer => parse_quote!(salsa::Id), - TheStructKind::Id => { - let ident = &self.struct_item.ident; - parse_quote!(#ident) - } - } - } - - /// Create "the struct" whose field is an id. + /// Create a struct that wraps the id. /// This is the struct the user will refernece, but only if there /// are no lifetimes. - pub(crate) fn the_struct_id(&self) -> syn::ItemStruct { - assert_eq!(self.the_struct_kind(), TheStructKind::Id); - - let ident = self.the_ident(); + pub(crate) fn id_struct(&self) -> syn::ItemStruct { + let ident = self.id_ident(); let visibility = &self.struct_item.vis; // Extract the attributes the user gave, but screen out derive, since we are adding our own, @@ -260,11 +227,14 @@ impl SalsaStruct { /// Create the struct that the user will reference. /// If - pub(crate) fn the_struct(&self, config_ident: &syn::Ident) -> syn::Result { + pub(crate) fn id_or_ptr_struct( + &self, + config_ident: &syn::Ident, + ) -> syn::Result { if self.struct_item.generics.params.is_empty() { - Ok(self.the_struct_id()) + Ok(self.id_struct()) } else { - let ident = self.the_ident(); + let ident = self.id_ident(); let visibility = &self.struct_item.vis; let generics = &self.struct_item.generics; @@ -329,43 +299,38 @@ impl SalsaStruct { pub(crate) fn constructor_name(&self) -> syn::Ident { match self.args.constructor_name.clone() { Some(name) => name, - None => Ident::new("new", self.the_ident().span()), + None => Ident::new("new", self.id_ident().span()), } } /// Generate `impl salsa::AsId for Foo` - pub(crate) fn as_id_impl(&self) -> Option { - match self.the_struct_kind() { - TheStructKind::Id => { - let ident = self.the_ident(); - let (impl_generics, type_generics, where_clause) = - self.struct_item.generics.split_for_impl(); - Some(parse_quote_spanned! { ident.span() => - impl #impl_generics salsa::AsId for #ident #type_generics - #where_clause - { - fn as_id(self) -> salsa::Id { - self.0 - } - - fn from_id(id: salsa::Id) -> Self { - #ident(id) - } - } + pub(crate) fn as_id_impl(&self) -> syn::ItemImpl { + let ident = self.id_ident(); + let (impl_generics, type_generics, where_clause) = + self.struct_item.generics.split_for_impl(); + parse_quote_spanned! { ident.span() => + impl #impl_generics salsa::AsId for #ident #type_generics + #where_clause + { + fn as_id(self) -> salsa::Id { + self.0 + } - }) + fn from_id(id: salsa::Id) -> Self { + #ident(id) + } } - TheStructKind::Pointer => None, + } } - /// Generate `impl salsa::DebugWithDb for Foo`, but only if this is an id struct. + /// Generate `impl salsa::DebugWithDb for Foo` pub(crate) fn as_debug_with_db_impl(&self) -> Option { if self.customizations.contains(&Customization::DebugWithDb) { return None; } - let ident = self.the_ident(); + let ident = self.id_ident(); let (impl_generics, type_generics, where_clause) = self.struct_item.generics.split_for_impl(); @@ -402,7 +367,7 @@ impl SalsaStruct { #[allow(unused_imports)] use ::salsa::debug::helper::Fallback; let mut debug_struct = &mut f.debug_struct(#ident_string); - // debug_struct = debug_struct.field("[salsa id]", &self.0.as_u32()); + debug_struct = debug_struct.field("[salsa id]", &self.0.as_u32()); #fields debug_struct.finish() } diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 27beaf39b..9d0ec4f54 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -1,6 +1,6 @@ use proc_macro2::{Literal, Span, TokenStream}; -use crate::salsa_struct::{SalsaField, SalsaStruct, TheStructKind}; +use crate::salsa_struct::{SalsaField, SalsaStruct}; /// For an tracked struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate... /// @@ -11,14 +11,7 @@ pub(crate) fn tracked( args: proc_macro::TokenStream, struct_item: syn::ItemStruct, ) -> syn::Result { - let tokens = SalsaStruct::with_struct(args, struct_item) - .and_then(|el| TrackedStruct(el).generate_tracked())?; - - if std::env::var("NDM").is_ok() { - eprintln!("{}", tokens); - } - - Ok(tokens) + SalsaStruct::with_struct(args, struct_item).and_then(|el| TrackedStruct(el).generate_tracked()) } struct TrackedStruct(SalsaStruct); @@ -58,7 +51,7 @@ impl TrackedStruct { self.validate_tracked()?; let config_struct = self.config_struct(); - let the_struct = self.the_struct(&config_struct.ident)?; + let the_struct = self.id_or_ptr_struct(&config_struct.ident)?; let config_impl = self.config_impl(&config_struct); let inherent_impl = self.tracked_inherent_impl(); let ingredients_for_impl = self.tracked_struct_ingredients(&config_struct); @@ -87,8 +80,8 @@ impl TrackedStruct { fn config_struct(&self) -> syn::ItemStruct { let config_ident = syn::Ident::new( - &format!("__{}Config", self.the_ident()), - self.the_ident().span(), + &format!("__{}Config", self.id_ident()), + self.id_ident().span(), ); let visibility = self.visibility(); @@ -100,7 +93,7 @@ impl TrackedStruct { } fn config_impl(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl { - let id_ty = self.id_ty(); + 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(); @@ -143,7 +136,7 @@ impl TrackedStruct { parse_quote! { impl salsa::tracked_struct::Configuration for #config_ident { - type Id = #id_ty; + type Id = #id_ident; type Fields = ( #(#field_tys,)* ); type Revisions = [salsa::Revision; #arity]; @@ -175,10 +168,7 @@ impl TrackedStruct { /// Generate an inherent impl with methods on the tracked type. fn tracked_inherent_impl(&self) -> syn::ItemImpl { - let (ident, parameters, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); - - let id_ty = self.id_ty(); - let lt_db = parameters.iter().next(); + let (ident, _, impl_generics, type_generics, where_clause) = self.id_ident_and_generics(); let jar_ty = self.jar_ty(); let db_dyn_ty = self.db_dyn_ty(); @@ -190,53 +180,25 @@ impl TrackedStruct { 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 = 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)| - match self.the_struct_kind() { - TheStructKind::Id => { - if !*is_clone_field { - parse_quote_spanned! { field_get_name.span() => - #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 #type_generics >>::ingredient(__jar); - &__ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index - } - } - } else { - parse_quote_spanned! { field_get_name.span() => - #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 #type_generics >>::ingredient(__jar); - __ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index.clone() - } - } + if !*is_clone_field { + parse_quote_spanned! { field_get_name.span() => + #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.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index } } - - TheStructKind::Pointer => { - let lt_db = lt_db.unwrap(); - - if !*is_clone_field { - parse_quote_spanned! { field_get_name.span() => - #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> & #lt_db #field_ty - { - let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); - let fields = unsafe { &*self.0 }.field(__runtime, #field_index); - &fields.#field_index - } - } - } else { - parse_quote_spanned! { field_get_name.span() => - #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> #field_ty - { - let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); - let fields = unsafe { &*self.0 }.field(__runtime, #field_index); - fields.#field_index.clone() - } - } + } else { + parse_quote_spanned! { field_get_name.span() => + #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.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index.clone() } } - } + } ) .collect(); @@ -244,18 +206,6 @@ impl TrackedStruct { let field_tys = self.all_field_tys(); let constructor_name = self.constructor_name(); - let data = syn::Ident::new("__data", Span::call_site()); - - let salsa_id = match self.the_struct_kind() { - TheStructKind::Id => quote!(self.0), - TheStructKind::Pointer => quote!(unsafe { &*self.0 }.id()), - }; - - let ctor = match self.the_struct_kind() { - TheStructKind::Id => quote!(salsa::AsId::from_id(#data.id())), - TheStructKind::Pointer => quote!(Self(#data, std::marker::PhantomData)), - }; - parse_quote! { #[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] impl #impl_generics #ident #type_generics @@ -263,16 +213,12 @@ impl TrackedStruct { 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< Self >>::ingredient(__jar); - let #data = __ingredients.0.new_struct( + let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar); + let __data = __ingredients.0.new_struct( __runtime, (#(#field_names,)*), ); - #ctor - } - - pub fn salsa_id(&self) -> #id_ty { - #salsa_id + __data.id() } #(#field_getters)* @@ -286,14 +232,14 @@ impl TrackedStruct { /// function ingredient for each of the value fields. fn tracked_struct_ingredients(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl { use crate::literal; - let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); + let (ident, _, impl_generics, type_generics, where_clause) = self.id_ident_and_generics(); let jar_ty = self.jar_ty(); let config_struct_name = &config_struct.ident; let field_indices: Vec = 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.the_ident()); + let debug_name_struct = literal(self.id_ident()); let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect(); parse_quote! { @@ -355,7 +301,7 @@ impl TrackedStruct { /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { - let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics(); + let (ident, parameters, _, type_generics, where_clause) = self.id_ident_and_generics(); let db = syn::Ident::new("DB", ident.span()); let jar_ty = self.jar_ty(); let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); @@ -367,7 +313,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 #type_generics>>::ingredient(jar); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident>>::ingredient(jar); ingredients.#tracked_struct_ingredient.register_dependent_fn(index) } } @@ -376,7 +322,7 @@ impl TrackedStruct { /// Implementation of `TrackedStructInDb`. fn tracked_struct_in_db_impl(&self) -> syn::ItemImpl { - let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics(); + let (ident, parameters, _, type_generics, where_clause) = self.id_ident_and_generics(); let db = syn::Ident::new("DB", ident.span()); let jar_ty = self.jar_ty(); let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); @@ -388,8 +334,8 @@ 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 #type_generics>>::ingredient(jar); - ingredients.#tracked_struct_ingredient.database_key_index(self.salsa_id()) + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident>>::ingredient(jar); + ingredients.#tracked_struct_ingredient.database_key_index(self) } } } @@ -397,11 +343,9 @@ impl TrackedStruct { /// Implementation of `Update`. fn update_impl(&self) -> syn::ItemImpl { - let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); + let ident = self.id_ident(); parse_quote! { - unsafe impl #impl_generics salsa::update::Update for #ident #type_generics - #where_clause - { + unsafe impl salsa::update::Update for #ident { unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { if unsafe { *old_pointer } != new_value { unsafe { *old_pointer = new_value }; diff --git a/components/salsa-2022/src/function.rs b/components/salsa-2022/src/function.rs index 4d76f2ded..41b25c0ae 100644 --- a/components/salsa-2022/src/function.rs +++ b/components/salsa-2022/src/function.rs @@ -89,7 +89,7 @@ pub trait Configuration { type Key: AsId; /// The value computed by the function. - type Value<'db>: fmt::Debug; + type Value: fmt::Debug; /// Determines whether this function can recover from being a participant in a cycle /// (and, if so, how). @@ -101,13 +101,13 @@ pub trait Configuration { /// even though it was recomputed). /// /// This invokes user's code in form of the `Eq` impl. - fn should_backdate_value(old_value: &Self::Value<'_>, new_value: &Self::Value<'_>) -> bool; + fn should_backdate_value(old_value: &Self::Value, new_value: &Self::Value) -> bool; /// Invoked when we need to compute the value for the given key, either because we've never /// computed it before or because the old one relied on inputs that have changed. /// /// This invokes the function the user wrote. - fn execute<'db>(db: &'db DynDb, key: Self::Key) -> Self::Value<'db>; + fn execute(db: &DynDb, key: Self::Key) -> Self::Value; /// If the cycle strategy is `Recover`, then invoked when `key` is a participant /// in a cycle to find out what value it should have. diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 515864f33..15f8d08ea 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -122,6 +122,7 @@ where C: Configuration, { /// Index of the struct ingredient. + #[allow(dead_code)] struct_ingredient_index: IngredientIndex, /// The id of this struct in the ingredient. @@ -384,24 +385,4 @@ where pub fn id(&self) -> C::Id { self.id } - - /// 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, field_index: u32) -> &'db C::Fields { - let field_ingredient_index = - IngredientIndex::from(self.struct_ingredient_index.as_usize() + field_index as usize); - let changed_at = C::revision(&self.revisions, field_index); - - runtime.report_tracked_read( - DependencyIndex { - ingredient_index: field_ingredient_index, - key_index: Some(self.id.as_id()), - }, - self.durability, - changed_at, - ); - - &self.fields - } } diff --git a/salsa-2022-tests/tests/hello_world.rs b/salsa-2022-tests/tests/hello_world.rs index 4cf14eb15..507e36cc3 100644 --- a/salsa-2022-tests/tests/hello_world.rs +++ b/salsa-2022-tests/tests/hello_world.rs @@ -7,12 +7,7 @@ use expect_test::expect; use test_log::test; #[salsa::jar(db = Db)] -struct Jar( - MyInput, - MyTracked<'static>, - final_result, - intermediate_result, -); +struct Jar(MyInput, MyTracked, final_result, intermediate_result); trait Db: salsa::DbWithJar + HasLogger {} @@ -28,12 +23,12 @@ fn final_result(db: &dyn Db, input: MyInput) -> u32 { } #[salsa::tracked(jar = Jar)] -struct MyTracked<'db> { +struct MyTracked { field: u32, } #[salsa::tracked(jar = Jar)] -fn intermediate_result<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { +fn intermediate_result(db: &dyn Db, input: MyInput) -> MyTracked { db.push_log(format!("intermediate_result({:?})", input)); MyTracked::new(db, input.field(db) / 2) } From 6e2647fa5055c47dbf4b1b8420a407b772d6eafc Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 25 Apr 2024 19:55:27 -0400 Subject: [PATCH 11/61] just take salsa::Id instead of id structs --- components/salsa-2022-macros/src/interned.rs | 10 ++++---- .../salsa-2022-macros/src/tracked_fn.rs | 2 +- .../salsa-2022-macros/src/tracked_struct.rs | 10 +++----- components/salsa-2022/src/interned.rs | 16 ++++-------- components/salsa-2022/src/tracked_struct.rs | 25 +++++++------------ .../src/tracked_struct/struct_map.rs | 20 +++++++-------- .../src/tracked_struct/tracked_field.rs | 6 ++--- 7 files changed, 37 insertions(+), 52 deletions(-) diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index c0bcbf0fe..fd53bde08 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -98,7 +98,7 @@ impl InternedStruct { #field_vis fn #field_get_name(self, 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< #id_ident >>::ingredient(jar); - std::clone::Clone::clone(&ingredients.data(runtime, self).#field_name) + std::clone::Clone::clone(&ingredients.data(runtime, self.0).#field_name) } } } else { @@ -106,7 +106,7 @@ impl InternedStruct { #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< #id_ident >>::ingredient(jar); - &ingredients.data(runtime, self).#field_name + &ingredients.data(runtime, self.0).#field_name } } } @@ -124,9 +124,9 @@ impl InternedStruct { ) -> Self { let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #id_ident >>::ingredient(jar); - ingredients.intern(runtime, #data_ident { + Self(ingredients.intern(runtime, #data_ident { #(#field_names,)* - }) + })) } }; @@ -151,7 +151,7 @@ impl InternedStruct { parse_quote! { impl salsa::storage::IngredientsFor for #id_ident { type Jar = #jar_ty; - type Ingredients = salsa::interned::InternedIngredient<#id_ident, #data_ident>; + type Ingredients = salsa::interned::InternedIngredient<#data_ident>; fn create_ingredients( routes: &mut salsa::routes::Routes, diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index d6dec4961..37b36e7da 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -329,7 +329,7 @@ fn configuration_struct(item_fn: &syn::ItemFn) -> syn::ItemStruct { } FunctionType::RequiresInterning => { let key_ty = key_tuple_ty(item_fn); - parse_quote! { salsa::interned::InternedIngredient } + parse_quote! { salsa::interned::InternedIngredient<#key_ty> } } }; diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 9d0ec4f54..2eeae7f6e 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -93,7 +93,6 @@ impl TrackedStruct { } 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(); @@ -136,7 +135,6 @@ impl TrackedStruct { parse_quote! { impl salsa::tracked_struct::Configuration for #config_ident { - type Id = #id_ident; type Fields = ( #(#field_tys,)* ); type Revisions = [salsa::Revision; #arity]; @@ -186,7 +184,7 @@ 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.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index + &__ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self.0).#field_index } } } else { @@ -195,7 +193,7 @@ 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.#tracked_field_ingredients[#field_index].field(__runtime, self).#field_index.clone() + __ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self.0).#field_index.clone() } } } @@ -218,7 +216,7 @@ impl TrackedStruct { __runtime, (#(#field_names,)*), ); - __data.id() + Self(__data.id()) } #(#field_getters)* @@ -335,7 +333,7 @@ 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_ingredient.database_key_index(self) + ingredients.#tracked_struct_ingredient.database_key_index(self.0) } } } diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index 47d176194..ef3c5e43b 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -11,23 +11,20 @@ use crate::key::DependencyIndex; use crate::plumbing::transmute_lifetime; use crate::runtime::local_state::QueryOrigin; use crate::runtime::Runtime; -use crate::DatabaseKeyIndex; +use crate::{DatabaseKeyIndex, Id}; use super::hash::FxDashMap; use super::ingredient::Ingredient; use super::routes::IngredientIndex; use super::Revision; -pub trait InternedId: AsId {} -impl InternedId for T {} - pub trait InternedData: Sized + Eq + Hash + Clone {} impl InternedData for T {} /// The interned ingredient has the job of hashing values of type `Data` to produce an `Id`. /// It used to store interned structs but also to store the id fields of a tracked struct. /// Interned values endure until they are explicitly removed in some way. -pub struct InternedIngredient { +pub struct InternedIngredient { /// Index of this ingredient in the database (used to construct database-ids, etc). ingredient_index: IngredientIndex, @@ -60,9 +57,8 @@ pub struct InternedIngredient { debug_name: &'static str, } -impl InternedIngredient +impl InternedIngredient where - Id: InternedId, Data: InternedData, { pub fn new(ingredient_index: IngredientIndex, debug_name: &'static str) -> Self { @@ -191,9 +187,8 @@ where } } -impl Ingredient for InternedIngredient +impl Ingredient for InternedIngredient where - Id: InternedId, Data: InternedData, { fn ingredient_index(&self) -> IngredientIndex { @@ -252,9 +247,8 @@ where } } -impl IngredientRequiresReset for InternedIngredient +impl IngredientRequiresReset for InternedIngredient where - Id: InternedId, Data: InternedData, { const RESET_ON_NEW_REVISION: bool = false; diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 15f8d08ea..7d0d4716a 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -5,11 +5,11 @@ use crate::{ id::AsId, ingredient::{fmt_index, Ingredient, IngredientRequiresReset}, ingredient_list::IngredientList, - interned::{InternedId, InternedIngredient}, + interned::InternedIngredient, key::{DatabaseKeyIndex, DependencyIndex}, runtime::{local_state::QueryOrigin, Runtime}, salsa_struct::SalsaStructInDb, - Database, Durability, Event, IngredientIndex, Revision, + Database, Durability, Event, Id, IngredientIndex, Revision, }; use self::struct_map::{StructMap, Update}; @@ -23,11 +23,6 @@ mod tracked_field; /// 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; @@ -93,7 +88,7 @@ pub struct TrackedStructIngredient where C: Configuration, { - interned: InternedIngredient, + interned: InternedIngredient, struct_map: struct_map::StructMap, @@ -126,7 +121,7 @@ where struct_ingredient_index: IngredientIndex, /// The id of this struct in the ingredient. - id: C::Id, + id: Id, /// The durability minimum durability of all inputs consumed /// by the creator query prior to creating this tracked struct. @@ -185,7 +180,7 @@ where field_index, ); - TrackedFieldIngredient { + TrackedFieldIngredient:: { ingredient_index: field_ingredient_index, field_index, struct_map: self.struct_map.view(), @@ -194,7 +189,7 @@ where } } - pub fn database_key_index(&self, id: C::Id) -> DatabaseKeyIndex { + pub fn database_key_index(&self, id: Id) -> DatabaseKeyIndex { DatabaseKeyIndex { ingredient_index: self.interned.ingredient_index(), key_index: id.as_id(), @@ -285,7 +280,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: C::Id) { + pub(crate) fn delete_entity(&self, db: &dyn crate::Database, id: Id) { db.salsa_event(Event { runtime_id: db.runtime().id(), kind: crate::EventKind::DidDiscard { @@ -338,7 +333,6 @@ where ) { let runtime = db.runtime(); let output_key = output_key.unwrap(); - let output_key: C::Id = ::from_id(output_key); self.struct_map.validate(runtime, output_key); } @@ -352,8 +346,7 @@ 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: C::Id = ::from_id(stale_output_key.unwrap()); - self.delete_entity(db.as_salsa_database(), stale_output_key); + self.delete_entity(db.as_salsa_database(), stale_output_key.unwrap()); } fn reset_for_new_revision(&mut self) { @@ -382,7 +375,7 @@ where C: Configuration, { /// The id of this struct in the ingredient. - pub fn id(&self) -> C::Id { + pub fn id(&self) -> Id { self.id } } diff --git a/components/salsa-2022/src/tracked_struct/struct_map.rs b/components/salsa-2022/src/tracked_struct/struct_map.rs index 9c26a4600..234911604 100644 --- a/components/salsa-2022/src/tracked_struct/struct_map.rs +++ b/components/salsa-2022/src/tracked_struct/struct_map.rs @@ -9,7 +9,7 @@ use dashmap::mapref::one::RefMut; use crate::{ hash::{FxDashMap, FxHasher}, plumbing::transmute_lifetime, - Runtime, + Id, Runtime, }; use super::{Configuration, TrackedStructValue}; @@ -18,7 +18,7 @@ pub(crate) struct StructMap where C: Configuration, { - map: Arc>>>, + map: Arc>>>, /// When specific entities are deleted, their data is added /// to this vector rather than being immediately freed. This is because we may` have @@ -32,7 +32,7 @@ pub(crate) struct StructMapView where C: Configuration, { - map: Arc>>>, + map: Arc>>>, } /// Return value for [`StructMap`][]'s `update` method. @@ -98,7 +98,7 @@ where unsafe { transmute_lifetime(self, &*pointer) } } - pub fn validate<'db>(&'db self, runtime: &'db Runtime, id: C::Id) { + pub fn validate<'db>(&'db self, runtime: &'db Runtime, id: Id) { let mut data = self.map.get_mut(&id).unwrap(); // Never update a struct twice in the same revision. @@ -114,7 +114,7 @@ where /// /// * If the value is not present in the map. /// * If the value is already updated in this revision. - pub fn update<'db>(&'db self, runtime: &'db Runtime, id: C::Id) -> Update<'db, C> { + pub fn update<'db>(&'db self, runtime: &'db Runtime, id: Id) -> Update<'db, C> { let mut data = self.map.get_mut(&id).unwrap(); // Never update a struct twice in the same revision. @@ -159,9 +159,9 @@ where /// * If the value is not present in the map. /// * If the value has not been updated in this revision. fn get_from_map<'db>( - map: &'db FxDashMap>>, + map: &'db FxDashMap>>, runtime: &'db Runtime, - id: C::Id, + id: Id, ) -> &'db TrackedStructValue { let data = map.get(&id).unwrap(); let data: &TrackedStructValue = &**data; @@ -186,7 +186,7 @@ where /// Remove the entry for `id` from the map. /// /// NB. the data won't actually be freed until `drop_deleted_entries` is called. - pub fn delete(&self, id: C::Id) { + pub fn delete(&self, id: Id) { if let Some((_, data)) = self.map.remove(&id) { self.deleted_entries.push(data); } @@ -208,7 +208,7 @@ where /// /// * If the value is not present in the map. /// * If the value has not been updated in this revision. - pub fn get<'db>(&'db self, runtime: &'db Runtime, id: C::Id) -> &'db TrackedStructValue { + pub fn get<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db TrackedStructValue { StructMap::get_from_map(&self.map, runtime, id) } } @@ -220,7 +220,7 @@ pub(crate) struct UpdateRef<'db, C> where C: Configuration, { - guard: RefMut<'db, C::Id, Box>, FxHasher>, + guard: RefMut<'db, Id, Box>, FxHasher>, } impl<'db, C> UpdateRef<'db, C> diff --git a/components/salsa-2022/src/tracked_struct/tracked_field.rs b/components/salsa-2022/src/tracked_struct/tracked_field.rs index 2ef4d8b70..39c8f03fd 100644 --- a/components/salsa-2022/src/tracked_struct/tracked_field.rs +++ b/components/salsa-2022/src/tracked_struct/tracked_field.rs @@ -2,7 +2,7 @@ use crate::{ id::AsId, ingredient::{Ingredient, IngredientRequiresReset}, key::DependencyIndex, - Database, IngredientIndex, Runtime, + Database, Id, IngredientIndex, Runtime, }; use super::{struct_map::StructMapView, Configuration}; @@ -34,7 +34,7 @@ where /// 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 { + pub fn field<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db C::Fields { let data = self.struct_map.get(runtime, id); let changed_at = C::revision(&data.revisions, self.field_index); @@ -72,7 +72,7 @@ where revision: crate::Revision, ) -> bool { let runtime = db.runtime(); - let id = ::from_id(input.key_index.unwrap()); + let id = input.key_index.unwrap(); let data = self.struct_map.get(runtime, id); let field_changed_at = C::revision(&data.revisions, self.field_index); field_changed_at > revision From b050bd874ac352c4d678e601a43e2be7ec9fbe17 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 25 Apr 2024 20:16:05 -0400 Subject: [PATCH 12/61] remove Key from Fn configuration Just use salsa::Id for the most part. --- .../salsa-2022-macros/src/configuration.rs | 8 ++--- .../salsa-2022-macros/src/tracked_fn.rs | 9 +++--- .../salsa-2022-macros/src/tracked_struct.rs | 4 +-- components/salsa-2022/src/function.rs | 32 +++++++------------ .../salsa-2022/src/function/accumulated.rs | 4 +-- components/salsa-2022/src/function/delete.rs | 4 +-- components/salsa-2022/src/function/execute.rs | 2 +- components/salsa-2022/src/function/fetch.rs | 12 +++---- components/salsa-2022/src/function/inputs.rs | 4 +-- .../src/function/maybe_changed_after.rs | 11 ++----- components/salsa-2022/src/function/specify.rs | 16 +++++----- components/salsa-2022/src/function/store.rs | 4 +-- components/salsa-2022/src/interned.rs | 8 ++--- components/salsa-2022/src/tracked_struct.rs | 2 +- ...ot-work-if-the-key-is-a-salsa-input.stderr | 6 ++-- ...work-if-the-key-is-a-salsa-interned.stderr | 6 ++-- 16 files changed, 59 insertions(+), 73 deletions(-) diff --git a/components/salsa-2022-macros/src/configuration.rs b/components/salsa-2022-macros/src/configuration.rs index 12a03bc9f..7b79e51ab 100644 --- a/components/salsa-2022-macros/src/configuration.rs +++ b/components/salsa-2022-macros/src/configuration.rs @@ -1,7 +1,7 @@ pub(crate) struct Configuration { pub(crate) jar_ty: syn::Type, pub(crate) salsa_struct_ty: syn::Type, - pub(crate) key_ty: syn::Type, + pub(crate) input_ty: syn::Type, pub(crate) value_ty: syn::Type, pub(crate) cycle_strategy: CycleRecoveryStrategy, pub(crate) backdate_fn: syn::ImplItemMethod, @@ -14,7 +14,7 @@ impl Configuration { let Configuration { jar_ty, salsa_struct_ty, - key_ty, + input_ty, value_ty, cycle_strategy, backdate_fn, @@ -25,7 +25,7 @@ impl Configuration { impl salsa::function::Configuration for #self_ty { type Jar = #jar_ty; type SalsaStruct = #salsa_struct_ty; - type Key = #key_ty; + type Input = #input_ty; type Value = #value_ty; const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = #cycle_strategy; #backdate_fn @@ -79,7 +79,7 @@ pub(crate) fn panic_cycle_recovery_fn() -> syn::ImplItemMethod { fn recover_from_cycle( _db: &salsa::function::DynDb, _cycle: &salsa::Cycle, - _key: Self::Key, + _key: salsa::Id, ) -> Self::Value { panic!() } diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index 37b36e7da..819b662b2 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -391,7 +391,7 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { let cycle_strategy = CycleRecoveryStrategy::Fallback; let cycle_fullback = parse_quote! { - fn recover_from_cycle(__db: &salsa::function::DynDb, __cycle: &salsa::Cycle, __id: Self::Key) -> Self::Value { + fn recover_from_cycle(__db: &salsa::function::DynDb, __cycle: &salsa::Cycle, __id: salsa::Id) -> Self::Value { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); let __ingredients = <_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar); @@ -422,7 +422,7 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { // 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! { - fn execute(__db: &salsa::function::DynDb, __id: Self::Key) -> Self::Value { + fn execute(__db: &salsa::function::DynDb, __id: salsa::Id) -> Self::Value { #inner_fn let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); @@ -436,7 +436,7 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { Configuration { jar_ty, salsa_struct_ty, - key_ty, + input_ty: key_ty, value_ty, cycle_strategy, backdate_fn, @@ -733,7 +733,8 @@ fn specify_fn( let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(#db_var); let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar); - __ingredients.function.specify_and_record(#db_var, #(#arg_names,)* #value_arg) + let __key = __ingredients.intern_map.intern(__runtime, (#(#arg_names),*)); + __ingredients.function.specify_and_record(#db_var, __key, #value_arg) } }, })) diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 2eeae7f6e..60043ddd5 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -330,10 +330,10 @@ impl TrackedStruct { #db: ?Sized + salsa::DbWithJar<#jar_ty>, #where_clause { - fn database_key_index(self, db: &#db) -> salsa::DatabaseKeyIndex { + fn database_key_index(db: &#db, id: salsa::Id) -> 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_ingredient.database_key_index(self.0) + ingredients.#tracked_struct_ingredient.database_key_index(id) } } } diff --git a/components/salsa-2022/src/function.rs b/components/salsa-2022/src/function.rs index 41b25c0ae..852577a63 100644 --- a/components/salsa-2022/src/function.rs +++ b/components/salsa-2022/src/function.rs @@ -45,7 +45,7 @@ pub struct FunctionIngredient { index: IngredientIndex, /// Tracks the keys for which we have memoized values. - memo_map: memo::MemoMap, + memo_map: memo::MemoMap, /// Tracks the keys that are currently being processed; used to coordinate between /// worker threads. @@ -83,10 +83,8 @@ pub trait Configuration { /// and are not clearly associated with any one salsa struct. type SalsaStruct: for<'db> SalsaStructInDb>; - /// What key is used to index the memo. Typically a salsa struct id, - /// but if this memoized function has multiple arguments it will be a `salsa::Id` - /// that results from interning those arguments. - type Key: AsId; + /// The input to the function + type Input; /// The value computed by the function. type Value: fmt::Debug; @@ -107,19 +105,13 @@ pub trait Configuration { /// computed it before or because the old one relied on inputs that have changed. /// /// This invokes the function the user wrote. - fn execute(db: &DynDb, key: Self::Key) -> Self::Value; + fn execute(db: &DynDb, key: Id) -> Self::Value; /// If the cycle strategy is `Recover`, then invoked when `key` is a participant /// in a cycle to find out what value it should have. /// /// This invokes the recovery function given by the user. - fn recover_from_cycle(db: &DynDb, cycle: &Cycle, key: Self::Key) -> Self::Value; - - /// Given a salsa Id, returns the key. Convenience function to avoid - /// having to type `::from_id`. - fn key_from_id(id: Id) -> Self::Key { - AsId::from_id(id) - } + fn recover_from_cycle(db: &DynDb, cycle: &Cycle, key: Id) -> Self::Value; } /// True if `old_value == new_value`. Invoked by the generated @@ -151,7 +143,7 @@ where } } - fn database_key_index(&self, k: C::Key) -> DatabaseKeyIndex { + fn database_key_index(&self, k: Id) -> DatabaseKeyIndex { DatabaseKeyIndex { ingredient_index: self.index, key_index: k.as_id(), @@ -180,7 +172,7 @@ where fn insert_memo( &self, db: &DynDb<'_, C>, - key: C::Key, + key: Id, memo: memo::Memo, ) -> Option<&C::Value> { self.register(db); @@ -218,7 +210,7 @@ where } fn maybe_changed_after(&self, db: &DB, input: DependencyIndex, revision: Revision) -> bool { - let key = C::key_from_id(input.key_index.unwrap()); + let key = input.key_index.unwrap(); let db = db.as_jar_db(); self.maybe_changed_after(db, key, revision) } @@ -227,8 +219,7 @@ where C::CYCLE_STRATEGY } - fn origin(&self, key_index: Id) -> Option { - let key = C::key_from_id(key_index); + fn origin(&self, key: Id) -> Option { self.origin(key) } @@ -238,7 +229,7 @@ where executor: DatabaseKeyIndex, output_key: Option, ) { - let output_key = C::key_from_id(output_key.unwrap()); + let output_key = output_key.unwrap(); self.validate_specified_value(db.as_jar_db(), executor, output_key); } @@ -257,11 +248,10 @@ where std::mem::take(&mut self.deleted_entries); } - fn salsa_struct_deleted(&self, db: &DB, id: crate::Id) { + fn salsa_struct_deleted(&self, db: &DB, id: Id) { // Remove any data keyed by `id`, since `id` no longer // exists in this revision. - let id: C::Key = C::key_from_id(id); if let Some(origin) = self.delete_memo(id) { let key = self.database_key_index(id); db.salsa_event(Event { diff --git a/components/salsa-2022/src/function/accumulated.rs b/components/salsa-2022/src/function/accumulated.rs index 9ee8b9e36..664058925 100644 --- a/components/salsa-2022/src/function/accumulated.rs +++ b/components/salsa-2022/src/function/accumulated.rs @@ -2,7 +2,7 @@ use crate::{ hash::FxHashSet, runtime::local_state::QueryOrigin, storage::{HasJar, HasJarsDyn}, - DatabaseKeyIndex, + DatabaseKeyIndex, Id, }; use super::{Configuration, DynDb, FunctionIngredient}; @@ -14,7 +14,7 @@ where { /// Returns all the values accumulated into `accumulator` by this query and its /// transitive inputs. - pub fn accumulated<'db, A>(&self, db: &DynDb<'db, C>, key: C::Key) -> Vec + pub fn accumulated<'db, A>(&self, db: &DynDb<'db, C>, key: Id) -> Vec where DynDb<'db, C>: HasJar, A: Accumulator, diff --git a/components/salsa-2022/src/function/delete.rs b/components/salsa-2022/src/function/delete.rs index 54796bc5a..66f831820 100644 --- a/components/salsa-2022/src/function/delete.rs +++ b/components/salsa-2022/src/function/delete.rs @@ -1,4 +1,4 @@ -use crate::runtime::local_state::QueryOrigin; +use crate::{runtime::local_state::QueryOrigin, Id}; use super::{Configuration, FunctionIngredient}; @@ -8,7 +8,7 @@ where { /// Removes the memoized value for `key` from the memo-map. /// Pushes the memo onto `deleted_entries` to ensure that any references into that memo which were handed out remain valid. - pub(super) fn delete_memo(&self, key: C::Key) -> Option { + pub(super) fn delete_memo(&self, key: Id) -> Option { if let Some(memo) = self.memo_map.remove(key) { let origin = memo.load().revisions.origin.clone(); self.deleted_entries.push(memo); diff --git a/components/salsa-2022/src/function/execute.rs b/components/salsa-2022/src/function/execute.rs index c05ad3288..32b940df3 100644 --- a/components/salsa-2022/src/function/execute.rs +++ b/components/salsa-2022/src/function/execute.rs @@ -44,7 +44,7 @@ where // Query was not previously executed, or value is potentially // stale, or value is absent. Let's execute! let database_key_index = active_query.database_key_index; - let key = C::key_from_id(database_key_index.key_index); + let key = database_key_index.key_index; let value = match Cycle::catch(|| C::execute(db, key)) { Ok(v) => v, Err(cycle) => { diff --git a/components/salsa-2022/src/function/fetch.rs b/components/salsa-2022/src/function/fetch.rs index ffba99814..223fa0cf3 100644 --- a/components/salsa-2022/src/function/fetch.rs +++ b/components/salsa-2022/src/function/fetch.rs @@ -1,6 +1,6 @@ use arc_swap::Guard; -use crate::{database::AsSalsaDatabase, runtime::StampedValue, storage::HasJarsDyn, AsId}; +use crate::{database::AsSalsaDatabase, runtime::StampedValue, storage::HasJarsDyn, AsId, Id}; use super::{Configuration, DynDb, FunctionIngredient}; @@ -8,7 +8,7 @@ impl FunctionIngredient where C: Configuration, { - pub fn fetch(&self, db: &DynDb, key: C::Key) -> &C::Value { + pub fn fetch(&self, db: &DynDb, key: Id) -> &C::Value { let runtime = db.runtime(); runtime.unwind_if_revision_cancelled(db); @@ -33,7 +33,7 @@ where } #[inline] - fn compute_value(&self, db: &DynDb, key: C::Key) -> StampedValue<&C::Value> { + fn compute_value(&self, db: &DynDb, key: Id) -> StampedValue<&C::Value> { loop { if let Some(value) = self.fetch_hot(db, key).or_else(|| self.fetch_cold(db, key)) { return value; @@ -42,7 +42,7 @@ where } #[inline] - fn fetch_hot(&self, db: &DynDb, key: C::Key) -> Option> { + fn fetch_hot(&self, db: &DynDb, key: Id) -> Option> { let memo_guard = self.memo_map.get(key); if let Some(memo) = &memo_guard { if memo.value.is_some() { @@ -59,7 +59,7 @@ where None } - fn fetch_cold(&self, db: &DynDb, key: C::Key) -> Option> { + fn fetch_cold(&self, db: &DynDb, key: Id) -> Option> { let runtime = db.runtime(); let database_key_index = self.database_key_index(key); @@ -87,7 +87,7 @@ where Some(self.execute(db, active_query, opt_old_memo)) } - fn evict(&self, key: C::Key) { + fn evict(&self, key: Id) { self.memo_map.evict(key); } } diff --git a/components/salsa-2022/src/function/inputs.rs b/components/salsa-2022/src/function/inputs.rs index 8f2b22410..09b56410b 100644 --- a/components/salsa-2022/src/function/inputs.rs +++ b/components/salsa-2022/src/function/inputs.rs @@ -1,4 +1,4 @@ -use crate::runtime::local_state::QueryOrigin; +use crate::{runtime::local_state::QueryOrigin, Id}; use super::{Configuration, FunctionIngredient}; @@ -6,7 +6,7 @@ impl FunctionIngredient where C: Configuration, { - pub(super) fn origin(&self, key: C::Key) -> Option { + pub(super) fn origin(&self, key: Id) -> Option { self.memo_map.get(key).map(|m| m.revisions.origin.clone()) } } diff --git a/components/salsa-2022/src/function/maybe_changed_after.rs b/components/salsa-2022/src/function/maybe_changed_after.rs index 3b4304140..6f8a8c6d9 100644 --- a/components/salsa-2022/src/function/maybe_changed_after.rs +++ b/components/salsa-2022/src/function/maybe_changed_after.rs @@ -9,7 +9,7 @@ use crate::{ StampedValue, }, storage::HasJarsDyn, - Revision, Runtime, + Id, Revision, Runtime, }; use super::{memo::Memo, Configuration, DynDb, FunctionIngredient}; @@ -18,12 +18,7 @@ impl FunctionIngredient where C: Configuration, { - pub(super) fn maybe_changed_after( - &self, - db: &DynDb, - key: C::Key, - revision: Revision, - ) -> bool { + pub(super) fn maybe_changed_after(&self, db: &DynDb, key: Id, revision: Revision) -> bool { let runtime = db.runtime(); runtime.unwind_if_revision_cancelled(db); @@ -58,7 +53,7 @@ where fn maybe_changed_after_cold( &self, db: &DynDb, - key_index: C::Key, + key_index: Id, revision: Revision, ) -> Option { let runtime = db.runtime(); diff --git a/components/salsa-2022/src/function/specify.rs b/components/salsa-2022/src/function/specify.rs index 1904cc630..9916deba3 100644 --- a/components/salsa-2022/src/function/specify.rs +++ b/components/salsa-2022/src/function/specify.rs @@ -5,7 +5,7 @@ use crate::{ runtime::local_state::{QueryOrigin, QueryRevisions}, storage::HasJarsDyn, tracked_struct::TrackedStructInDb, - DatabaseKeyIndex, DebugWithDb, + DatabaseKeyIndex, DebugWithDb, Id, }; use super::{memo::Memo, Configuration, DynDb, FunctionIngredient}; @@ -17,14 +17,14 @@ where /// Specifies the value of the function for the given key. /// This is a way to imperatively set the value of a function. /// It only works if the key is a tracked struct created in the current query. - pub(crate) fn specify<'db>( + fn specify<'db>( &self, db: &'db DynDb<'db, C>, - key: C::Key, + key: Id, value: C::Value, origin: impl Fn(DatabaseKeyIndex) -> QueryOrigin, ) where - C::Key: TrackedStructInDb>, + C::Input: TrackedStructInDb>, { let runtime = db.runtime(); @@ -45,7 +45,7 @@ where // * Q4 invokes Q2 and then Q1 // // Now, if We invoke Q3 first, We get one result for Q2, but if We invoke Q4 first, We get a different value. That's no good. - let database_key_index = key.database_key_index(db); + let database_key_index = ::database_key_index(db, key); let dependency_index = database_key_index.into(); if !runtime.is_output_of_active_query(dependency_index) { panic!("can only use `specfiy` on entities created during current query"); @@ -94,9 +94,9 @@ where /// Specify the value for `key` *and* record that we did so. /// Used for explicit calls to `specify`, but not needed for pre-declared tracked struct fields. - pub fn specify_and_record<'db>(&self, db: &'db DynDb<'db, C>, key: C::Key, value: C::Value) + pub fn specify_and_record<'db>(&self, db: &'db DynDb<'db, C>, key: Id, value: C::Value) where - C::Key: TrackedStructInDb>, + C::Input: TrackedStructInDb>, { self.specify(db, key, value, |database_key_index| { QueryOrigin::Assigned(database_key_index) @@ -115,7 +115,7 @@ where &self, db: &DynDb<'_, C>, executor: DatabaseKeyIndex, - key: C::Key, + key: Id, ) { let runtime = db.runtime(); diff --git a/components/salsa-2022/src/function/store.rs b/components/salsa-2022/src/function/store.rs index a90b12455..f09fca000 100644 --- a/components/salsa-2022/src/function/store.rs +++ b/components/salsa-2022/src/function/store.rs @@ -5,7 +5,7 @@ use crossbeam::atomic::AtomicCell; use crate::{ durability::Durability, runtime::local_state::{QueryOrigin, QueryRevisions}, - Runtime, + Id, Runtime, }; use super::{memo::Memo, Configuration, FunctionIngredient}; @@ -17,7 +17,7 @@ where pub fn store( &mut self, runtime: &mut Runtime, - key: C::Key, + key: Id, value: C::Value, durability: Durability, ) { diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index ef3c5e43b..7ee8493aa 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -264,11 +264,11 @@ impl IdentityInterner { IdentityInterner { data: PhantomData } } - pub fn intern(&self, _runtime: &Runtime, id: Id) -> Id { - id + pub fn intern(&self, _runtime: &Runtime, id: Id) -> crate::Id { + id.as_id() } - pub fn data(&self, _runtime: &Runtime, id: Id) -> (Id,) { - (id,) + pub fn data(&self, _runtime: &Runtime, id: crate::Id) -> (Id,) { + (Id::from_id(id),) } } diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 7d0d4716a..5520b0b67 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -73,7 +73,7 @@ pub trait Configuration { pub trait TrackedStructInDb: SalsaStructInDb { /// Converts the identifier for this tracked struct into a `DatabaseKeyIndex`. - fn database_key_index(self, db: &DB) -> DatabaseKeyIndex; + fn database_key_index(db: &DB, id: Id) -> DatabaseKeyIndex; } /// Created for each tracked struct. diff --git a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr index 84d11e22d..60fb56316 100644 --- a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr +++ b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr @@ -8,9 +8,9 @@ error[E0277]: the trait bound `MyInput: TrackedStructInDb` is not satisf note: required by a bound in `function::specify::>::specify_and_record` --> $WORKSPACE/components/salsa-2022/src/function/specify.rs | - | pub fn specify_and_record<'db>(&self, db: &'db DynDb<'db, C>, key: C::Key, value: C::Value) + | pub fn specify_and_record<'db>(&self, db: &'db DynDb<'db, C>, key: Id, value: C::Value) | ------------------ required by a bound in this associated function | where - | C::Key: TrackedStructInDb>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `function::specify::>::specify_and_record` + | C::Input: TrackedStructInDb>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `function::specify::>::specify_and_record` = note: this error originates in the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr index b6efedf6f..a6afbf0a8 100644 --- a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr +++ b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr @@ -8,9 +8,9 @@ error[E0277]: the trait bound `MyInterned: TrackedStructInDb` is not sat note: required by a bound in `function::specify::>::specify_and_record` --> $WORKSPACE/components/salsa-2022/src/function/specify.rs | - | pub fn specify_and_record<'db>(&self, db: &'db DynDb<'db, C>, key: C::Key, value: C::Value) + | pub fn specify_and_record<'db>(&self, db: &'db DynDb<'db, C>, key: Id, value: C::Value) | ------------------ required by a bound in this associated function | where - | C::Key: TrackedStructInDb>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `function::specify::>::specify_and_record` + | C::Input: TrackedStructInDb>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `function::specify::>::specify_and_record` = note: this error originates in the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) From 44a8a2f41c90c474525869590a21df4fe06b781c Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 27 Apr 2024 10:33:54 -0400 Subject: [PATCH 13/61] make fn input/value a GAT --- .../salsa-2022-macros/src/configuration.rs | 14 ++--- components/salsa-2022-macros/src/lib.rs | 18 ++++-- .../salsa-2022-macros/src/tracked_fn.rs | 4 +- components/salsa-2022/src/function.rs | 35 +++++------ .../salsa-2022/src/function/accumulated.rs | 2 +- .../salsa-2022/src/function/backdate.rs | 4 +- components/salsa-2022/src/function/delete.rs | 27 ++++++++- .../salsa-2022/src/function/diff_outputs.rs | 2 +- components/salsa-2022/src/function/execute.rs | 10 ++-- components/salsa-2022/src/function/fetch.rs | 20 +++++-- .../src/function/maybe_changed_after.rs | 17 ++++-- components/salsa-2022/src/function/memo.rs | 59 ++++++++++++++----- components/salsa-2022/src/function/specify.rs | 12 ++-- components/salsa-2022/src/function/store.rs | 6 +- ...ot-work-if-the-key-is-a-salsa-input.stderr | 6 +- ...work-if-the-key-is-a-salsa-interned.stderr | 6 +- 16 files changed, 161 insertions(+), 81 deletions(-) diff --git a/components/salsa-2022-macros/src/configuration.rs b/components/salsa-2022-macros/src/configuration.rs index 7b79e51ab..02276aafa 100644 --- a/components/salsa-2022-macros/src/configuration.rs +++ b/components/salsa-2022-macros/src/configuration.rs @@ -25,8 +25,8 @@ impl Configuration { impl salsa::function::Configuration for #self_ty { type Jar = #jar_ty; type SalsaStruct = #salsa_struct_ty; - type Input = #input_ty; - type Value = #value_ty; + type Input<'db> = #input_ty; + type Value<'db> = #value_ty; const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = #cycle_strategy; #backdate_fn #execute_fn @@ -59,13 +59,13 @@ impl quote::ToTokens for CycleRecoveryStrategy { pub(crate) fn should_backdate_value_fn(should_backdate: bool) -> syn::ImplItemMethod { if should_backdate { parse_quote! { - fn should_backdate_value(v1: &Self::Value, v2: &Self::Value) -> bool { + fn should_backdate_value(v1: &Self::Value<'_>, v2: &Self::Value<'_>) -> bool { salsa::function::should_backdate_value(v1, v2) } } } else { parse_quote! { - fn should_backdate_value(_v1: &Self::Value, _v2: &Self::Value) -> bool { + fn should_backdate_value(_v1: &Self::Value<'_>, _v2: &Self::Value<'_>) -> bool { false } } @@ -76,11 +76,11 @@ pub(crate) fn should_backdate_value_fn(should_backdate: bool) -> syn::ImplItemMe /// the cycle recovery is panic. pub(crate) fn panic_cycle_recovery_fn() -> syn::ImplItemMethod { parse_quote! { - fn recover_from_cycle( - _db: &salsa::function::DynDb, + fn recover_from_cycle<'db>( + _db: &'db salsa::function::DynDb<'db, Self>, _cycle: &salsa::Cycle, _key: salsa::Id, - ) -> Self::Value { + ) -> Self::Value<'db> { panic!() } } diff --git a/components/salsa-2022-macros/src/lib.rs b/components/salsa-2022-macros/src/lib.rs index 45b31e676..febac410a 100644 --- a/components/salsa-2022-macros/src/lib.rs +++ b/components/salsa-2022-macros/src/lib.rs @@ -11,17 +11,23 @@ use proc_macro::TokenStream; macro_rules! parse_quote { ($($inp:tt)*) => { - syn::parse2(quote!{$($inp)*}).unwrap_or_else(|err| { - panic!("failed to parse at {}:{}:{}: {}", file!(), line!(), column!(), err) - }) + { + let tt = quote!{$($inp)*}; + syn::parse2(tt.clone()).unwrap_or_else(|err| { + panic!("failed to parse `{}` at {}:{}:{}: {}", tt, file!(), line!(), column!(), err) + }) + } } } macro_rules! parse_quote_spanned { ($($inp:tt)*) => { - syn::parse2(quote_spanned!{$($inp)*}).unwrap_or_else(|err| { - panic!("failed to parse at {}:{}:{}: {}", file!(), line!(), column!(), err) - }) + { + let tt = quote_spanned!{$($inp)*}; + syn::parse2(tt.clone()).unwrap_or_else(|err| { + panic!("failed to parse `{}` at {}:{}:{}: {}", tt, file!(), line!(), column!(), err) + }) + } } } diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index 819b662b2..e7a03ccb5 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -391,7 +391,7 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { let cycle_strategy = CycleRecoveryStrategy::Fallback; let cycle_fullback = parse_quote! { - fn recover_from_cycle(__db: &salsa::function::DynDb, __cycle: &salsa::Cycle, __id: salsa::Id) -> Self::Value { + fn recover_from_cycle<'db>(__db: &'db salsa::function::DynDb<'db, Self>, __cycle: &salsa::Cycle, __id: salsa::Id) -> Self::Value<'db> { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); let __ingredients = <_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar); @@ -422,7 +422,7 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { // 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! { - fn execute(__db: &salsa::function::DynDb, __id: salsa::Id) -> Self::Value { + fn execute<'db>(__db: &'db salsa::function::DynDb<'db, Self>, __id: salsa::Id) -> Self::Value<'db> { #inner_fn let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); diff --git a/components/salsa-2022/src/function.rs b/components/salsa-2022/src/function.rs index 852577a63..469235a8e 100644 --- a/components/salsa-2022/src/function.rs +++ b/components/salsa-2022/src/function.rs @@ -1,7 +1,6 @@ use std::{fmt, sync::Arc}; -use arc_swap::ArcSwap; -use crossbeam::{atomic::AtomicCell, queue::SegQueue}; +use crossbeam::atomic::AtomicCell; use crate::{ cycle::CycleRecoveryStrategy, @@ -13,6 +12,8 @@ use crate::{ Cycle, DbWithJar, Event, EventKind, Id, Revision, }; +use self::delete::DeletedEntries; + use super::{ingredient::Ingredient, routes::IngredientIndex, AsId}; mod accumulated; @@ -45,7 +46,7 @@ pub struct FunctionIngredient { index: IngredientIndex, /// Tracks the keys for which we have memoized values. - memo_map: memo::MemoMap, + memo_map: memo::MemoMap, /// Tracks the keys that are currently being processed; used to coordinate between /// worker threads. @@ -66,7 +67,7 @@ pub struct FunctionIngredient { /// current revision: you would be right, but we are being defensive, because /// we don't know that we can trust the database to give us the same runtime /// everytime and so forth. - deleted_entries: SegQueue>>, + deleted_entries: DeletedEntries, /// Set to true once we invoke `register_dependent_fn` for `C::SalsaStruct`. /// Prevents us from registering more than once. @@ -84,10 +85,10 @@ pub trait Configuration { type SalsaStruct: for<'db> SalsaStructInDb>; /// The input to the function - type Input; + type Input<'db>; /// The value computed by the function. - type Value: fmt::Debug; + type Value<'db>: fmt::Debug; /// Determines whether this function can recover from being a participant in a cycle /// (and, if so, how). @@ -99,19 +100,19 @@ pub trait Configuration { /// even though it was recomputed). /// /// This invokes user's code in form of the `Eq` impl. - fn should_backdate_value(old_value: &Self::Value, new_value: &Self::Value) -> bool; + fn should_backdate_value(old_value: &Self::Value<'_>, new_value: &Self::Value<'_>) -> bool; /// Invoked when we need to compute the value for the given key, either because we've never /// computed it before or because the old one relied on inputs that have changed. /// /// This invokes the function the user wrote. - fn execute(db: &DynDb, key: Id) -> Self::Value; + fn execute<'db>(db: &'db DynDb, key: Id) -> Self::Value<'db>; /// If the cycle strategy is `Recover`, then invoked when `key` is a participant /// in a cycle to find out what value it should have. /// /// This invokes the recovery function given by the user. - fn recover_from_cycle(db: &DynDb, cycle: &Cycle, key: Id) -> Self::Value; + fn recover_from_cycle<'db>(db: &'db DynDb, cycle: &Cycle, key: Id) -> Self::Value<'db>; } /// True if `old_value == new_value`. Invoked by the generated @@ -163,18 +164,18 @@ where /// only cleared with `&mut self`. unsafe fn extend_memo_lifetime<'this, 'memo>( &'this self, - memo: &'memo memo::Memo, - ) -> Option<&'this C::Value> { - let memo_value: Option<&'memo C::Value> = memo.value.as_ref(); + memo: &'memo memo::Memo>, + ) -> Option<&'this C::Value<'this>> { + let memo_value: Option<&'memo C::Value<'this>> = memo.value.as_ref(); std::mem::transmute(memo_value) } - fn insert_memo( - &self, - db: &DynDb<'_, C>, + fn insert_memo<'db>( + &'db self, + db: &'db DynDb<'db, C>, key: Id, - memo: memo::Memo, - ) -> Option<&C::Value> { + memo: memo::Memo>, + ) -> Option<&C::Value<'db>> { self.register(db); let memo = Arc::new(memo); let value = unsafe { diff --git a/components/salsa-2022/src/function/accumulated.rs b/components/salsa-2022/src/function/accumulated.rs index 664058925..ad4a95d41 100644 --- a/components/salsa-2022/src/function/accumulated.rs +++ b/components/salsa-2022/src/function/accumulated.rs @@ -14,7 +14,7 @@ where { /// Returns all the values accumulated into `accumulator` by this query and its /// transitive inputs. - pub fn accumulated<'db, A>(&self, db: &DynDb<'db, C>, key: Id) -> Vec + pub fn accumulated<'db, A>(&'db self, db: &'db DynDb<'db, C>, key: Id) -> Vec where DynDb<'db, C>: HasJar, A: Accumulator, diff --git a/components/salsa-2022/src/function/backdate.rs b/components/salsa-2022/src/function/backdate.rs index 9695eebf9..5d9b4ece0 100644 --- a/components/salsa-2022/src/function/backdate.rs +++ b/components/salsa-2022/src/function/backdate.rs @@ -11,9 +11,9 @@ where /// on an old memo when a new memo has been produced to check whether there have been changed. pub(super) fn backdate_if_appropriate( &self, - old_memo: &Memo, + old_memo: &Memo>, revisions: &mut QueryRevisions, - value: &C::Value, + value: &C::Value<'_>, ) { if let Some(old_value) = &old_memo.value { // Careful: if the value became less durable than it diff --git a/components/salsa-2022/src/function/delete.rs b/components/salsa-2022/src/function/delete.rs index 66f831820..0a4039d72 100644 --- a/components/salsa-2022/src/function/delete.rs +++ b/components/salsa-2022/src/function/delete.rs @@ -1,6 +1,9 @@ +use arc_swap::ArcSwap; +use crossbeam::queue::SegQueue; + use crate::{runtime::local_state::QueryOrigin, Id}; -use super::{Configuration, FunctionIngredient}; +use super::{memo, Configuration, FunctionIngredient}; impl FunctionIngredient where @@ -18,3 +21,25 @@ where } } } + +/// Stores the list of memos that have been deleted so they can be freed +/// once the next revision starts. See the comment on the field +/// `deleted_entries` of [`FunctionIngredient`][] for more details. +pub(super) struct DeletedEntries { + seg_queue: SegQueue>>>, +} + +impl Default for DeletedEntries { + fn default() -> Self { + Self { + seg_queue: Default::default(), + } + } +} + +impl DeletedEntries { + pub(super) fn push<'db>(&'db self, memo: ArcSwap>>) { + let memo = unsafe { std::mem::transmute(memo) }; + self.seg_queue.push(memo); + } +} diff --git a/components/salsa-2022/src/function/diff_outputs.rs b/components/salsa-2022/src/function/diff_outputs.rs index c8f8fc633..8a93a056d 100644 --- a/components/salsa-2022/src/function/diff_outputs.rs +++ b/components/salsa-2022/src/function/diff_outputs.rs @@ -15,7 +15,7 @@ where &self, db: &DynDb<'_, C>, key: DatabaseKeyIndex, - old_memo: &Memo, + old_memo: &Memo>, revisions: &QueryRevisions, ) { // Iterate over the outputs of the `old_memo` and put them into a hashset diff --git a/components/salsa-2022/src/function/execute.rs b/components/salsa-2022/src/function/execute.rs index 32b940df3..b79700e1c 100644 --- a/components/salsa-2022/src/function/execute.rs +++ b/components/salsa-2022/src/function/execute.rs @@ -22,12 +22,12 @@ where /// * `db`, the database. /// * `active_query`, the active stack frame for the query to execute. /// * `opt_old_memo`, the older memo, if any existed. Used for backdated. - pub(super) fn execute( - &self, - db: &DynDb, + pub(super) fn execute<'db>( + &'db self, + db: &'db DynDb<'db, C>, active_query: ActiveQueryGuard<'_>, - opt_old_memo: Option>>, - ) -> StampedValue<&C::Value> { + opt_old_memo: Option>>>, + ) -> StampedValue<&C::Value<'db>> { let runtime = db.runtime(); let revision_now = runtime.current_revision(); let database_key_index = active_query.database_key_index; diff --git a/components/salsa-2022/src/function/fetch.rs b/components/salsa-2022/src/function/fetch.rs index 223fa0cf3..55aa0eae1 100644 --- a/components/salsa-2022/src/function/fetch.rs +++ b/components/salsa-2022/src/function/fetch.rs @@ -8,7 +8,7 @@ impl FunctionIngredient where C: Configuration, { - pub fn fetch(&self, db: &DynDb, key: Id) -> &C::Value { + pub fn fetch<'db>(&'db self, db: &'db DynDb<'db, C>, key: Id) -> &C::Value<'db> { let runtime = db.runtime(); runtime.unwind_if_revision_cancelled(db); @@ -33,7 +33,11 @@ where } #[inline] - fn compute_value(&self, db: &DynDb, key: Id) -> StampedValue<&C::Value> { + fn compute_value<'db>( + &'db self, + db: &'db DynDb<'db, C>, + key: Id, + ) -> StampedValue<&'db C::Value<'db>> { loop { if let Some(value) = self.fetch_hot(db, key).or_else(|| self.fetch_cold(db, key)) { return value; @@ -42,7 +46,11 @@ where } #[inline] - fn fetch_hot(&self, db: &DynDb, key: Id) -> Option> { + fn fetch_hot<'db>( + &'db self, + db: &'db DynDb<'db, C>, + key: Id, + ) -> Option>> { let memo_guard = self.memo_map.get(key); if let Some(memo) = &memo_guard { if memo.value.is_some() { @@ -59,7 +67,11 @@ where None } - fn fetch_cold(&self, db: &DynDb, key: Id) -> Option> { + fn fetch_cold<'db>( + &'db self, + db: &'db DynDb<'db, C>, + key: Id, + ) -> Option>> { let runtime = db.runtime(); let database_key_index = self.database_key_index(key); diff --git a/components/salsa-2022/src/function/maybe_changed_after.rs b/components/salsa-2022/src/function/maybe_changed_after.rs index 6f8a8c6d9..c2a7cd75d 100644 --- a/components/salsa-2022/src/function/maybe_changed_after.rs +++ b/components/salsa-2022/src/function/maybe_changed_after.rs @@ -18,7 +18,12 @@ impl FunctionIngredient where C: Configuration, { - pub(super) fn maybe_changed_after(&self, db: &DynDb, key: Id, revision: Revision) -> bool { + pub(super) fn maybe_changed_after<'db>( + &'db self, + db: &'db DynDb<'db, C>, + key: Id, + revision: Revision, + ) -> bool { let runtime = db.runtime(); runtime.unwind_if_revision_cancelled(db); @@ -50,9 +55,9 @@ where } } - fn maybe_changed_after_cold( - &self, - db: &DynDb, + fn maybe_changed_after_cold<'db>( + &'db self, + db: &'db DynDb<'db, C>, key_index: Id, revision: Revision, ) -> Option { @@ -104,7 +109,7 @@ where db: &DynDb, runtime: &Runtime, database_key_index: DatabaseKeyIndex, - memo: &Memo, + memo: &Memo>, ) -> bool { let verified_at = memo.verified_at.load(); let revision_now = runtime.current_revision(); @@ -142,7 +147,7 @@ where pub(super) fn deep_verify_memo( &self, db: &DynDb, - old_memo: &Memo, + old_memo: &Memo>, active_query: &ActiveQueryGuard<'_>, ) -> bool { let runtime = db.runtime(); diff --git a/components/salsa-2022/src/function/memo.rs b/components/salsa-2022/src/function/memo.rs index 200029499..45182b6ed 100644 --- a/components/salsa-2022/src/function/memo.rs +++ b/components/salsa-2022/src/function/memo.rs @@ -4,18 +4,23 @@ use arc_swap::{ArcSwap, Guard}; use crossbeam_utils::atomic::AtomicCell; use crate::{ - hash::FxDashMap, key::DatabaseKeyIndex, runtime::local_state::QueryRevisions, AsId, Event, - EventKind, Revision, Runtime, + hash::FxDashMap, key::DatabaseKeyIndex, runtime::local_state::QueryRevisions, Event, EventKind, + Id, Revision, Runtime, }; +use super::Configuration; + /// The memo map maps from a key of type `K` to the memoized value for that `K`. /// The memoized value is a `Memo` which contains, in addition to the value `V`, /// dependency information. -pub(super) struct MemoMap { - map: FxDashMap>>, +pub(super) struct MemoMap { + map: FxDashMap>, } -impl Default for MemoMap { +#[allow(type_alias_bounds)] +type ArcMemo<'lt, C: Configuration> = ArcSwap::Value<'lt>>>; + +impl Default for MemoMap { fn default() -> Self { Self { map: Default::default(), @@ -23,30 +28,56 @@ impl Default for MemoMap { } } -impl MemoMap { +impl MemoMap { + /// Memos have to be stored internally using `'static` as the database lifetime. + /// This (unsafe) function call converts from something tied to self to static. + /// Values transmuted this way have to be transmuted back to being tied to self + /// when they are returned to the user. + unsafe fn to_static<'db>(&'db self, value: ArcMemo<'db, C>) -> ArcMemo<'static, C> { + unsafe { std::mem::transmute(value) } + } + + /// Convert from an internal memo (which uses statis) to one tied to self + /// so it can be publicly released. + unsafe fn to_self<'db>(&'db self, value: ArcMemo<'static, C>) -> ArcMemo<'db, C> { + unsafe { std::mem::transmute(value) } + } /// Inserts the memo for the given key; (atomically) overwrites any previously existing memo.- #[must_use] - pub(super) fn insert(&self, key: K, memo: Arc>) -> Option>> { - self.map.insert(key, ArcSwap::from(memo)) + pub(super) fn insert<'db>( + &'db self, + key: Id, + memo: Arc>>, + ) -> Option>>> { + unsafe { + let value = ArcSwap::from(memo); + let old_value = self.map.insert(key, self.to_static(value))?; + Some(self.to_self(old_value)) + } } /// Removes any existing memo for the given key. #[must_use] - pub(super) fn remove(&self, key: K) -> Option>> { - self.map.remove(&key).map(|o| o.1) + pub(super) fn remove<'db>(&'db self, key: Id) -> Option>>> { + unsafe { self.map.remove(&key).map(|o| self.to_self(o.1)) } } /// Loads the current memo for `key_index`. This does not hold any sort of /// lock on the `memo_map` once it returns, so this memo could immediately /// become outdated if other threads store into the `memo_map`. - pub(super) fn get(&self, key: K) -> Option>>> { - self.map.get(&key).map(|v| v.load()) + pub(super) fn get<'db>(&self, key: Id) -> Option>>>> { + self.map.get(&key).map(|v| unsafe { + std::mem::transmute::< + Guard>>>, + Guard>>>, + >(v.load()) + }) } /// Evicts the existing memo for the given key, replacing it /// with an equivalent memo that has no value. If the memo is untracked, BaseInput, /// or has values assigned as output of another query, this has no effect. - pub(super) fn evict(&self, key: K) { + pub(super) fn evict(&self, key: Id) { use crate::runtime::local_state::QueryOrigin; use dashmap::mapref::entry::Entry::*; @@ -64,7 +95,7 @@ impl MemoMap { QueryOrigin::Derived(_) => { let memo_evicted = Arc::new(Memo::new( - None::, + None::>, memo.verified_at.load(), memo.revisions.clone(), )); diff --git a/components/salsa-2022/src/function/specify.rs b/components/salsa-2022/src/function/specify.rs index 9916deba3..3009635c4 100644 --- a/components/salsa-2022/src/function/specify.rs +++ b/components/salsa-2022/src/function/specify.rs @@ -18,13 +18,13 @@ where /// This is a way to imperatively set the value of a function. /// It only works if the key is a tracked struct created in the current query. fn specify<'db>( - &self, + &'db self, db: &'db DynDb<'db, C>, key: Id, - value: C::Value, + value: C::Value<'db>, origin: impl Fn(DatabaseKeyIndex) -> QueryOrigin, ) where - C::Input: TrackedStructInDb>, + C::Input<'db>: TrackedStructInDb>, { let runtime = db.runtime(); @@ -45,7 +45,7 @@ where // * Q4 invokes Q2 and then Q1 // // Now, if We invoke Q3 first, We get one result for Q2, but if We invoke Q4 first, We get a different value. That's no good. - let database_key_index = ::database_key_index(db, key); + let database_key_index = >::database_key_index(db, key); let dependency_index = database_key_index.into(); if !runtime.is_output_of_active_query(dependency_index) { panic!("can only use `specfiy` on entities created during current query"); @@ -94,9 +94,9 @@ where /// Specify the value for `key` *and* record that we did so. /// Used for explicit calls to `specify`, but not needed for pre-declared tracked struct fields. - pub fn specify_and_record<'db>(&self, db: &'db DynDb<'db, C>, key: Id, value: C::Value) + pub fn specify_and_record<'db>(&'db self, db: &'db DynDb<'db, C>, key: Id, value: C::Value<'db>) where - C::Input: TrackedStructInDb>, + C::Input<'db>: TrackedStructInDb>, { self.specify(db, key, value, |database_key_index| { QueryOrigin::Assigned(database_key_index) diff --git a/components/salsa-2022/src/function/store.rs b/components/salsa-2022/src/function/store.rs index f09fca000..c0d17e509 100644 --- a/components/salsa-2022/src/function/store.rs +++ b/components/salsa-2022/src/function/store.rs @@ -14,11 +14,11 @@ impl FunctionIngredient where C: Configuration, { - pub fn store( - &mut self, + pub fn store<'db>( + &'db mut self, runtime: &mut Runtime, key: Id, - value: C::Value, + value: C::Value<'db>, durability: Durability, ) { let revision = runtime.current_revision(); diff --git a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr index 60fb56316..9553bb0ea 100644 --- a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr +++ b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr @@ -8,9 +8,9 @@ error[E0277]: the trait bound `MyInput: TrackedStructInDb` is not satisf note: required by a bound in `function::specify::>::specify_and_record` --> $WORKSPACE/components/salsa-2022/src/function/specify.rs | - | pub fn specify_and_record<'db>(&self, db: &'db DynDb<'db, C>, key: Id, value: C::Value) + | pub fn specify_and_record<'db>(&'db self, db: &'db DynDb<'db, C>, key: Id, value: C::Value<'db>) | ------------------ required by a bound in this associated function | where - | C::Input: TrackedStructInDb>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `function::specify::>::specify_and_record` + | C::Input<'db>: TrackedStructInDb>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `function::specify::>::specify_and_record` = note: this error originates in the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr index a6afbf0a8..1b5064381 100644 --- a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr +++ b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr @@ -8,9 +8,9 @@ error[E0277]: the trait bound `MyInterned: TrackedStructInDb` is not sat note: required by a bound in `function::specify::>::specify_and_record` --> $WORKSPACE/components/salsa-2022/src/function/specify.rs | - | pub fn specify_and_record<'db>(&self, db: &'db DynDb<'db, C>, key: Id, value: C::Value) + | pub fn specify_and_record<'db>(&'db self, db: &'db DynDb<'db, C>, key: Id, value: C::Value<'db>) | ------------------ required by a bound in this associated function | where - | C::Input: TrackedStructInDb>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `function::specify::>::specify_and_record` + | C::Input<'db>: TrackedStructInDb>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `function::specify::>::specify_and_record` = note: this error originates in the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) From e95c8b21fb687aca0e576b8d6e5b21eeffcfaf5a Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 27 Apr 2024 14:42:15 -0400 Subject: [PATCH 14/61] give fields a lifetime --- .../salsa-2022-macros/src/tracked_struct.rs | 10 +++---- components/salsa-2022/src/tracked_struct.rs | 26 ++++++++++++------- .../src/tracked_struct/tracked_field.rs | 8 ++++-- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 60043ddd5..046981fce 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -135,11 +135,11 @@ impl TrackedStruct { parse_quote! { impl salsa::tracked_struct::Configuration for #config_ident { - type Fields = ( #(#field_tys,)* ); + type Fields<'db> = ( #(#field_tys,)* ); type Revisions = [salsa::Revision; #arity]; #[allow(clippy::unused_unit)] - fn id_fields(fields: &Self::Fields) -> impl std::hash::Hash { + fn id_fields(fields: &Self::Fields<'_>) -> impl std::hash::Hash { ( #( &fields.#id_field_indices ),* ) } @@ -151,11 +151,11 @@ impl TrackedStruct { [current_revision; #arity] } - unsafe fn update_fields( + unsafe fn update_fields<'db>( #current_revision: salsa::Revision, #revisions: &mut Self::Revisions, - #old_fields: *mut Self::Fields, - #new_fields: Self::Fields, + #old_fields: *mut Self::Fields<'db>, + #new_fields: Self::Fields<'db>, ) { use salsa::update::helper::Fallback as _; #update_fields diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 5520b0b67..04dff1bcb 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -24,7 +24,7 @@ mod tracked_field; /// to a struct. pub trait Configuration { /// A (possibly empty) tuple of the fields for this struct. - type Fields; + type Fields<'db>; /// A array of [`Revision`][] values, one per each of the value fields. /// When a struct is re-recreated in a new revision, the corresponding @@ -32,7 +32,7 @@ pub trait Configuration { /// values have changed (or if the field is marked as `#[no_eq]`). type Revisions; - fn id_fields(fields: &Self::Fields) -> impl Hash; + 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. @@ -62,11 +62,11 @@ pub trait Configuration { /// Ensures that `old_fields` is fully updated and valid /// after it returns and that `revisions` has been updated /// for any field that changed. - unsafe fn update_fields( + unsafe fn update_fields<'db>( current_revision: Revision, revisions: &mut Self::Revisions, - old_fields: *mut Self::Fields, - new_fields: Self::Fields, + old_fields: *mut Self::Fields<'db>, + new_fields: Self::Fields<'db>, ); } // ANCHOR_END: Configuration @@ -140,7 +140,7 @@ where /// Fields of this tracked struct. They can change across revisions, /// but they do not change within a particular revision. - fields: C::Fields, + fields: C::Fields<'static>, /// 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 @@ -156,6 +156,14 @@ impl TrackedStructIngredient where C: Configuration, { + unsafe fn to_static<'db>(&'db self, fields: C::Fields<'db>) -> C::Fields<'static> { + unsafe { std::mem::transmute(fields) } + } + + unsafe fn to_self_ptr<'db>(&'db self, fields: *mut C::Fields<'static>) -> *mut C::Fields<'db> { + unsafe { std::mem::transmute(fields) } + } + pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self { Self { interned: InternedIngredient::new(index, debug_name), @@ -199,7 +207,7 @@ where pub fn new_struct<'db>( &'db self, runtime: &'db Runtime, - fields: C::Fields, + fields: C::Fields<'db>, ) -> &'db TrackedStructValue { let data_hash = crate::hash::hash(&C::id_fields(&fields)); @@ -226,7 +234,7 @@ where struct_ingredient_index: self.struct_ingredient_index(), created_at: current_revision, durability: current_deps.durability, - fields, + fields: unsafe { self.to_static(fields) }, revisions: C::new_revisions(current_deps.changed_at), }, ) @@ -254,7 +262,7 @@ where C::update_fields( current_revision, &mut data.revisions, - std::ptr::addr_of_mut!(data.fields), + self.to_self_ptr(std::ptr::addr_of_mut!(data.fields)), fields, ); } diff --git a/components/salsa-2022/src/tracked_struct/tracked_field.rs b/components/salsa-2022/src/tracked_struct/tracked_field.rs index 39c8f03fd..03451fe4b 100644 --- a/components/salsa-2022/src/tracked_struct/tracked_field.rs +++ b/components/salsa-2022/src/tracked_struct/tracked_field.rs @@ -31,10 +31,14 @@ impl TrackedFieldIngredient where C: Configuration, { + unsafe fn to_self_ref<'db>(&'db self, fields: &'db C::Fields<'static>) -> &'db C::Fields<'db> { + unsafe { std::mem::transmute(fields) } + } + /// 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: Id) -> &'db C::Fields { + pub fn field<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db C::Fields<'db> { let data = self.struct_map.get(runtime, id); let changed_at = C::revision(&data.revisions, self.field_index); @@ -48,7 +52,7 @@ where changed_at, ); - &data.fields + unsafe { self.to_self_ref(&data.fields) } } } From a84777d5a7949cfe65cd21a770460fe0da4e0e29 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 27 Apr 2024 16:25:19 -0400 Subject: [PATCH 15/61] permit `<'db>` on tracked struct tracked structs with `'db` carry a pointer and not an id. --- components/salsa-2022-macros/src/input.rs | 20 ++- components/salsa-2022-macros/src/interned.rs | 16 ++- components/salsa-2022-macros/src/jar.rs | 14 +++ .../salsa-2022-macros/src/salsa_struct.rs | 90 +++++++++----- .../salsa-2022-macros/src/tracked_struct.rs | 115 +++++++++++++----- components/salsa-2022/src/tracked_struct.rs | 27 +++- components/salsa-2022/src/update.rs | 4 +- salsa-2022-tests/tests/hello_world.rs | 6 +- 8 files changed, 210 insertions(+), 82 deletions(-) diff --git a/components/salsa-2022-macros/src/input.rs b/components/salsa-2022-macros/src/input.rs index c3a0276bd..aba903980 100644 --- a/components/salsa-2022-macros/src/input.rs +++ b/components/salsa-2022-macros/src/input.rs @@ -49,7 +49,7 @@ impl crate::options::AllowedOptions for InputStruct { impl InputStruct { fn generate_input(&self) -> syn::Result { - let id_struct = self.id_struct(); + let id_struct = self.the_struct_id(); let inherent_impl = self.input_inherent_impl(); let ingredients_for_impl = self.input_ingredients(); let as_id_impl = self.as_id_impl(); @@ -68,7 +68,7 @@ impl InputStruct { /// Generate an inherent impl with methods on the entity type. fn input_inherent_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); + let ident = self.the_ident(); let jar_ty = self.jar_ty(); let db_dyn_ty = self.db_dyn_ty(); let input_index = self.input_index(); @@ -157,6 +157,12 @@ impl InputStruct { } }; + let salsa_id = quote!( + pub fn salsa_id(&self) -> salsa::Id { + self.0 + } + ); + if singleton { let get: syn::ImplItemMethod = parse_quote! { #[track_caller] @@ -188,6 +194,8 @@ impl InputStruct { #(#field_getters)* #(#field_setters)* + + #salsa_id } } } else { @@ -199,6 +207,8 @@ impl InputStruct { #(#field_getters)* #(#field_setters)* + + #salsa_id } } } @@ -212,12 +222,12 @@ impl InputStruct { /// function ingredient for each of the value fields. fn input_ingredients(&self) -> syn::ItemImpl { use crate::literal; - let ident = self.id_ident(); + let ident = self.the_ident(); let field_ty = self.all_field_tys(); let jar_ty = self.jar_ty(); let all_field_indices: Vec = self.all_field_indices(); let input_index: Literal = self.input_index(); - let debug_name_struct = literal(self.id_ident()); + let debug_name_struct = literal(self.the_ident()); let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect(); parse_quote! { @@ -304,7 +314,7 @@ impl InputStruct { /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); + let ident = self.the_ident(); let jar_ty = self.jar_ty(); parse_quote! { impl salsa::salsa_struct::SalsaStructInDb for #ident diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index fd53bde08..8bd91d735 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -54,7 +54,7 @@ impl crate::options::AllowedOptions for InternedStruct { impl InternedStruct { fn generate_interned(&self) -> syn::Result { self.validate_interned()?; - let id_struct = self.id_struct(); + let id_struct = self.the_struct_id(); let data_struct = self.data_struct(); let ingredients_for_impl = self.ingredients_for_impl(); let as_id_impl = self.as_id_impl(); @@ -82,7 +82,7 @@ impl InternedStruct { /// as well as a `new` method. fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl { let vis = self.visibility(); - let id_ident = self.id_ident(); + let id_ident = self.the_ident(); let db_dyn_ty = self.db_dyn_ty(); let jar_ty = self.jar_ty(); @@ -130,12 +130,20 @@ impl InternedStruct { } }; + let salsa_id = quote!( + pub fn salsa_id(&self) -> salsa::Id { + self.0 + } + ); + parse_quote! { #[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] impl #id_ident { #(#field_getters)* #new_method + + #salsa_id } } } @@ -144,7 +152,7 @@ impl InternedStruct { /// /// For a memoized type, the only ingredient is an `InternedIngredient`. fn ingredients_for_impl(&self) -> syn::ItemImpl { - let id_ident = self.id_ident(); + let id_ident = self.the_ident(); let debug_name = crate::literal(id_ident); let jar_ty = self.jar_ty(); let data_ident = self.data_ident(); @@ -177,7 +185,7 @@ impl InternedStruct { /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); + let ident = self.the_ident(); let jar_ty = self.jar_ty(); parse_quote! { impl salsa::salsa_struct::SalsaStructInDb for #ident diff --git a/components/salsa-2022-macros/src/jar.rs b/components/salsa-2022-macros/src/jar.rs index 44b9fbdde..2b10cda48 100644 --- a/components/salsa-2022-macros/src/jar.rs +++ b/components/salsa-2022-macros/src/jar.rs @@ -1,6 +1,7 @@ use proc_macro2::Literal; use syn::punctuated::Punctuated; use syn::spanned::Spanned; +use syn::visit_mut::VisitMut; use syn::{Field, FieldsUnnamed, Ident, ItemStruct, Path, Token}; use crate::options::Options; @@ -147,6 +148,9 @@ fn generate_fields(input: &ItemStruct) -> FieldsUnnamed { // Convert to anonymous fields field.ident = None; + // Convert ty to reference static and not `'_` + ChangeToStatic.visit_type_mut(&mut field.ty); + let field_ty = &field.ty; field.ty = syn::parse2(quote!(< #field_ty as salsa::storage::IngredientsFor >::Ingredients)) @@ -170,3 +174,13 @@ fn generate_fields(input: &ItemStruct) -> FieldsUnnamed { unnamed: output_fields, } } + +struct ChangeToStatic; + +impl syn::visit_mut::VisitMut for ChangeToStatic { + fn visit_lifetime_mut(&mut self, i: &mut syn::Lifetime) { + if i.ident == "_" { + i.ident = syn::Ident::new("static", i.ident.span()); + } + } +} diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index b57b4e1e8..e84236c7e 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -46,6 +46,17 @@ pub enum Customization { const BANNED_FIELD_NAMES: &[&str] = &["from", "new"]; +/// Classifies the kind of field stored in this salsa +/// struct. +#[derive(Debug, PartialEq, Eq)] +pub enum TheStructKind { + /// Stores an "id" + Id, + + /// Stores a "pointer" + Pointer, +} + impl SalsaStruct { pub(crate) fn new( args: proc_macro::TokenStream, @@ -70,6 +81,14 @@ impl SalsaStruct { }) } + pub(crate) fn the_struct_kind(&self) -> TheStructKind { + if self.struct_item.generics.params.is_empty() { + TheStructKind::Id + } else { + TheStructKind::Pointer + } + } + fn extract_customizations(struct_item: &syn::ItemStruct) -> syn::Result> { Ok(struct_item .attrs @@ -142,8 +161,8 @@ impl SalsaStruct { self.all_fields().map(|ef| ef.ty()).collect() } - /// The name of the "identity" struct (this is the name the user gave, e.g., `Foo`). - pub(crate) fn id_ident(&self) -> &syn::Ident { + /// The name of "the struct" (this is the name the user gave, e.g., `Foo`). + pub(crate) fn the_ident(&self) -> &syn::Ident { &self.struct_item.ident } @@ -151,7 +170,7 @@ impl SalsaStruct { /// /// * its list of generic parameters /// * the generics "split for impl". - pub(crate) fn id_ident_and_generics( + pub(crate) fn the_ident_and_generics( &self, ) -> ( &syn::Ident, @@ -195,17 +214,19 @@ impl SalsaStruct { match &self.args.data { Some(d) => d.clone(), None => syn::Ident::new( - &format!("__{}Data", self.id_ident()), - self.id_ident().span(), + &format!("__{}Data", self.the_ident()), + self.the_ident().span(), ), } } - /// Create a struct that wraps the id. + /// Create "the struct" whose field is an id. /// This is the struct the user will refernece, but only if there /// are no lifetimes. - pub(crate) fn id_struct(&self) -> syn::ItemStruct { - let ident = self.id_ident(); + pub(crate) fn the_struct_id(&self) -> syn::ItemStruct { + assert_eq!(self.the_struct_kind(), TheStructKind::Id); + + let ident = self.the_ident(); let visibility = &self.struct_item.vis; // Extract the attributes the user gave, but screen out derive, since we are adding our own, @@ -227,14 +248,11 @@ impl SalsaStruct { /// Create the struct that the user will reference. /// If - pub(crate) fn id_or_ptr_struct( - &self, - config_ident: &syn::Ident, - ) -> syn::Result { + pub(crate) fn the_struct(&self, config_ident: &syn::Ident) -> syn::Result { if self.struct_item.generics.params.is_empty() { - Ok(self.id_struct()) + Ok(self.the_struct_id()) } else { - let ident = self.id_ident(); + let ident = self.the_ident(); let visibility = &self.struct_item.vis; let generics = &self.struct_item.generics; @@ -299,38 +317,43 @@ impl SalsaStruct { pub(crate) fn constructor_name(&self) -> syn::Ident { match self.args.constructor_name.clone() { Some(name) => name, - None => Ident::new("new", self.id_ident().span()), + None => Ident::new("new", self.the_ident().span()), } } /// Generate `impl salsa::AsId for Foo` - pub(crate) fn as_id_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); - let (impl_generics, type_generics, where_clause) = - self.struct_item.generics.split_for_impl(); - parse_quote_spanned! { ident.span() => - impl #impl_generics salsa::AsId for #ident #type_generics - #where_clause - { - fn as_id(self) -> salsa::Id { - self.0 - } + pub(crate) fn as_id_impl(&self) -> Option { + match self.the_struct_kind() { + TheStructKind::Id => { + let ident = self.the_ident(); + let (impl_generics, type_generics, where_clause) = + self.struct_item.generics.split_for_impl(); + Some(parse_quote_spanned! { ident.span() => + impl #impl_generics salsa::AsId for #ident #type_generics + #where_clause + { + fn as_id(self) -> salsa::Id { + self.0 + } + + fn from_id(id: salsa::Id) -> Self { + #ident(id) + } + } - fn from_id(id: salsa::Id) -> Self { - #ident(id) - } + }) } - + TheStructKind::Pointer => None, } } - /// Generate `impl salsa::DebugWithDb for Foo` + /// Generate `impl salsa::DebugWithDb for Foo`, but only if this is an id struct. pub(crate) fn as_debug_with_db_impl(&self) -> Option { if self.customizations.contains(&Customization::DebugWithDb) { return None; } - let ident = self.id_ident(); + let ident = self.the_ident(); let (impl_generics, type_generics, where_clause) = self.struct_item.generics.split_for_impl(); @@ -366,8 +389,9 @@ impl SalsaStruct { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>, _db: &#db_type) -> ::std::fmt::Result { #[allow(unused_imports)] use ::salsa::debug::helper::Fallback; + #[allow(unused_mut)] let mut debug_struct = &mut f.debug_struct(#ident_string); - debug_struct = debug_struct.field("[salsa id]", &self.0.as_u32()); + debug_struct = debug_struct.field("[salsa id]", &self.salsa_id().as_u32()); #fields debug_struct.finish() } diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 046981fce..7134f9cf3 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -1,6 +1,6 @@ use proc_macro2::{Literal, Span, TokenStream}; -use crate::salsa_struct::{SalsaField, SalsaStruct}; +use crate::salsa_struct::{SalsaField, SalsaStruct, TheStructKind}; /// For an tracked struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate... /// @@ -11,7 +11,10 @@ pub(crate) fn tracked( args: proc_macro::TokenStream, struct_item: syn::ItemStruct, ) -> syn::Result { - SalsaStruct::with_struct(args, struct_item).and_then(|el| TrackedStruct(el).generate_tracked()) + let tokens = SalsaStruct::with_struct(args, struct_item) + .and_then(|el| TrackedStruct(el).generate_tracked())?; + + Ok(tokens) } struct TrackedStruct(SalsaStruct); @@ -51,7 +54,7 @@ impl TrackedStruct { self.validate_tracked()?; let config_struct = self.config_struct(); - let the_struct = self.id_or_ptr_struct(&config_struct.ident)?; + let the_struct = self.the_struct(&config_struct.ident)?; let config_impl = self.config_impl(&config_struct); let inherent_impl = self.tracked_inherent_impl(); let ingredients_for_impl = self.tracked_struct_ingredients(&config_struct); @@ -80,8 +83,8 @@ impl TrackedStruct { fn config_struct(&self) -> syn::ItemStruct { let config_ident = syn::Ident::new( - &format!("__{}Config", self.id_ident()), - self.id_ident().span(), + &format!("__{}Config", self.the_ident()), + self.the_ident().span(), ); let visibility = self.visibility(); @@ -166,7 +169,9 @@ impl TrackedStruct { /// Generate an inherent impl with methods on the tracked type. fn tracked_inherent_impl(&self) -> syn::ItemImpl { - let (ident, _, impl_generics, type_generics, where_clause) = self.id_ident_and_generics(); + let (ident, parameters, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); + + let lt_db = parameters.iter().next(); let jar_ty = self.jar_ty(); let db_dyn_ty = self.db_dyn_ty(); @@ -178,25 +183,53 @@ impl TrackedStruct { 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 = 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_spanned! { field_get_name.span() => - #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.#tracked_field_ingredients[#field_index].field(__runtime, self.0).#field_index + match self.the_struct_kind() { + TheStructKind::Id => { + if !*is_clone_field { + parse_quote_spanned! { field_get_name.span() => + #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 #type_generics >>::ingredient(__jar); + &__ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self.0).#field_index + } + } + } else { + parse_quote_spanned! { field_get_name.span() => + #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 #type_generics >>::ingredient(__jar); + __ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self.0).#field_index.clone() + } + } } } - } else { - parse_quote_spanned! { field_get_name.span() => - #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.#tracked_field_ingredients[#field_index].field(__runtime, self.0).#field_index.clone() + + TheStructKind::Pointer => { + let lt_db = lt_db.unwrap(); + + if !*is_clone_field { + parse_quote_spanned! { field_get_name.span() => + #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> & #lt_db #field_ty + { + let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); + let fields = unsafe { &*self.0 }.field(__runtime, #field_index); + &fields.#field_index + } + } + } else { + parse_quote_spanned! { field_get_name.span() => + #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> #field_ty + { + let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); + let fields = unsafe { &*self.0 }.field(__runtime, #field_index); + fields.#field_index.clone() + } + } } } - } + } ) .collect(); @@ -204,6 +237,18 @@ impl TrackedStruct { let field_tys = self.all_field_tys(); let constructor_name = self.constructor_name(); + let data = syn::Ident::new("__data", Span::call_site()); + + let salsa_id = match self.the_struct_kind() { + TheStructKind::Id => quote!(self.0), + TheStructKind::Pointer => quote!(unsafe { &*self.0 }.id()), + }; + + let ctor = match self.the_struct_kind() { + TheStructKind::Id => quote!(salsa::AsId::from_id(#data.id())), + TheStructKind::Pointer => quote!(Self(#data, std::marker::PhantomData)), + }; + parse_quote! { #[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] impl #impl_generics #ident #type_generics @@ -211,12 +256,16 @@ impl TrackedStruct { 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 __data = __ingredients.0.new_struct( + let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< Self >>::ingredient(__jar); + let #data = __ingredients.0.new_struct( __runtime, (#(#field_names,)*), ); - Self(__data.id()) + #ctor + } + + pub fn salsa_id(&self) -> salsa::Id { + #salsa_id } #(#field_getters)* @@ -230,14 +279,14 @@ impl TrackedStruct { /// function ingredient for each of the value fields. fn tracked_struct_ingredients(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl { use crate::literal; - let (ident, _, impl_generics, type_generics, where_clause) = self.id_ident_and_generics(); + let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); let jar_ty = self.jar_ty(); let config_struct_name = &config_struct.ident; let field_indices: Vec = 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_struct = literal(self.the_ident()); let debug_name_fields: Vec<_> = self.all_field_names().into_iter().map(literal).collect(); parse_quote! { @@ -299,7 +348,7 @@ impl TrackedStruct { /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { - let (ident, parameters, _, type_generics, where_clause) = self.id_ident_and_generics(); + let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics(); let db = syn::Ident::new("DB", ident.span()); let jar_ty = self.jar_ty(); let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); @@ -311,7 +360,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); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); ingredients.#tracked_struct_ingredient.register_dependent_fn(index) } } @@ -320,7 +369,7 @@ impl TrackedStruct { /// Implementation of `TrackedStructInDb`. fn tracked_struct_in_db_impl(&self) -> syn::ItemImpl { - let (ident, parameters, _, type_generics, where_clause) = self.id_ident_and_generics(); + let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics(); let db = syn::Ident::new("DB", ident.span()); let jar_ty = self.jar_ty(); let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); @@ -332,7 +381,7 @@ impl TrackedStruct { { fn database_key_index(db: &#db, id: salsa::Id) -> salsa::DatabaseKeyIndex { let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); - let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident>>::ingredient(jar); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); ingredients.#tracked_struct_ingredient.database_key_index(id) } } @@ -341,9 +390,11 @@ impl TrackedStruct { /// Implementation of `Update`. fn update_impl(&self) -> syn::ItemImpl { - let ident = self.id_ident(); + let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); parse_quote! { - unsafe impl salsa::update::Update for #ident { + unsafe impl #impl_generics salsa::update::Update for #ident #type_generics + #where_clause + { unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { if unsafe { *old_pointer } != new_value { unsafe { *old_pointer = new_value }; diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 04dff1bcb..38f16b684 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -117,7 +117,6 @@ where C: Configuration, { /// Index of the struct ingredient. - #[allow(dead_code)] struct_ingredient_index: IngredientIndex, /// The id of this struct in the ingredient. @@ -271,7 +270,6 @@ where } data.created_at = current_revision; data.durability = current_deps.durability; - data_ref.freeze() } } @@ -386,4 +384,29 @@ where pub fn id(&self) -> Id { self.id } + + /// 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, field_index: u32) -> &'db C::Fields<'db> { + let field_ingredient_index = IngredientIndex::from( + self.struct_ingredient_index.as_usize() + field_index as usize + 1, + ); + let changed_at = C::revision(&self.revisions, field_index); + + runtime.report_tracked_read( + DependencyIndex { + ingredient_index: field_ingredient_index, + key_index: Some(self.id.as_id()), + }, + self.durability, + changed_at, + ); + + unsafe { self.to_self_ref(&self.fields) } + } + + unsafe fn to_self_ref<'db>(&'db self, fields: &'db C::Fields<'static>) -> &'db C::Fields<'db> { + unsafe { std::mem::transmute(fields) } + } } diff --git a/components/salsa-2022/src/update.rs b/components/salsa-2022/src/update.rs index 4a3d369af..9e6868a8f 100644 --- a/components/salsa-2022/src/update.rs +++ b/components/salsa-2022/src/update.rs @@ -87,9 +87,7 @@ pub fn always_update( new_revision: Revision, old_pointer: &mut T, new_value: T, -) where - T: 'static, -{ +) { *old_revision = new_revision; *old_pointer = new_value; } diff --git a/salsa-2022-tests/tests/hello_world.rs b/salsa-2022-tests/tests/hello_world.rs index 507e36cc3..b3471b5bc 100644 --- a/salsa-2022-tests/tests/hello_world.rs +++ b/salsa-2022-tests/tests/hello_world.rs @@ -7,7 +7,7 @@ use expect_test::expect; use test_log::test; #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, final_result, intermediate_result); +struct Jar(MyInput, MyTracked<'_>, final_result, intermediate_result); trait Db: salsa::DbWithJar + HasLogger {} @@ -23,12 +23,12 @@ fn final_result(db: &dyn Db, input: MyInput) -> u32 { } #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked(jar = Jar)] -fn intermediate_result(db: &dyn Db, input: MyInput) -> MyTracked { +fn intermediate_result<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { db.push_log(format!("intermediate_result({:?})", input)); MyTracked::new(db, input.field(db) / 2) } From fe4ff9816aae1228a3e2a26212a057214a10b8e4 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 29 Apr 2024 21:15:48 -0400 Subject: [PATCH 16/61] support db lifetimes in fields --- components/salsa-2022-macros/src/input.rs | 2 + components/salsa-2022-macros/src/interned.rs | 1 + components/salsa-2022-macros/src/jar.rs | 13 +-- components/salsa-2022-macros/src/lib.rs | 1 + .../salsa-2022-macros/src/salsa_struct.rs | 94 +++++++++++++++++-- .../salsa-2022-macros/src/tracked_struct.rs | 48 +++++----- components/salsa-2022-macros/src/xform.rs | 36 +++++++ components/salsa-2022/src/debug.rs | 2 +- .../tests/tracked_struct_db1_lt.rs | 46 +++++++++ 9 files changed, 201 insertions(+), 42 deletions(-) create mode 100644 components/salsa-2022-macros/src/xform.rs create mode 100644 salsa-2022-tests/tests/tracked_struct_db1_lt.rs diff --git a/components/salsa-2022-macros/src/input.rs b/components/salsa-2022-macros/src/input.rs index aba903980..4f487e472 100644 --- a/components/salsa-2022-macros/src/input.rs +++ b/components/salsa-2022-macros/src/input.rs @@ -49,6 +49,8 @@ impl crate::options::AllowedOptions for InputStruct { impl InputStruct { fn generate_input(&self) -> syn::Result { + self.require_no_generics()?; + let id_struct = self.the_struct_id(); let inherent_impl = self.input_inherent_impl(); let ingredients_for_impl = self.input_ingredients(); diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 8bd91d735..498ae652a 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -75,6 +75,7 @@ impl InternedStruct { fn validate_interned(&self) -> syn::Result<()> { self.disallow_id_fields("interned")?; + self.require_no_generics()?; Ok(()) } diff --git a/components/salsa-2022-macros/src/jar.rs b/components/salsa-2022-macros/src/jar.rs index 2b10cda48..14fd134d2 100644 --- a/components/salsa-2022-macros/src/jar.rs +++ b/components/salsa-2022-macros/src/jar.rs @@ -5,6 +5,7 @@ use syn::visit_mut::VisitMut; use syn::{Field, FieldsUnnamed, Ident, ItemStruct, Path, Token}; use crate::options::Options; +use crate::xform::ChangeLt; // Source: // @@ -149,7 +150,7 @@ fn generate_fields(input: &ItemStruct) -> FieldsUnnamed { field.ident = None; // Convert ty to reference static and not `'_` - ChangeToStatic.visit_type_mut(&mut field.ty); + ChangeLt::elided_to_static().visit_type_mut(&mut field.ty); let field_ty = &field.ty; field.ty = @@ -174,13 +175,3 @@ fn generate_fields(input: &ItemStruct) -> FieldsUnnamed { unnamed: output_fields, } } - -struct ChangeToStatic; - -impl syn::visit_mut::VisitMut for ChangeToStatic { - fn visit_lifetime_mut(&mut self, i: &mut syn::Lifetime) { - if i.ident == "_" { - i.ident = syn::Ident::new("static", i.ident.span()); - } - } -} diff --git a/components/salsa-2022-macros/src/lib.rs b/components/salsa-2022-macros/src/lib.rs index febac410a..a1476b078 100644 --- a/components/salsa-2022-macros/src/lib.rs +++ b/components/salsa-2022-macros/src/lib.rs @@ -47,6 +47,7 @@ mod salsa_struct; mod tracked; mod tracked_fn; mod tracked_struct; +mod xform; #[proc_macro_attribute] pub fn accumulator(args: TokenStream, input: TokenStream) -> TokenStream { diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index e84236c7e..4edf694d3 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -25,7 +25,10 @@ //! * 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::options::{AllowedOptions, Options}; +use crate::{ + options::{AllowedOptions, Options}, + xform::ChangeLt, +}; use proc_macro2::{Ident, Span, TokenStream}; use syn::{ punctuated::Punctuated, spanned::Spanned, token::Comma, GenericParam, ImplGenerics, @@ -53,8 +56,8 @@ pub enum TheStructKind { /// Stores an "id" Id, - /// Stores a "pointer" - Pointer, + /// Stores a "pointer" with the given lifetime + Pointer(syn::Lifetime), } impl SalsaStruct { @@ -81,11 +84,50 @@ impl SalsaStruct { }) } + pub(crate) fn require_no_generics(&self) -> syn::Result<()> { + if let Some(param) = self.struct_item.generics.params.iter().next() { + return Err(syn::Error::new_spanned( + param, + "generic parameters not allowed here", + )); + } + + Ok(()) + } + + pub(crate) fn require_db_lifetime(&self) -> syn::Result<()> { + let generics = &self.struct_item.generics; + + if generics.params.len() == 0 { + return Ok(()); + } + + for (param, index) in generics.params.iter().zip(0..) { + let error = match param { + syn::GenericParam::Lifetime(_) => index > 0, + syn::GenericParam::Type(_) | syn::GenericParam::Const(_) => true, + }; + + if error { + return Err(syn::Error::new_spanned( + param, + "only a single lifetime parameter is accepted", + )); + } + } + + Ok(()) + } + pub(crate) fn the_struct_kind(&self) -> TheStructKind { if self.struct_item.generics.params.is_empty() { TheStructKind::Id } else { - TheStructKind::Pointer + if let Some(lt) = self.struct_item.generics.lifetimes().next() { + TheStructKind::Pointer(lt.lifetime.clone()) + } else { + TheStructKind::Pointer(self.default_db_lifetime()) + } } } @@ -203,8 +245,9 @@ impl SalsaStruct { pub(crate) fn db_dyn_ty(&self) -> syn::Type { let jar_ty = self.jar_ty(); + let lt_db = self.maybe_elided_db_lifetime(); parse_quote! { - <#jar_ty as salsa::jar::Jar<'_>>::DynDb + <#jar_ty as salsa::jar::Jar< #lt_db >>::DynDb } } @@ -321,6 +364,40 @@ impl SalsaStruct { } } + /// Returns the lifetime to use for `'db`. This is normally whatever lifetime + /// parameter the user put on the struct, but it might be a generated default + /// if there is no such parameter. Using the name the user gave is important + /// because it may appear in field types and the like. + pub(crate) fn named_db_lifetime(&self) -> syn::Lifetime { + match self.the_struct_kind() { + TheStructKind::Id => self.default_db_lifetime(), + TheStructKind::Pointer(db) => db, + } + } + + /// Returns lifetime to use for `'db`, substituting `'_` if there is no name required. + /// This is convenient in function signatures where `'db` may not be in scope. + pub(crate) fn maybe_elided_db_lifetime(&self) -> syn::Lifetime { + match self.the_struct_kind() { + TheStructKind::Id => syn::Lifetime { + apostrophe: self.struct_item.ident.span(), + ident: syn::Ident::new("_", self.struct_item.ident.span()), + }, + TheStructKind::Pointer(db) => db, + } + } + + /// Normally we try to use whatever lifetime parameter the use gave us + /// to represent `'db`; but if they didn't give us one, we need to use a default + /// name. We choose `'db`. + fn default_db_lifetime(&self) -> syn::Lifetime { + let span = self.struct_item.ident.span(); + syn::Lifetime { + apostrophe: span, + ident: syn::Ident::new("db", span), + } + } + /// Generate `impl salsa::AsId for Foo` pub(crate) fn as_id_impl(&self) -> Option { match self.the_struct_kind() { @@ -343,7 +420,7 @@ impl SalsaStruct { }) } - TheStructKind::Pointer => None, + TheStructKind::Pointer(_) => None, } } @@ -366,7 +443,8 @@ impl SalsaStruct { .map(|field| -> TokenStream { let field_name_string = field.name().to_string(); let field_getter = field.get_name(); - let field_ty = field.ty(); + let field_ty = ChangeLt::to_elided().in_type(field.ty()); + let db_type = ChangeLt::to_elided().in_type(&db_type); quote_spanned! { field.field.span() => debug_struct = debug_struct.field( @@ -386,7 +464,7 @@ impl SalsaStruct { impl #impl_generics ::salsa::DebugWithDb<#db_type> for #ident #type_generics #where_clause { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>, _db: &#db_type) -> ::std::fmt::Result { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>, _db: & #db_type) -> ::std::fmt::Result { #[allow(unused_imports)] use ::salsa::debug::helper::Fallback; #[allow(unused_mut)] diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 7134f9cf3..3c41e3d38 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -11,9 +11,17 @@ pub(crate) fn tracked( args: proc_macro::TokenStream, struct_item: syn::ItemStruct, ) -> syn::Result { + let struct_name = struct_item.ident.clone(); + let tokens = SalsaStruct::with_struct(args, struct_item) .and_then(|el| TrackedStruct(el).generate_tracked())?; + if let Ok(name) = std::env::var("NDM") { + if name == "*" || name == &struct_name.to_string()[..] { + eprintln!("{}", tokens); + } + } + Ok(tokens) } @@ -51,7 +59,7 @@ impl crate::options::AllowedOptions for TrackedStruct { impl TrackedStruct { fn generate_tracked(&self) -> syn::Result { - self.validate_tracked()?; + self.require_db_lifetime()?; let config_struct = self.config_struct(); let the_struct = self.the_struct(&config_struct.ident)?; @@ -75,11 +83,7 @@ impl TrackedStruct { #as_id_impl #as_debug_with_db_impl }) - } - - fn validate_tracked(&self) -> syn::Result<()> { - Ok(()) - } + } fn config_struct(&self) -> syn::ItemStruct { let config_ident = syn::Ident::new( @@ -100,6 +104,7 @@ impl TrackedStruct { 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(); + let lt_db = &self.named_db_lifetime(); // 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 @@ -138,7 +143,7 @@ impl TrackedStruct { parse_quote! { impl salsa::tracked_struct::Configuration for #config_ident { - type Fields<'db> = ( #(#field_tys,)* ); + type Fields<#lt_db> = ( #(#field_tys,)* ); type Revisions = [salsa::Revision; #arity]; #[allow(clippy::unused_unit)] @@ -154,11 +159,11 @@ impl TrackedStruct { [current_revision; #arity] } - unsafe fn update_fields<'db>( + unsafe fn update_fields<#lt_db>( #current_revision: salsa::Revision, #revisions: &mut Self::Revisions, - #old_fields: *mut Self::Fields<'db>, - #new_fields: Self::Fields<'db>, + #old_fields: *mut Self::Fields<#lt_db>, + #new_fields: Self::Fields<#lt_db>, ) { use salsa::update::helper::Fallback as _; #update_fields @@ -169,9 +174,9 @@ impl TrackedStruct { /// Generate an inherent impl with methods on the tracked type. fn tracked_inherent_impl(&self) -> syn::ItemImpl { - let (ident, parameters, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); + let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); - let lt_db = parameters.iter().next(); + let the_kind = &self.the_struct_kind(); let jar_ty = self.jar_ty(); let db_dyn_ty = self.db_dyn_ty(); @@ -183,7 +188,7 @@ impl TrackedStruct { 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 = 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)| - match self.the_struct_kind() { + match the_kind { TheStructKind::Id => { if !*is_clone_field { parse_quote_spanned! { field_get_name.span() => @@ -206,9 +211,7 @@ impl TrackedStruct { } } - TheStructKind::Pointer => { - let lt_db = lt_db.unwrap(); - + TheStructKind::Pointer(lt_db) => { if !*is_clone_field { parse_quote_spanned! { field_get_name.span() => #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> & #lt_db #field_ty @@ -229,7 +232,7 @@ impl TrackedStruct { } } } - } + } ) .collect(); @@ -239,21 +242,22 @@ impl TrackedStruct { let data = syn::Ident::new("__data", Span::call_site()); - let salsa_id = match self.the_struct_kind() { + let salsa_id = match the_kind { TheStructKind::Id => quote!(self.0), - TheStructKind::Pointer => quote!(unsafe { &*self.0 }.id()), + TheStructKind::Pointer(_) => quote!(unsafe { &*self.0 }.id()), }; - let ctor = match self.the_struct_kind() { + let ctor = match the_kind { TheStructKind::Id => quote!(salsa::AsId::from_id(#data.id())), - TheStructKind::Pointer => quote!(Self(#data, std::marker::PhantomData)), + TheStructKind::Pointer(_) => quote!(Self(#data, std::marker::PhantomData)), }; + let lt_db = self.maybe_elided_db_lifetime(); parse_quote! { #[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] impl #impl_generics #ident #type_generics #where_clause { - pub fn #constructor_name(__db: &#db_dyn_ty, #(#field_names: #field_tys,)*) -> Self + pub fn #constructor_name(__db: &#lt_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< Self >>::ingredient(__jar); diff --git a/components/salsa-2022-macros/src/xform.rs b/components/salsa-2022-macros/src/xform.rs new file mode 100644 index 000000000..ec00d5c5f --- /dev/null +++ b/components/salsa-2022-macros/src/xform.rs @@ -0,0 +1,36 @@ +use syn::visit_mut::VisitMut; + +pub(crate) struct ChangeLt<'a> { + from: Option<&'a str>, + to: &'a str, +} + +impl<'a> ChangeLt<'a> { + pub fn elided_to_static() -> Self { + ChangeLt { + from: Some("_"), + to: "static", + } + } + + pub fn to_elided() -> Self { + ChangeLt { + from: None, + to: "_", + } + } + + pub fn in_type(mut self, ty: &syn::Type) -> syn::Type { + let mut ty = ty.clone(); + self.visit_type_mut(&mut ty); + ty + } +} + +impl syn::visit_mut::VisitMut for ChangeLt<'_> { + fn visit_lifetime_mut(&mut self, i: &mut syn::Lifetime) { + if self.from.map(|f| i.ident == f).unwrap_or(true) { + i.ident = syn::Ident::new(self.to, i.ident.span()); + } + } +} diff --git a/components/salsa-2022/src/debug.rs b/components/salsa-2022/src/debug.rs index 1c2a3a2c3..547d66823 100644 --- a/components/salsa-2022/src/debug.rs +++ b/components/salsa-2022/src/debug.rs @@ -257,7 +257,7 @@ pub mod helper { Db: AsSalsaDatabase, { #[allow(dead_code)] - pub fn salsa_debug<'a, 'b: 'a>(a: &'a T, db: &'b Db) -> DebugWith<'a, Db> { + pub fn salsa_debug<'a>(a: &'a T, db: &'a Db) -> DebugWith<'a, Db> { a.debug(db) } } diff --git a/salsa-2022-tests/tests/tracked_struct_db1_lt.rs b/salsa-2022-tests/tests/tracked_struct_db1_lt.rs new file mode 100644 index 000000000..d71d0cd2a --- /dev/null +++ b/salsa-2022-tests/tests/tracked_struct_db1_lt.rs @@ -0,0 +1,46 @@ +//! Test that tracked structs with lifetimes not named `'db` +//! compile successfully. + +use salsa_2022_tests::{HasLogger, Logger}; + +use test_log::test; + +#[salsa::jar(db = Db)] +struct Jar(MyInput, MyTracked1<'_>, MyTracked2<'_>); + +trait Db: salsa::DbWithJar + HasLogger {} + +#[salsa::input(jar = Jar)] +struct MyInput { + field: u32, +} + +#[salsa::tracked(jar = Jar)] +struct MyTracked1<'db1> { + field: MyTracked2<'db1>, +} + +#[salsa::tracked(jar = Jar)] +struct MyTracked2<'db2> { + field: u32, +} + +#[salsa::db(Jar)] +#[derive(Default)] +struct Database { + storage: salsa::Storage, + logger: Logger, +} + +impl salsa::Database for Database {} + +impl Db for Database {} + +impl HasLogger for Database { + fn logger(&self) -> &Logger { + &self.logger + } +} + +#[test] +fn create_db() {} From 04e041b4a24a666f1a9522bd1ec5ad51ff0a1760 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 May 2024 09:57:38 -0400 Subject: [PATCH 17/61] rework debugging to be more permanent --- components/salsa-2022-macros/src/debug.rs | 23 +++++++++++++++++++ components/salsa-2022-macros/src/lib.rs | 1 + .../salsa-2022-macros/src/tracked_fn.rs | 19 +++++++++------ .../salsa-2022-macros/src/tracked_struct.rs | 8 +------ 4 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 components/salsa-2022-macros/src/debug.rs diff --git a/components/salsa-2022-macros/src/debug.rs b/components/salsa-2022-macros/src/debug.rs new file mode 100644 index 000000000..ed9e2a8be --- /dev/null +++ b/components/salsa-2022-macros/src/debug.rs @@ -0,0 +1,23 @@ +use std::sync::OnceLock; + +use proc_macro2::TokenStream; + +static SALSA_DEBUG_MACRO: OnceLock> = OnceLock::new(); + +pub(crate) fn debug_enabled(input_name: impl ToString) -> bool { + let Some(env_name) = SALSA_DEBUG_MACRO.get_or_init(|| std::env::var("SALSA_DEBUG_MACRO").ok()) + else { + return false; + }; + + let input_name = input_name.to_string(); + env_name == "*" || env_name == &input_name[..] +} + +pub(crate) fn dump_tokens(input_name: impl ToString, tokens: TokenStream) -> TokenStream { + if debug_enabled(input_name) { + eprintln!("{}", tokens); + } + + tokens +} diff --git a/components/salsa-2022-macros/src/lib.rs b/components/salsa-2022-macros/src/lib.rs index a1476b078..0fcdccaf4 100644 --- a/components/salsa-2022-macros/src/lib.rs +++ b/components/salsa-2022-macros/src/lib.rs @@ -39,6 +39,7 @@ pub(crate) fn literal(ident: &proc_macro2::Ident) -> proc_macro2::Literal { mod accumulator; mod configuration; mod db; +mod debug; mod input; mod interned; mod jar; diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index e7a03ccb5..cfd3d9aad 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -10,6 +10,8 @@ pub(crate) fn tracked_fn( args: proc_macro::TokenStream, mut item_fn: syn::ItemFn, ) -> syn::Result { + let fn_ident = item_fn.sig.ident.clone(); + let args: FnArgs = syn::parse(args)?; if item_fn.sig.inputs.is_empty() { return Err(syn::Error::new( @@ -44,14 +46,17 @@ pub(crate) fn tracked_fn( let (config_ty, fn_struct) = fn_struct(&args, &item_fn)?; *item_fn.block = getter_fn(&args, &mut item_fn.sig, item_fn.block.span(), &config_ty)?; - Ok(quote! { - #fn_struct + Ok(crate::debug::dump_tokens( + &fn_ident, + quote! { + #fn_struct - // we generate a `'db` lifetime that clippy - // sometimes doesn't like - #[allow(clippy::needless_lifetimes)] - #item_fn - }) + // we generate a `'db` lifetime that clippy + // sometimes doesn't like + #[allow(clippy::needless_lifetimes)] + #item_fn + }, + )) } type FnArgs = Options; diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 3c41e3d38..479867d9d 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -16,13 +16,7 @@ pub(crate) fn tracked( let tokens = SalsaStruct::with_struct(args, struct_item) .and_then(|el| TrackedStruct(el).generate_tracked())?; - if let Ok(name) = std::env::var("NDM") { - if name == "*" || name == &struct_name.to_string()[..] { - eprintln!("{}", tokens); - } - } - - Ok(tokens) + Ok(crate::debug::dump_tokens(&struct_name, tokens)) } struct TrackedStruct(SalsaStruct); From 4f74037f4122f2eab6786106c8abda7540ee7a36 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 5 May 2024 10:11:21 -0400 Subject: [PATCH 18/61] pipe debug output through rustfmt is there a nicer way to do this?! --- components/salsa-2022-macros/src/debug.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/components/salsa-2022-macros/src/debug.rs b/components/salsa-2022-macros/src/debug.rs index ed9e2a8be..22ba85618 100644 --- a/components/salsa-2022-macros/src/debug.rs +++ b/components/salsa-2022-macros/src/debug.rs @@ -1,3 +1,5 @@ +use std::io::Write; +use std::process::{Command, Stdio}; use std::sync::OnceLock; use proc_macro2::TokenStream; @@ -16,7 +18,22 @@ pub(crate) fn debug_enabled(input_name: impl ToString) -> bool { pub(crate) fn dump_tokens(input_name: impl ToString, tokens: TokenStream) -> TokenStream { if debug_enabled(input_name) { - eprintln!("{}", tokens); + let token_string = tokens.to_string(); + + let _: Result<(), ()> = Command::new("rustfmt") + .arg("--emit=stdout") + .stdin(Stdio::piped()) + .spawn() + .and_then(|mut rustfmt| { + rustfmt + .stdin + .take() + .unwrap() + .write_all(token_string.as_bytes())?; + rustfmt.wait_with_output() + }) + .and_then(|output| Ok(eprintln!("{}", String::from_utf8_lossy(&output.stdout)))) + .or_else(|_| Ok(eprintln!("{token_string}"))); } tokens From 8ba6e606c0d252aa47d273c819b27f514b4e6281 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 6 May 2024 05:43:32 -0400 Subject: [PATCH 19/61] generate configuration struct in salsa_struct It will be shared between tracked structs and interned structs. --- .../salsa-2022-macros/src/salsa_struct.rs | 17 +++++++++++++++++ .../salsa-2022-macros/src/tracked_struct.rs | 14 -------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index 4edf694d3..f4241ef2c 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -119,6 +119,23 @@ impl SalsaStruct { Ok(()) } + /// Some salsa structs require a "Configuration" struct + /// because they make use of GATs. This function + /// synthesizes a name and generates the struct declaration. + pub(crate) fn config_struct(&self) -> syn::ItemStruct { + let config_ident = syn::Ident::new( + &format!("__{}Config", self.the_ident()), + self.the_ident().span(), + ); + let visibility = self.visibility(); + + parse_quote! { + #visibility struct #config_ident { + _uninhabited: std::convert::Infallible, + } + } + } + pub(crate) fn the_struct_kind(&self) -> TheStructKind { if self.struct_item.generics.params.is_empty() { TheStructKind::Id diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 479867d9d..82824d21f 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -79,20 +79,6 @@ impl TrackedStruct { }) } - fn config_struct(&self) -> syn::ItemStruct { - let config_ident = syn::Ident::new( - &format!("__{}Config", self.the_ident()), - self.the_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 config_ident = &config_struct.ident; let field_tys: Vec<_> = self.all_fields().map(SalsaField::ty).collect(); From 54c9586b45a7a3febc5e6f504e4eb55d3ac4e96f Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 6 May 2024 05:49:46 -0400 Subject: [PATCH 20/61] move interned-specific fns out of salsa struct Salsa struct is already a grab-bag, best to keep it to shared functionality. --- components/salsa-2022-macros/src/interned.rs | 34 +++++++++++++++++ .../salsa-2022-macros/src/salsa_struct.rs | 38 ++----------------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 498ae652a..ecfcdbc68 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -79,6 +79,40 @@ impl InternedStruct { Ok(()) } + /// The name of the "data" struct (this comes from the `data = Foo` option or, + /// if that is not provided, by concatenating `Data` to the name of the struct). + fn data_ident(&self) -> syn::Ident { + match &self.args().data { + Some(d) => d.clone(), + None => syn::Ident::new( + &format!("__{}Data", self.the_ident()), + self.the_ident().span(), + ), + } + } + + /// Generates the `struct FooData` struct (or enum). + /// This type inherits all the attributes written by the user. + /// + /// When using named fields, we synthesize the struct and field names. + /// + /// When no named fields are available, copy the existing type. + fn data_struct(&self) -> syn::ItemStruct { + let ident = self.data_ident(); + let visibility = self.visibility(); + let all_field_names = self.all_field_names(); + let all_field_tys = self.all_field_tys(); + parse_quote_spanned! { ident.span() => + /// Internal struct used for interned item + #[derive(Eq, PartialEq, Hash, Clone)] + #visibility struct #ident { + #( + #all_field_names: #all_field_tys, + )* + } + } + } + /// If this is an interned struct, then generate methods to access each field, /// as well as a `new` method. fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl { diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index f4241ef2c..1e3c2a7ec 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -84,6 +84,10 @@ impl SalsaStruct { }) } + pub(crate) fn args(&self) -> &Options { + &self.args + } + pub(crate) fn require_no_generics(&self) -> syn::Result<()> { if let Some(param) = self.struct_item.generics.params.iter().next() { return Err(syn::Error::new_spanned( @@ -268,18 +272,6 @@ impl SalsaStruct { } } - /// The name of the "data" struct (this comes from the `data = Foo` option or, - /// if that is not provided, by concatenating `Data` to the name of the struct). - pub(crate) fn data_ident(&self) -> syn::Ident { - match &self.args.data { - Some(d) => d.clone(), - None => syn::Ident::new( - &format!("__{}Data", self.the_ident()), - self.the_ident().span(), - ), - } - } - /// Create "the struct" whose field is an id. /// This is the struct the user will refernece, but only if there /// are no lifetimes. @@ -346,28 +338,6 @@ impl SalsaStruct { } } - /// Generates the `struct FooData` struct (or enum). - /// This type inherits all the attributes written by the user. - /// - /// When using named fields, we synthesize the struct and field names. - /// - /// When no named fields are available, copy the existing type. - pub(crate) fn data_struct(&self) -> syn::ItemStruct { - let ident = self.data_ident(); - let visibility = self.visibility(); - let all_field_names = self.all_field_names(); - let all_field_tys = self.all_field_tys(); - parse_quote_spanned! { ident.span() => - /// Internal struct used for interned item - #[derive(Eq, PartialEq, Hash, Clone)] - #visibility struct #ident { - #( - #all_field_names: #all_field_tys, - )* - } - } - } - /// Returns the visibility of this item pub(crate) fn visibility(&self) -> &syn::Visibility { &self.struct_item.vis From 97fc6a0920ae8c265a3f2a6bf522d5c661d8087d Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 6 May 2024 06:20:35 -0400 Subject: [PATCH 21/61] rework interning to have a Configuration This will permit GATs so that interned values can carry lifetimes. --- components/salsa-2022-macros/src/interned.rs | 25 ++++++++-- .../salsa-2022-macros/src/tracked_fn.rs | 50 +++++++++++++------ components/salsa-2022/src/interned.rs | 50 ++++++++++++------- components/salsa-2022/src/tracked_struct.rs | 4 ++ 4 files changed, 91 insertions(+), 38 deletions(-) diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index ecfcdbc68..2ce7594a5 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -55,8 +55,10 @@ impl InternedStruct { fn generate_interned(&self) -> syn::Result { self.validate_interned()?; let id_struct = self.the_struct_id(); + let config_struct = self.config_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 named_fields_impl = self.inherent_impl_for_named_fields(); let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl(); @@ -64,6 +66,8 @@ impl InternedStruct { Ok(quote! { #id_struct + #config_struct + #configuration_impl #data_struct #ingredients_for_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, /// as well as a `new` method. fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl { @@ -186,15 +204,14 @@ impl InternedStruct { /// Generates an impl of `salsa::storage::IngredientsFor`. /// /// 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 debug_name = crate::literal(id_ident); let jar_ty = self.jar_ty(); - let data_ident = self.data_ident(); parse_quote! { impl salsa::storage::IngredientsFor for #id_ident { type Jar = #jar_ty; - type Ingredients = salsa::interned::InternedIngredient<#data_ident>; + type Ingredients = salsa::interned::InternedIngredient<#config_struct>; fn create_ingredients( routes: &mut salsa::routes::Routes, diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index cfd3d9aad..8cee8aab1 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -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 config_ty: syn::Type = parse_quote!(#struct_item_ident); 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 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! { #struct_item #configuration_impl + #interned_configuration_impl #ingredients_for_impl #item_impl }, )) } -/// Returns the key type for this tracked function. -/// This is a tuple of all the argument types (apart from the database). -fn key_tuple_ty(item_fn: &syn::ItemFn) -> syn::Type { +fn interned_configuration_impl(item_fn: &syn::ItemFn, config_ty: &syn::Type) -> syn::ItemImpl { let arg_tys = item_fn.sig.inputs.iter().skip(1).map(|arg| match arg { syn::FnArg::Receiver(_) => unreachable!(), syn::FnArg::Typed(pat_ty) => pat_ty.ty.clone(), }); + let intern_data_ty: syn::Type = parse_quote!( + (#(#arg_tys),*) + ); + 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 visibility = &item_fn.vis; - let salsa_struct_ty = salsa_struct_ty(item_fn); let intern_map: syn::Type = match function_type(item_fn) { FunctionType::Constant => { - parse_quote! { salsa::interned::IdentityInterner<()> } + parse_quote! { salsa::interned::IdentityInterner } } FunctionType::SalsaStruct => { - parse_quote! { salsa::interned::IdentityInterner<#salsa_struct_ty> } + parse_quote! { salsa::interned::IdentityInterner } } FunctionType::RequiresInterning => { - let key_ty = key_tuple_ty(item_fn); - parse_quote! { salsa::interned::InternedIngredient<#key_ty> } + parse_quote! { salsa::interned::InternedIngredient } } }; @@ -389,7 +393,24 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { 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::>(); + quote!(#(__key.#indices),*) + }; + 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 // 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 __ingredients = <_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar); - let __key = __ingredients.intern_map.data(__runtime, __id).clone(); - #recovery_fn(__db, __cycle, #(__key.#indices),*) + let #key_var = __ingredients.intern_map.data(__runtime, __id).clone(); + #recovery_fn(__db, __cycle, #key_splat) } }; (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 // 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! { fn execute<'db>(__db: &'db salsa::function::DynDb<'db, Self>, __id: salsa::Id) -> Self::Value<'db> { #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 __ingredients = <_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar); - let __key = __ingredients.intern_map.data(__runtime, __id).clone(); - #inner_fn_name(__db, #(__key.#indices),*) + let #key_var = __ingredients.intern_map.data(__runtime, __id).clone(); + #inner_fn_name(__db, #key_splat) } }; diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index 7ee8493aa..c7639aebd 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -18,25 +18,29 @@ use super::ingredient::Ingredient; use super::routes::IngredientIndex; use super::Revision; +pub trait Configuration { + type Data: InternedData; +} + pub trait InternedData: Sized + Eq + Hash + Clone {} impl InternedData for T {} /// The interned ingredient has the job of hashing values of type `Data` to produce an `Id`. /// It used to store interned structs but also to store the id fields of a tracked struct. /// Interned values endure until they are explicitly removed in some way. -pub struct InternedIngredient { +pub struct InternedIngredient { /// Index of this ingredient in the database (used to construct database-ids, etc). ingredient_index: IngredientIndex, /// Maps from data to the existing interned id for that data. /// /// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa. - key_map: FxDashMap, + key_map: FxDashMap, /// Maps from an interned id to its data. /// /// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa. - value_map: FxDashMap>, + value_map: FxDashMap>, /// counter for the next id. counter: AtomicCell, @@ -52,14 +56,14 @@ pub struct InternedIngredient { /// 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>, + deleted_entries: SegQueue>, debug_name: &'static str, } -impl InternedIngredient +impl InternedIngredient where - Data: InternedData, + C: Configuration, { pub fn new(ingredient_index: IngredientIndex, debug_name: &'static str) -> Self { Self { @@ -78,7 +82,7 @@ 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) { + pub(crate) fn intern_full(&self, runtime: &Runtime, data: C::Data) -> (Id, bool) { runtime.report_tracked_read( DependencyIndex::for_table(self.ingredient_index), 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 } @@ -125,7 +129,7 @@ where } #[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( DependencyIndex::for_table(self.ingredient_index), Durability::MAX, @@ -187,9 +191,9 @@ where } } -impl Ingredient for InternedIngredient +impl Ingredient for InternedIngredient where - Data: InternedData, + C: Configuration, { fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index @@ -247,28 +251,36 @@ where } } -impl IngredientRequiresReset for InternedIngredient +impl IngredientRequiresReset for InternedIngredient where - Data: InternedData, + C: Configuration, { const RESET_ON_NEW_REVISION: bool = false; } -pub struct IdentityInterner { - data: PhantomData, +pub struct IdentityInterner +where + C: Configuration, + C::Data: AsId, +{ + data: PhantomData, } -impl IdentityInterner { +impl IdentityInterner +where + C: Configuration, + C::Data: AsId, +{ #[allow(clippy::new_without_default)] pub fn new() -> Self { 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() } - pub fn data(&self, _runtime: &Runtime, id: crate::Id) -> (Id,) { - (Id::from_id(id),) + pub fn data(&self, _runtime: &Runtime, id: crate::Id) -> C::Data { + ::from_id(id) } } diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 38f16b684..4240a428f 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -110,6 +110,10 @@ struct TrackedStructKey { data_hash: u64, } +impl crate::interned::Configuration for TrackedStructKey { + type Data = TrackedStructKey; +} + // ANCHOR: TrackedStructValue #[derive(Debug)] pub struct TrackedStructValue From 344166617c25d970f982b2c057da90e4973402f3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 13 May 2024 05:13:34 -0400 Subject: [PATCH 22/61] update tests for new error messages also fix name of a fn in one case --- .../input_struct_id_fields_no_setters.stderr | 7 ++- .../tests/compile-fail/span-tracked-getter.rs | 2 +- .../compile-fail/tracked_fn_incompatibles.rs | 33 +++++----- .../tracked_fn_incompatibles.stderr | 60 ++++++++++++------- 4 files changed, 61 insertions(+), 41 deletions(-) diff --git a/salsa-2022-tests/tests/compile-fail/input_struct_id_fields_no_setters.stderr b/salsa-2022-tests/tests/compile-fail/input_struct_id_fields_no_setters.stderr index aa6227de6..51cf0dbc5 100644 --- a/salsa-2022-tests/tests/compile-fail/input_struct_id_fields_no_setters.stderr +++ b/salsa-2022-tests/tests/compile-fail/input_struct_id_fields_no_setters.stderr @@ -5,4 +5,9 @@ error[E0599]: no method named `set_id_one` found for struct `MyInput` in the cur | ------- method `set_id_one` not found for this struct ... 30 | input.set_id_one(1); - | ^^^^^^^^^^ help: there is a method with a similar name: `id_one` + | ^^^^^^^^^^ + | +help: there is a method `id_one` with a similar name + | +30 | input.id_one(1); + | ~~~~~~ diff --git a/salsa-2022-tests/tests/compile-fail/span-tracked-getter.rs b/salsa-2022-tests/tests/compile-fail/span-tracked-getter.rs index 2dbcb9c3c..936ad170c 100644 --- a/salsa-2022-tests/tests/compile-fail/span-tracked-getter.rs +++ b/salsa-2022-tests/tests/compile-fail/span-tracked-getter.rs @@ -25,6 +25,6 @@ fn my_fn(db: &dyn crate::Db) { } fn main() { - let mut db = Database::default(); + let db = Database::default(); my_fn(&db); } diff --git a/salsa-2022-tests/tests/compile-fail/tracked_fn_incompatibles.rs b/salsa-2022-tests/tests/compile-fail/tracked_fn_incompatibles.rs index 7a5fc84eb..3fcc92896 100644 --- a/salsa-2022-tests/tests/compile-fail/tracked_fn_incompatibles.rs +++ b/salsa-2022-tests/tests/compile-fail/tracked_fn_incompatibles.rs @@ -1,5 +1,13 @@ #[salsa::jar(db = Db)] -struct Jar(MyInput, tracked_fn_with_data, tracked_fn_with_db, tracked_fn_with_constructor, tracked_fn_with_one_input, tracked_fn_with_receiver_not_applied_to_impl_block); +struct Jar( + MyInput, + tracked_fn_with_data, + tracked_fn_with_db, + tracked_fn_with_constructor, + tracked_fn_with_one_input, + tracked_fn_with_receiver_not_applied_to_impl_block, + tracked_fn_with_too_many_arguments_for_specify, +); trait Db: salsa::DbWithJar {} @@ -8,7 +16,6 @@ struct MyInput { field: u32, } - #[salsa::tracked(jar = Jar, data = Data)] fn tracked_fn_with_data(db: &dyn Db, input: MyInput) -> u32 { input.field(db) * 2 @@ -24,24 +31,18 @@ fn tracked_fn_with_constructor(db: &dyn Db, input: MyInput) -> u32 { input.field(db) * 2 } - #[salsa::tracked(jar = Jar)] -fn tracked_fn_with_one_input(db: &dyn Db) -> u32 { -} - +fn tracked_fn_with_one_input(db: &dyn Db) -> u32 {} #[salsa::tracked(jar = Jar)] -fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 { -} +fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {} #[salsa::tracked(jar = Jar, specify)] -fn tracked_fn_with_receiver_not_applied_to_impl_block(db: &dyn Db, input: MyInput, input: MyInput) -> u32 { +fn tracked_fn_with_too_many_arguments_for_specify( + db: &dyn Db, + input: MyInput, + input: MyInput, +) -> u32 { } - - - - - - -fn main() {} \ No newline at end of file +fn main() {} diff --git a/salsa-2022-tests/tests/compile-fail/tracked_fn_incompatibles.stderr b/salsa-2022-tests/tests/compile-fail/tracked_fn_incompatibles.stderr index 7a21ce066..46f197336 100644 --- a/salsa-2022-tests/tests/compile-fail/tracked_fn_incompatibles.stderr +++ b/salsa-2022-tests/tests/compile-fail/tracked_fn_incompatibles.stderr @@ -1,56 +1,70 @@ error: `data` option not allowed here - --> tests/compile-fail/tracked_fn_incompatibles.rs:12:29 + --> tests/compile-fail/tracked_fn_incompatibles.rs:19:29 | -12 | #[salsa::tracked(jar = Jar, data = Data)] +19 | #[salsa::tracked(jar = Jar, data = Data)] | ^^^^ error: `db` option not allowed here - --> tests/compile-fail/tracked_fn_incompatibles.rs:17:29 + --> tests/compile-fail/tracked_fn_incompatibles.rs:24:29 | -17 | #[salsa::tracked(jar = Jar, db = Db)] +24 | #[salsa::tracked(jar = Jar, db = Db)] | ^^ error: `constructor` option not allowed here - --> tests/compile-fail/tracked_fn_incompatibles.rs:22:29 + --> tests/compile-fail/tracked_fn_incompatibles.rs:29:29 | -22 | #[salsa::tracked(jar = Jar, constructor = TrackedFn3)] +29 | #[salsa::tracked(jar = Jar, constructor = TrackedFn3)] | ^^^^^^^^^^^ error: #[salsa::tracked] must also be applied to the impl block for tracked methods - --> tests/compile-fail/tracked_fn_incompatibles.rs:34:55 + --> tests/compile-fail/tracked_fn_incompatibles.rs:38:55 | -34 | fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 { +38 | fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {} | ^ error: tracked function takes too many arguments to have its value set with `specify` - --> tests/compile-fail/tracked_fn_incompatibles.rs:37:29 + --> tests/compile-fail/tracked_fn_incompatibles.rs:40:29 | -37 | #[salsa::tracked(jar = Jar, specify)] +40 | #[salsa::tracked(jar = Jar, specify)] | ^^^^^^^ error[E0412]: cannot find type `tracked_fn_with_data` in this scope - --> tests/compile-fail/tracked_fn_incompatibles.rs:2:21 + --> tests/compile-fail/tracked_fn_incompatibles.rs:4:5 | -2 | struct Jar(MyInput, tracked_fn_with_data, tracked_fn_with_db, tracked_fn_with_constructor, tracked_fn_with_one_input, tracked_fn_with_rec... - | ^^^^^^^^^^^^^^^^^^^^ not found in this scope +4 | tracked_fn_with_data, + | ^^^^^^^^^^^^^^^^^^^^ not found in this scope error[E0412]: cannot find type `tracked_fn_with_db` in this scope - --> tests/compile-fail/tracked_fn_incompatibles.rs:2:43 + --> tests/compile-fail/tracked_fn_incompatibles.rs:5:5 | -2 | struct Jar(MyInput, tracked_fn_with_data, tracked_fn_with_db, tracked_fn_with_constructor, tracked_fn_with_one_input, tracked_fn_with_rec... - | ^^^^^^^^^^^^^^^^^^ not found in this scope +5 | tracked_fn_with_db, + | ^^^^^^^^^^^^^^^^^^ not found in this scope error[E0412]: cannot find type `tracked_fn_with_constructor` in this scope - --> tests/compile-fail/tracked_fn_incompatibles.rs:2:63 + --> tests/compile-fail/tracked_fn_incompatibles.rs:6:5 | -2 | struct Jar(MyInput, tracked_fn_with_data, tracked_fn_with_db, tracked_fn_with_constructor, tracked_fn_with_one_input, tracked_fn_with_rec... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: a struct with a similar name exists: `tracked_fn_with_one_input` +6 | tracked_fn_with_constructor, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: a struct with a similar name exists: `tracked_fn_with_one_input` ... -28 | #[salsa::tracked(jar = Jar)] +34 | #[salsa::tracked(jar = Jar)] | ---------------------------- similarly named struct `tracked_fn_with_one_input` defined here error[E0412]: cannot find type `tracked_fn_with_receiver_not_applied_to_impl_block` in this scope - --> tests/compile-fail/tracked_fn_incompatibles.rs:2:119 + --> tests/compile-fail/tracked_fn_incompatibles.rs:8:5 | -2 | ...r, tracked_fn_with_one_input, tracked_fn_with_receiver_not_applied_to_impl_block); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope +8 | tracked_fn_with_receiver_not_applied_to_impl_block, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope + +error[E0412]: cannot find type `tracked_fn_with_too_many_arguments_for_specify` in this scope + --> tests/compile-fail/tracked_fn_incompatibles.rs:9:5 + | +9 | tracked_fn_with_too_many_arguments_for_specify, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope + +error[E0308]: mismatched types + --> tests/compile-fail/tracked_fn_incompatibles.rs:35:46 + | +35 | fn tracked_fn_with_one_input(db: &dyn Db) -> u32 {} + | ------------------------- ^^^ expected `u32`, found `()` + | | + | implicitly returns `()` as its body has no tail or `return` expression From d190bebcacbd62fe9d3e56f397fae911beaad88a Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 13 May 2024 05:30:49 -0400 Subject: [PATCH 23/61] introduce helper functions We'll need these for use with tracked functions --- .../salsa-2022-macros/src/db_lifetime.rs | 48 +++++++++++++++++++ components/salsa-2022-macros/src/lib.rs | 1 + .../salsa-2022-macros/src/salsa_struct.rs | 36 ++------------ 3 files changed, 54 insertions(+), 31 deletions(-) create mode 100644 components/salsa-2022-macros/src/db_lifetime.rs diff --git a/components/salsa-2022-macros/src/db_lifetime.rs b/components/salsa-2022-macros/src/db_lifetime.rs new file mode 100644 index 000000000..7e3d94942 --- /dev/null +++ b/components/salsa-2022-macros/src/db_lifetime.rs @@ -0,0 +1,48 @@ +//! Helper functions for working with fns, structs, and other generic things +//! that are allowed to have a `'db` lifetime. + +use proc_macro2::Span; +use syn::spanned::Spanned; + +/// Normally we try to use whatever lifetime parameter the use gave us +/// to represent `'db`; but if they didn't give us one, we need to use a default +/// name. We choose `'db`. +pub(crate) fn default_db_lifetime(span: Span) -> syn::Lifetime { + syn::Lifetime { + apostrophe: span, + ident: syn::Ident::new("db", span), + } +} + +/// Require that either there are no generics or exactly one lifetime parameter. +pub(crate) fn require_db_lifetime(generics: &syn::Generics) -> syn::Result<()> { + if generics.params.len() == 0 { + return Ok(()); + } + + for (param, index) in generics.params.iter().zip(0..) { + let error = match param { + syn::GenericParam::Lifetime(_) => index > 0, + syn::GenericParam::Type(_) | syn::GenericParam::Const(_) => true, + }; + + if error { + return Err(syn::Error::new_spanned( + param, + "only a single lifetime parameter is accepted", + )); + } + } + + Ok(()) +} + +/// Return the `'db` lifetime given be the user, or a default. +/// The generics ought to have been checked with `require_db_lifetime` already. +pub(crate) fn db_lifetime(generics: &syn::Generics) -> syn::Lifetime { + if let Some(lt) = generics.lifetimes().next() { + lt.lifetime.clone() + } else { + default_db_lifetime(generics.span()) + } +} diff --git a/components/salsa-2022-macros/src/lib.rs b/components/salsa-2022-macros/src/lib.rs index 0fcdccaf4..596cb18b7 100644 --- a/components/salsa-2022-macros/src/lib.rs +++ b/components/salsa-2022-macros/src/lib.rs @@ -39,6 +39,7 @@ pub(crate) fn literal(ident: &proc_macro2::Ident) -> proc_macro2::Literal { mod accumulator; mod configuration; mod db; +mod db_lifetime; mod debug; mod input; mod interned; diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index 1e3c2a7ec..a96a56ce0 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -26,6 +26,7 @@ //! * this could be optimized, particularly for interned fields use crate::{ + db_lifetime::{self, db_lifetime, default_db_lifetime}, options::{AllowedOptions, Options}, xform::ChangeLt, }; @@ -99,28 +100,9 @@ impl SalsaStruct { Ok(()) } + /// Require that either there are no generics or exactly one lifetime parameter. pub(crate) fn require_db_lifetime(&self) -> syn::Result<()> { - let generics = &self.struct_item.generics; - - if generics.params.len() == 0 { - return Ok(()); - } - - for (param, index) in generics.params.iter().zip(0..) { - let error = match param { - syn::GenericParam::Lifetime(_) => index > 0, - syn::GenericParam::Type(_) | syn::GenericParam::Const(_) => true, - }; - - if error { - return Err(syn::Error::new_spanned( - param, - "only a single lifetime parameter is accepted", - )); - } - } - - Ok(()) + db_lifetime::require_db_lifetime(&self.struct_item.generics) } /// Some salsa structs require a "Configuration" struct @@ -144,11 +126,7 @@ impl SalsaStruct { if self.struct_item.generics.params.is_empty() { TheStructKind::Id } else { - if let Some(lt) = self.struct_item.generics.lifetimes().next() { - TheStructKind::Pointer(lt.lifetime.clone()) - } else { - TheStructKind::Pointer(self.default_db_lifetime()) - } + TheStructKind::Pointer(db_lifetime(&self.struct_item.generics)) } } @@ -378,11 +356,7 @@ impl SalsaStruct { /// to represent `'db`; but if they didn't give us one, we need to use a default /// name. We choose `'db`. fn default_db_lifetime(&self) -> syn::Lifetime { - let span = self.struct_item.ident.span(); - syn::Lifetime { - apostrophe: span, - ident: syn::Ident::new("db", span), - } + default_db_lifetime(self.struct_item.generics.span()) } /// Generate `impl salsa::AsId for Foo` From 4822013523e4a7e82819ff667bfd486456728a20 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 13 May 2024 05:35:23 -0400 Subject: [PATCH 24/61] permit interned data to take 'db lifetime --- components/salsa-2022-macros/src/interned.rs | 70 ++++++++++++------- .../salsa-2022-macros/src/tracked_fn.rs | 13 +++- components/salsa-2022/src/interned.rs | 46 +++++++----- components/salsa-2022/src/tracked_struct.rs | 2 +- 4 files changed, 84 insertions(+), 47 deletions(-) diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 2ce7594a5..3667f6c1a 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -54,8 +54,8 @@ impl crate::options::AllowedOptions for InternedStruct { impl InternedStruct { fn generate_interned(&self) -> syn::Result { self.validate_interned()?; - let id_struct = self.the_struct_id(); let config_struct = self.config_struct(); + let the_struct = self.the_struct(&config_struct.ident)?; let data_struct = self.data_struct(); let configuration_impl = self.configuration_impl(&data_struct.ident, &config_struct.ident); let ingredients_for_impl = self.ingredients_for_impl(&config_struct.ident); @@ -65,7 +65,7 @@ impl InternedStruct { let as_debug_with_db_impl = self.as_debug_with_db_impl(); Ok(quote! { - #id_struct + #the_struct #config_struct #configuration_impl #data_struct @@ -79,7 +79,7 @@ impl InternedStruct { fn validate_interned(&self) -> syn::Result<()> { self.disallow_id_fields("interned")?; - self.require_no_generics()?; + self.require_db_lifetime()?; Ok(()) } @@ -102,14 +102,19 @@ impl InternedStruct { /// /// When no named fields are available, copy the existing type. fn data_struct(&self) -> syn::ItemStruct { - let ident = self.data_ident(); + let data_ident = self.data_ident(); + let (_, _, impl_generics, _, where_clause) = self.the_ident_and_generics(); + let visibility = self.visibility(); let all_field_names = self.all_field_names(); let all_field_tys = self.all_field_tys(); - parse_quote_spanned! { ident.span() => + parse_quote_spanned! { data_ident.span() => /// Internal struct used for interned item #[derive(Eq, PartialEq, Hash, Clone)] - #visibility struct #ident { + #visibility struct #data_ident #impl_generics + where + #where_clause + { #( #all_field_names: #all_field_tys, )* @@ -119,14 +124,16 @@ impl InternedStruct { fn configuration_impl( &self, - data_struct: &syn::Ident, - config_struct: &syn::Ident, + data_ident: &syn::Ident, + config_ident: &syn::Ident, ) -> syn::ItemImpl { + let lt_db = &self.named_db_lifetime(); + let (_, _, _, type_generics, _) = self.the_ident_and_generics(); parse_quote_spanned!( - config_struct.span() => + config_ident.span() => - impl salsa::interned::Configuration for #config_struct { - type Data = #data_struct; + impl salsa::interned::Configuration for #config_ident { + type Data<#lt_db> = #data_ident #type_generics; } ) } @@ -134,8 +141,9 @@ impl InternedStruct { /// If this is an interned struct, then generate methods to access each field, /// as well as a `new` method. fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl { - let vis = self.visibility(); - let id_ident = self.the_ident(); + let vis: &syn::Visibility = self.visibility(); + let (the_ident, _, impl_generics, type_generics, where_clause) = + self.the_ident_and_generics(); let db_dyn_ty = self.db_dyn_ty(); let jar_ty = self.jar_ty(); @@ -150,7 +158,7 @@ impl InternedStruct { parse_quote_spanned! { field_get_name.span() => #field_vis fn #field_get_name(self, 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< #id_ident >>::ingredient(jar); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #the_ident #type_generics >>::ingredient(jar); std::clone::Clone::clone(&ingredients.data(runtime, self.0).#field_name) } } @@ -158,7 +166,7 @@ impl InternedStruct { parse_quote_spanned! { field_get_name.span() => #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< #id_ident >>::ingredient(jar); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #the_ident #type_generics >>::ingredient(jar); &ingredients.data(runtime, self.0).#field_name } } @@ -176,7 +184,7 @@ impl InternedStruct { #(#field_names: #field_tys,)* ) -> Self { let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); - let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #id_ident >>::ingredient(jar); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #the_ident #type_generics >>::ingredient(jar); Self(ingredients.intern(runtime, #data_ident { #(#field_names,)* })) @@ -191,7 +199,10 @@ impl InternedStruct { parse_quote! { #[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] - impl #id_ident { + impl #impl_generics #the_ident #type_generics + where + #where_clause + { #(#field_getters)* #new_method @@ -204,14 +215,18 @@ impl InternedStruct { /// Generates an impl of `salsa::storage::IngredientsFor`. /// /// For a memoized type, the only ingredient is an `InternedIngredient`. - fn ingredients_for_impl(&self, config_struct: &syn::Ident) -> syn::ItemImpl { - let id_ident = self.the_ident(); - let debug_name = crate::literal(id_ident); + fn ingredients_for_impl(&self, config_ident: &syn::Ident) -> syn::ItemImpl { + let (the_ident, _, impl_generics, type_generics, where_clause) = + self.the_ident_and_generics(); + let debug_name = crate::literal(the_ident); let jar_ty = self.jar_ty(); parse_quote! { - impl salsa::storage::IngredientsFor for #id_ident { + impl #impl_generics salsa::storage::IngredientsFor for #the_ident #type_generics + where + #where_clause + { type Jar = #jar_ty; - type Ingredients = salsa::interned::InternedIngredient<#config_struct>; + type Ingredients = salsa::interned::InternedIngredient<#config_ident>; fn create_ingredients( routes: &mut salsa::routes::Routes, @@ -237,14 +252,17 @@ impl InternedStruct { /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { - let ident = self.the_ident(); + let (the_ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics(); + #[allow(non_snake_case)] + let DB = syn::Ident::new("DB", the_ident.span()); let jar_ty = self.jar_ty(); parse_quote! { - impl salsa::salsa_struct::SalsaStructInDb for #ident + impl<#DB, #parameters> salsa::salsa_struct::SalsaStructInDb for #the_ident #type_generics where - DB: ?Sized + salsa::DbWithJar<#jar_ty>, + #DB: ?Sized + salsa::DbWithJar<#jar_ty>, + #where_clause { - fn register_dependent_fn(_db: &DB, _index: salsa::routes::IngredientIndex) { + fn register_dependent_fn(_db: &#DB, _index: salsa::routes::IngredientIndex) { // Do nothing here, at least for now. // If/when we add ability to delete inputs, this would become relevant. } diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index 8cee8aab1..0cdafecb2 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -4,6 +4,7 @@ use syn::visit_mut::VisitMut; use syn::{ReturnType, Token}; use crate::configuration::{self, Configuration, CycleRecoveryStrategy}; +use crate::db_lifetime::{db_lifetime, require_db_lifetime}; use crate::options::Options; pub(crate) fn tracked_fn( @@ -288,12 +289,14 @@ fn rename_self_in_block(mut block: syn::Block) -> syn::Result { /// /// This returns the name of the constructed type and the code defining everything. fn fn_struct(args: &FnArgs, item_fn: &syn::ItemFn) -> syn::Result<(syn::Type, TokenStream)> { + require_db_lifetime(&item_fn.sig.generics)?; + let db_lt = &db_lifetime(&item_fn.sig.generics); let struct_item = configuration_struct(item_fn); let configuration = fn_configuration(args, item_fn); let struct_item_ident = &struct_item.ident; let config_ty: syn::Type = parse_quote!(#struct_item_ident); let configuration_impl = configuration.to_impl(&config_ty); - let interned_configuration_impl = interned_configuration_impl(item_fn, &config_ty); + let interned_configuration_impl = interned_configuration_impl(db_lt, 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)?; @@ -309,7 +312,11 @@ fn fn_struct(args: &FnArgs, item_fn: &syn::ItemFn) -> syn::Result<(syn::Type, To )) } -fn interned_configuration_impl(item_fn: &syn::ItemFn, config_ty: &syn::Type) -> syn::ItemImpl { +fn interned_configuration_impl( + db_lt: &syn::Lifetime, + item_fn: &syn::ItemFn, + config_ty: &syn::Type, +) -> syn::ItemImpl { let arg_tys = item_fn.sig.inputs.iter().skip(1).map(|arg| match arg { syn::FnArg::Receiver(_) => unreachable!(), syn::FnArg::Typed(pat_ty) => pat_ty.ty.clone(), @@ -321,7 +328,7 @@ fn interned_configuration_impl(item_fn: &syn::ItemFn, config_ty: &syn::Type) -> parse_quote!( impl salsa::interned::Configuration for #config_ty { - type Data = #intern_data_ty; + type Data<#db_lt> = #intern_data_ty; } ) } diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index c7639aebd..5301812fd 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -8,7 +8,6 @@ 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, Id}; @@ -19,7 +18,7 @@ use super::routes::IngredientIndex; use super::Revision; pub trait Configuration { - type Data: InternedData; + type Data<'db>: InternedData; } pub trait InternedData: Sized + Eq + Hash + Clone {} @@ -35,12 +34,12 @@ pub struct InternedIngredient { /// Maps from data to the existing interned id for that data. /// /// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa. - key_map: FxDashMap, + key_map: FxDashMap, Id>, /// Maps from an interned id to its data. /// /// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa. - value_map: FxDashMap>, + value_map: FxDashMap>>, /// counter for the next id. counter: AtomicCell, @@ -56,7 +55,7 @@ pub struct InternedIngredient { /// 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>, + deleted_entries: SegQueue>>, debug_name: &'static str, } @@ -77,12 +76,24 @@ where } } + unsafe fn to_internal_data<'db>(&'db self, data: C::Data<'db>) -> C::Data<'static> { + unsafe { std::mem::transmute(data) } + } + + unsafe fn from_internal_data<'db>(&'db self, data: &C::Data<'static>) -> &'db C::Data<'db> { + unsafe { std::mem::transmute(data) } + } + /// 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: C::Data) -> (Id, bool) { + pub(crate) fn intern_full<'db>( + &'db self, + runtime: &'db Runtime, + data: C::Data<'db>, + ) -> (Id, bool) { runtime.report_tracked_read( DependencyIndex::for_table(self.ingredient_index), Durability::MAX, @@ -91,18 +102,19 @@ 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) { + let internal_data = unsafe { self.to_internal_data(data) }; + if let Some(id) = self.key_map.get(&internal_data) { return (*id, false); } - match self.key_map.entry(data.clone()) { + match self.key_map.entry(internal_data.clone()) { // Data has been interned by a racing call, use that ID instead 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); let next_id = Id::from_id(crate::id::Id::from_u32(next_id)); - let old_value = self.value_map.insert(next_id, Box::new(data)); + let old_value = self.value_map.insert(next_id, Box::new(internal_data)); assert!( old_value.is_none(), "next_id is guaranteed to be unique, bar overflow" @@ -113,7 +125,7 @@ where } } - pub fn intern(&self, runtime: &Runtime, data: C::Data) -> Id { + pub fn intern<'db>(&'db self, runtime: &'db Runtime, data: C::Data<'db>) -> Id { self.intern_full(runtime, data).0 } @@ -129,7 +141,7 @@ where } #[track_caller] - pub fn data<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db C::Data { + pub fn data<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db C::Data<'db> { runtime.report_tracked_read( DependencyIndex::for_table(self.ingredient_index), Durability::MAX, @@ -146,7 +158,7 @@ where // Unsafety clause: // // * Values are only removed or altered when we have `&mut self` - unsafe { transmute_lifetime(self, &**data) } + unsafe { self.from_internal_data(&data) } } /// Get the ingredient index for this table. @@ -261,7 +273,7 @@ where pub struct IdentityInterner where C: Configuration, - C::Data: AsId, + for<'db> C::Data<'db>: AsId, { data: PhantomData, } @@ -269,18 +281,18 @@ where impl IdentityInterner where C: Configuration, - C::Data: AsId, + for<'db> C::Data<'db>: AsId, { #[allow(clippy::new_without_default)] pub fn new() -> Self { IdentityInterner { data: PhantomData } } - pub fn intern(&self, _runtime: &Runtime, id: C::Data) -> crate::Id { + pub fn intern<'db>(&'db self, _runtime: &'db Runtime, id: C::Data<'db>) -> crate::Id { id.as_id() } - pub fn data(&self, _runtime: &Runtime, id: crate::Id) -> C::Data { - ::from_id(id) + pub fn data<'db>(&'db self, _runtime: &'db Runtime, id: crate::Id) -> C::Data<'db> { + >::from_id(id) } } diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 4240a428f..c9fdb1345 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -111,7 +111,7 @@ struct TrackedStructKey { } impl crate::interned::Configuration for TrackedStructKey { - type Data = TrackedStructKey; + type Data<'db> = TrackedStructKey; } // ANCHOR: TrackedStructValue From d6d522662c5383b06f0260f5c89d4cc4892b26b6 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 14 May 2024 06:17:55 -0400 Subject: [PATCH 25/61] have tracked struct intern its own keys Previously tracked structs relied on an interned ingredient to intern their keys. But really it has more complex logic than we need. Simpler to just remove it and duplicate the basic concept. --- components/salsa-2022/src/interned.rs | 77 +----------- components/salsa-2022/src/tracked_struct.rs | 111 ++++++++++++++---- .../src/tracked_struct/struct_map.rs | 8 +- 3 files changed, 98 insertions(+), 98 deletions(-) diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index 5301812fd..b912ed860 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -1,5 +1,4 @@ use crossbeam::atomic::AtomicCell; -use crossbeam::queue::SegQueue; use std::fmt; use std::hash::Hash; use std::marker::PhantomData; @@ -50,13 +49,6 @@ pub struct InternedIngredient { /// of being recomputed. reset_at: Revision, - /// When specific entries are deleted from the interned table, their data is added - /// to this vector rather than being immediately freed. This is because we may` have - /// references to that data floating about that are tied to the lifetime of some - /// `&db` reference. This queue itself is not freed until we have an `&mut db` reference, - /// guaranteeing that there are no more references to it. - deleted_entries: SegQueue>>, - debug_name: &'static str, } @@ -71,7 +63,6 @@ where value_map: Default::default(), counter: AtomicCell::default(), reset_at: Revision::start(), - deleted_entries: Default::default(), debug_name, } } @@ -84,16 +75,8 @@ where unsafe { std::mem::transmute(data) } } - /// 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<'db>( - &'db self, - runtime: &'db Runtime, - data: C::Data<'db>, - ) -> (Id, bool) { + /// Intern data to a unique id. + pub fn intern<'db>(&'db self, runtime: &'db Runtime, data: C::Data<'db>) -> Id { runtime.report_tracked_read( DependencyIndex::for_table(self.ingredient_index), Durability::MAX, @@ -104,12 +87,13 @@ where // been interned. let internal_data = unsafe { self.to_internal_data(data) }; if let Some(id) = self.key_map.get(&internal_data) { - return (*id, false); + return *id; } match self.key_map.entry(internal_data.clone()) { // Data has been interned by a racing call, use that ID instead - dashmap::mapref::entry::Entry::Occupied(entry) => (*entry.get(), false), + dashmap::mapref::entry::Entry::Occupied(entry) => *entry.get(), + // We won any races so should intern the data dashmap::mapref::entry::Entry::Vacant(entry) => { let next_id = self.counter.fetch_add(1); @@ -120,19 +104,11 @@ where "next_id is guaranteed to be unique, bar overflow" ); entry.insert(next_id); - (next_id, true) + next_id } } } - pub fn intern<'db>(&'db self, runtime: &'db Runtime, data: C::Data<'db>) -> Id { - self.intern_full(runtime, data).0 - } - - pub(crate) fn reset_at(&self) -> Revision { - self.reset_at - } - pub fn reset(&mut self, revision: Revision) { assert!(revision > self.reset_at); self.reset_at = revision; @@ -160,47 +136,6 @@ where // * Values are only removed or altered when we have `&mut self` unsafe { self.from_internal_data(&data) } } - - /// Get the ingredient index for this table. - pub(super) fn ingredient_index(&self) -> IngredientIndex { - self.ingredient_index - } - - /// Deletes an index from the interning table, making it available for re-use. - /// - /// # Warning - /// - /// This should only be used when you are certain that: - /// 1. The given `id` has not (and will not) be used in the current revision. - /// 2. The interned data corresponding to `id` will not be interned in this revision. - /// - /// More specifically, this is used when a query `Q` executes and we can compare the - /// entities `E_now` that it produced in this revision vs the entities `E_prev` it - /// produced in the last revision. Any missing entities `E_prev - E_new` can be deleted. - /// - /// If you are wrong about this, it should not be unsafe, but unpredictable results may occur. - pub(crate) fn delete_index(&self, id: Id) { - let (_, key) = self - .value_map - .remove(&id) - .unwrap_or_else(|| panic!("No entry for id `{:?}`", id)); - - self.key_map.remove(&key); - // Careful: even though `id` ought not to have been used in this revision, - // we don't know that for sure since users could have leaked things. If they did, - // they may have stray references into `data`. So push the box onto the - // "to be deleted" queue. - // - // To avoid this, we could include some kind of atomic counter in the `Box` that - // gets set whenever `data` executes, so we can track if the data was accessed since - // the last time an `&mut self` method was called. But that'd take extra storage - // and doesn't obviously seem worth it. - self.deleted_entries.push(key); - } - - pub(crate) fn clear_deleted_indices(&mut self) { - std::mem::take(&mut self.deleted_entries); - } } impl Ingredient for InternedIngredient diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index c9fdb1345..d4db3f9fc 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -1,11 +1,14 @@ use std::{fmt, hash::Hash}; +use crossbeam::atomic::AtomicCell; +use dashmap::mapref::entry::Entry; + use crate::{ cycle::CycleRecoveryStrategy, + hash::FxDashMap, id::AsId, ingredient::{fmt_index, Ingredient, IngredientRequiresReset}, ingredient_list::IngredientList, - interned::InternedIngredient, key::{DatabaseKeyIndex, DependencyIndex}, runtime::{local_state::QueryOrigin, Runtime}, salsa_struct::SalsaStructInDb, @@ -88,8 +91,19 @@ pub struct TrackedStructIngredient where C: Configuration, { - interned: InternedIngredient, + /// Our index in the database. + ingredient_index: IngredientIndex, + + /// Defines the set of live tracked structs. + /// Entries are added to this map when a new struct is created. + /// They are removed when that struct is deleted + /// (i.e., a query completes without having recreated the struct). + keys: FxDashMap, + + /// The number of tracked structs created. + counter: AtomicCell, + /// Map from the [`Id`][] of each struct to its fields/values. struct_map: struct_map::StructMap, /// A list of each tracked function `f` whose key is this @@ -103,11 +117,19 @@ where debug_name: &'static str, } +/// Defines the identity of a tracked struct. #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] struct TrackedStructKey { + /// The active query (i.e., tracked function) that created this tracked struct. query_key: DatabaseKeyIndex, - disambiguator: Disambiguator, + + /// The hash of the `#[id]` fields of this struct. + /// Note that multiple structs may share the same hash. data_hash: u64, + + /// The unique disambiguator assigned within the active query + /// to distinguish distinct tracked structs with the same hash. + disambiguator: Disambiguator, } impl crate::interned::Configuration for TrackedStructKey { @@ -126,6 +148,9 @@ where /// The id of this struct in the ingredient. id: Id, + /// The key used to create the id. + key: TrackedStructKey, + /// 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 @@ -159,27 +184,33 @@ impl TrackedStructIngredient where C: Configuration, { + /// Convert the fields from a `'db` lifetime to `'static`: used when storing + /// the data into this ingredient, should never be released outside this type. unsafe fn to_static<'db>(&'db self, fields: C::Fields<'db>) -> C::Fields<'static> { unsafe { std::mem::transmute(fields) } } + /// Convert from static back to the db lifetime; used when returning data + /// out from this ingredient. unsafe fn to_self_ptr<'db>(&'db self, fields: *mut C::Fields<'static>) -> *mut C::Fields<'db> { unsafe { std::mem::transmute(fields) } } + /// Create a tracked struct ingredient. Generated by the `#[tracked]` macro, + /// not meant to be called directly by end-users. pub fn new(index: IngredientIndex, debug_name: &'static str) -> Self { Self { - interned: InternedIngredient::new(index, debug_name), + ingredient_index: index, + keys: FxDashMap::default(), + counter: AtomicCell::new(0), struct_map: StructMap::new(), dependent_fns: IngredientList::new(), debug_name, } } - fn struct_ingredient_index(&self) -> IngredientIndex { - self.interned.ingredient_index() - } - + /// Creates and returns a new field ingredient for the database. + /// Invoked by the `#[tracked]` struct and not meant to be called by end-users. pub fn new_field_ingredient( &self, field_ingredient_index: IngredientIndex, @@ -187,7 +218,7 @@ where field_debug_name: &'static str, ) -> TrackedFieldIngredient { assert_eq!( - field_ingredient_index.as_u32() - self.struct_ingredient_index().as_u32() - 1, + field_ingredient_index.as_u32() - self.ingredient_index.as_u32() - 1, field_index, ); @@ -200,13 +231,33 @@ where } } + /// Returns the database key index for a tracked struct with the given id. pub fn database_key_index(&self, id: Id) -> DatabaseKeyIndex { DatabaseKeyIndex { - ingredient_index: self.interned.ingredient_index(), - key_index: id.as_id(), + ingredient_index: self.ingredient_index, + key_index: id, } } + /// Intern a tracked struct key to get a unique tracked struct id. + /// Also returns a bool indicating whether this id was newly created or whether it already existed. + fn intern(&self, key: TrackedStructKey) -> (Id, bool) { + let (id, new_id) = if let Some(g) = self.keys.get(&key) { + (*g.value(), false) + } else { + match self.keys.entry(key) { + Entry::Occupied(o) => (*o.get(), false), + Entry::Vacant(v) => { + let id = Id::from_u32(self.counter.fetch_add(1)); + v.insert(id); + (id, true) + } + } + }; + + (id, new_id) + } + pub fn new_struct<'db>( &'db self, runtime: &'db Runtime, @@ -214,27 +265,28 @@ where ) -> &'db TrackedStructValue { 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 (query_key, current_deps, disambiguator) = + runtime.disambiguate_entity(self.ingredient_index, Revision::start(), data_hash); let entity_key = TrackedStructKey { query_key, disambiguator, data_hash, }; - let (id, new_id) = self.interned.intern_full(runtime, entity_key); + + let (id, new_id) = self.intern(entity_key); runtime.add_output(self.database_key_index(id).into()); let current_revision = runtime.current_revision(); if new_id { + // This is a new tracked struct, so create an entry in the struct map. + self.struct_map.insert( runtime, TrackedStructValue { id, - struct_ingredient_index: self.struct_ingredient_index(), + key: entity_key, + struct_ingredient_index: self.ingredient_index, created_at: current_revision, durability: current_deps.durability, fields: unsafe { self.to_static(fields) }, @@ -242,6 +294,15 @@ where }, ) } else { + // The struct already exists in the intern map. + // Note that we assume there is at most one executing copy of + // the current query at a time, which implies that the + // struct must exist in `self.struct_map` already + // (if the same query could execute twice in parallel, + // then it would potentially create the same struct twice in parallel, + // which means the interned key could exist but `struct_map` not yet have + // been updated). + match self.struct_map.update(runtime, id) { Update::Current(r) => { // All inputs up to this point were previously @@ -298,8 +359,9 @@ where }, }); - self.interned.delete_index(id); - self.struct_map.delete(id); + if let Some(key) = self.struct_map.delete(id) { + self.keys.remove(&key); + } for dependent_fn in self.dependent_fns.iter() { db.salsa_struct_deleted(dependent_fn, id.as_id()); @@ -320,15 +382,15 @@ where C: Configuration, { fn ingredient_index(&self) -> IngredientIndex { - self.interned.ingredient_index() + self.ingredient_index } - fn maybe_changed_after(&self, db: &DB, input: DependencyIndex, revision: Revision) -> bool { - self.interned.maybe_changed_after(db, input, revision) + fn maybe_changed_after(&self, _db: &DB, _input: DependencyIndex, _revision: Revision) -> bool { + false } fn cycle_recovery_strategy(&self) -> CycleRecoveryStrategy { - <_ as Ingredient>::cycle_recovery_strategy(&self.interned) + crate::cycle::CycleRecoveryStrategy::Panic } fn origin(&self, _key_index: crate::Id) -> Option { @@ -360,7 +422,6 @@ where } fn reset_for_new_revision(&mut self) { - self.interned.clear_deleted_indices(); self.struct_map.drop_deleted_entries(); } diff --git a/components/salsa-2022/src/tracked_struct/struct_map.rs b/components/salsa-2022/src/tracked_struct/struct_map.rs index 234911604..b95cd294e 100644 --- a/components/salsa-2022/src/tracked_struct/struct_map.rs +++ b/components/salsa-2022/src/tracked_struct/struct_map.rs @@ -12,7 +12,7 @@ use crate::{ Id, Runtime, }; -use super::{Configuration, TrackedStructValue}; +use super::{Configuration, TrackedStructKey, TrackedStructValue}; pub(crate) struct StructMap where @@ -186,9 +186,13 @@ where /// Remove the entry for `id` from the map. /// /// NB. the data won't actually be freed until `drop_deleted_entries` is called. - pub fn delete(&self, id: Id) { + pub fn delete(&self, id: Id) -> Option { if let Some((_, data)) = self.map.remove(&id) { + let key = data.key; self.deleted_entries.push(data); + Some(key) + } else { + None } } From af94b253bed345d62bdd6d2767feda52590b2dd1 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 14 May 2024 06:22:00 -0400 Subject: [PATCH 26/61] debug dump for interned struct tokens --- components/salsa-2022-macros/src/interned.rs | 25 +++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 3667f6c1a..976a8d578 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -64,17 +64,20 @@ impl InternedStruct { let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); - Ok(quote! { - #the_struct - #config_struct - #configuration_impl - #data_struct - #ingredients_for_impl - #as_id_impl - #named_fields_impl - #salsa_struct_in_db_impl - #as_debug_with_db_impl - }) + Ok(crate::debug::dump_tokens( + self.the_ident(), + quote! { + #the_struct + #config_struct + #configuration_impl + #data_struct + #ingredients_for_impl + #as_id_impl + #named_fields_impl + #salsa_struct_in_db_impl + #as_debug_with_db_impl + }, + )) } fn validate_interned(&self) -> syn::Result<()> { From d92f2aa0a53ff5d81baf04c5b6094ae1bde4cd71 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 15 May 2024 05:33:34 -0400 Subject: [PATCH 27/61] factor out useful helper fn --- components/salsa-2022-macros/src/salsa_struct.rs | 8 ++++++++ components/salsa-2022-macros/src/tracked_struct.rs | 5 +---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index a96a56ce0..00093e342 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -316,6 +316,14 @@ impl SalsaStruct { } } + /// Generate code to access a `salsa::Id` from `self` + pub(crate) fn access_salsa_id_from_self(&self) -> syn::Expr { + match self.the_struct_kind() { + TheStructKind::Id => parse_quote!(self.0), + TheStructKind::Pointer(_) => parse_quote!(unsafe { &*self.0 }.id()), + } + } + /// Returns the visibility of this item pub(crate) fn visibility(&self) -> &syn::Visibility { &self.struct_item.vis diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 82824d21f..c99ee87f3 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -222,10 +222,7 @@ impl TrackedStruct { let data = syn::Ident::new("__data", Span::call_site()); - let salsa_id = match the_kind { - TheStructKind::Id => quote!(self.0), - TheStructKind::Pointer(_) => quote!(unsafe { &*self.0 }.id()), - }; + let salsa_id = self.access_salsa_id_from_self(); let ctor = match the_kind { TheStructKind::Id => quote!(salsa::AsId::from_id(#data.id())), From 5095d79d134b0852093444c0f4c2f5e0b9b28217 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 15 May 2024 05:37:34 -0400 Subject: [PATCH 28/61] return a pointer from interning, not just id --- components/salsa-2022-macros/src/interned.rs | 12 +- .../salsa-2022-macros/src/tracked_fn.rs | 12 +- components/salsa-2022/src/interned.rs | 106 ++++++++++++------ 3 files changed, 81 insertions(+), 49 deletions(-) diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 976a8d578..b58634145 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -82,7 +82,7 @@ impl InternedStruct { fn validate_interned(&self) -> syn::Result<()> { self.disallow_id_fields("interned")?; - self.require_db_lifetime()?; + self.require_no_generics()?; Ok(()) } @@ -160,17 +160,17 @@ impl InternedStruct { if field.is_clone_field() { parse_quote_spanned! { field_get_name.span() => #field_vis fn #field_get_name(self, db: &#db_dyn_ty) -> #field_ty { - let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); + let (jar, _runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #the_ident #type_generics >>::ingredient(jar); - std::clone::Clone::clone(&ingredients.data(runtime, self.0).#field_name) + std::clone::Clone::clone(&ingredients.data(self.0).#field_name) } } } else { parse_quote_spanned! { field_get_name.span() => #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 (jar, _runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #the_ident #type_generics >>::ingredient(jar); - &ingredients.data(runtime, self.0).#field_name + &ingredients.data(self.0).#field_name } } } @@ -190,7 +190,7 @@ impl InternedStruct { let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #the_ident #type_generics >>::ingredient(jar); Self(ingredients.intern(runtime, #data_ident { #(#field_names,)* - })) + }).salsa_id()) } }; diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index 0cdafecb2..bc0ed847f 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -428,7 +428,7 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); let __ingredients = <_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar); - let #key_var = __ingredients.intern_map.data(__runtime, __id).clone(); + let #key_var = __ingredients.intern_map.data(__id).clone(); #recovery_fn(__db, __cycle, #key_splat) } }; @@ -460,7 +460,7 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); let __ingredients = <_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar); - let #key_var = __ingredients.intern_map.data(__runtime, __id).clone(); + let #key_var = __ingredients.intern_map.data(__id).clone(); #inner_fn_name(__db, #key_splat) } }; @@ -652,7 +652,7 @@ fn ref_getter_fn( { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(#db_var); let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar); - let __key = __ingredients.intern_map.intern(__runtime, (#(#arg_names),*)); + let __key = __ingredients.intern_map.intern_id(__runtime, (#(#arg_names),*)); __ingredients.function.fetch(#db_var, __key) } }; @@ -696,7 +696,7 @@ fn setter_fn( { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar_mut(#db_var); let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient_mut(__jar); - let __key = __ingredients.intern_map.intern(__runtime, (#(#arg_names),*)); + let __key = __ingredients.intern_map.intern_id(__runtime, (#(#arg_names),*)); __ingredients.function.store(__runtime, __key, #value_arg, salsa::Durability::LOW) } }, @@ -765,7 +765,7 @@ fn specify_fn( let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(#db_var); let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar); - let __key = __ingredients.intern_map.intern(__runtime, (#(#arg_names),*)); + let __key = __ingredients.intern_map.intern_id(__runtime, (#(#arg_names),*)); __ingredients.function.specify_and_record(#db_var, __key, #value_arg) } }, @@ -877,7 +877,7 @@ fn accumulated_fn( { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(#db_var); let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient(__jar); - let __key = __ingredients.intern_map.intern(__runtime, (#(#arg_names),*)); + let __key = __ingredients.intern_map.intern_id(__runtime, (#(#arg_names),*)); __ingredients.function.accumulated::<__A>(#db_var, __key) } }; diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index b912ed860..71f9417b3 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -7,6 +7,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, Id}; @@ -38,7 +39,7 @@ pub struct InternedIngredient { /// Maps from an interned id to its data. /// /// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa. - value_map: FxDashMap>>, + value_map: FxDashMap>>, /// counter for the next id. counter: AtomicCell, @@ -52,6 +53,15 @@ pub struct InternedIngredient { debug_name: &'static str, } +/// Struct storing the interned fields. +pub struct InternedValue +where + C: Configuration, +{ + id: Id, + fields: C::Data<'static>, +} + impl InternedIngredient where C: Configuration, @@ -71,12 +81,16 @@ where unsafe { std::mem::transmute(data) } } - unsafe fn from_internal_data<'db>(&'db self, data: &C::Data<'static>) -> &'db C::Data<'db> { - unsafe { std::mem::transmute(data) } + pub fn intern_id<'db>(&'db self, runtime: &'db Runtime, data: C::Data<'db>) -> crate::Id { + self.intern(runtime, data).salsa_id() } - /// Intern data to a unique id. - pub fn intern<'db>(&'db self, runtime: &'db Runtime, data: C::Data<'db>) -> Id { + /// Intern data to a unique reference. + pub fn intern<'db>( + &'db self, + runtime: &'db Runtime, + data: C::Data<'db>, + ) -> &'db InternedValue { runtime.report_tracked_read( DependencyIndex::for_table(self.ingredient_index), Durability::MAX, @@ -86,56 +100,57 @@ where // Optimisation to only get read lock on the map if the data has already // been interned. let internal_data = unsafe { self.to_internal_data(data) }; - if let Some(id) = self.key_map.get(&internal_data) { - return *id; + if let Some(guard) = self.key_map.get(&internal_data) { + let id = *guard; + drop(guard); + return self.interned_value(id); } match self.key_map.entry(internal_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) => { + let id = *entry.get(); + drop(entry); + self.interned_value(id) + } // We won any races so should intern the data dashmap::mapref::entry::Entry::Vacant(entry) => { let next_id = self.counter.fetch_add(1); let next_id = Id::from_id(crate::id::Id::from_u32(next_id)); - let old_value = self.value_map.insert(next_id, Box::new(internal_data)); - assert!( - old_value.is_none(), - "next_id is guaranteed to be unique, bar overflow" - ); + let value = self + .value_map + .entry(next_id) + .or_insert(Box::new(InternedValue { + id: next_id, + fields: internal_data, + })); + // SAFETY: Items are only removed from the `value_map` with an `&mut self` reference. + let value_ref = unsafe { transmute_lifetime(self, &**value) }; + drop(value); entry.insert(next_id); - next_id + value_ref } } } + pub fn interned_value<'db>(&'db self, id: Id) -> &'db InternedValue { + let r = self.value_map.get(&id).unwrap(); + + // SAFETY: Items are only removed from the `value_map` with an `&mut self` reference. + unsafe { transmute_lifetime(self, &**r) } + } + + pub fn data<'db>(&'db self, id: Id) -> &'db C::Data<'db> { + self.interned_value(id).data() + } + pub fn reset(&mut self, revision: Revision) { assert!(revision > self.reset_at); self.reset_at = revision; self.key_map.clear(); self.value_map.clear(); } - - #[track_caller] - pub fn data<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db C::Data<'db> { - runtime.report_tracked_read( - DependencyIndex::for_table(self.ingredient_index), - Durability::MAX, - self.reset_at, - ); - - let data = match self.value_map.get(&id) { - Some(d) => d, - None => { - panic!("no data found for id `{:?}`", id) - } - }; - - // Unsafety clause: - // - // * Values are only removed or altered when we have `&mut self` - unsafe { self.from_internal_data(&data) } - } } impl Ingredient for InternedIngredient @@ -223,11 +238,28 @@ where IdentityInterner { data: PhantomData } } - pub fn intern<'db>(&'db self, _runtime: &'db Runtime, id: C::Data<'db>) -> crate::Id { + pub fn intern_id<'db>(&'db self, _runtime: &'db Runtime, id: C::Data<'db>) -> crate::Id { id.as_id() } - pub fn data<'db>(&'db self, _runtime: &'db Runtime, id: crate::Id) -> C::Data<'db> { + pub fn data<'db>(&'db self, id: crate::Id) -> C::Data<'db> { >::from_id(id) } } + +impl InternedValue +where + C: Configuration, +{ + pub fn salsa_id(&self) -> Id { + self.id + } + + pub fn data<'db>(&'db self) -> &'db C::Data<'db> { + unsafe { self.to_self_ref(&self.fields) } + } + + unsafe fn to_self_ref<'db>(&'db self, fields: &'db C::Data<'static>) -> &'db C::Data<'db> { + unsafe { std::mem::transmute(fields) } + } +} From 0b8c27bc3052da89d59718b4e8e53c8b87654876 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 15 May 2024 21:13:54 -0400 Subject: [PATCH 29/61] rename from TrackedStruct to just Struct this will let us use different packages but the same struct name from salsa struct --- book/src/plumbing/tracked_structs.md | 8 ++--- .../salsa-2022-macros/src/salsa_struct.rs | 4 +-- components/salsa-2022/src/tracked_struct.rs | 26 +++++++------- .../src/tracked_struct/struct_map.rs | 34 ++++++++----------- 4 files changed, 34 insertions(+), 38 deletions(-) diff --git a/book/src/plumbing/tracked_structs.md b/book/src/plumbing/tracked_structs.md index 4444b4631..8fbf46a9d 100644 --- a/book/src/plumbing/tracked_structs.md +++ b/book/src/plumbing/tracked_structs.md @@ -10,12 +10,12 @@ 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 +It also shares access to a hashtable that stores the `ValueStruct` 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` +to access the `ValueStruct` instance for a given id. The `ValueStruct` contains both the field values but also the revisions when they last changed value. ## Each tracked struct has a globally unique id @@ -26,13 +26,13 @@ This will begin by creating a *globally unique, 32-bit id* for the tracked struc * 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 +## Each tracked struct has a `ValueStruct` 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}} +{{#include ../../../components/salsa-2022/src/tracked_struct.rs:ValueStruct}} ``` The value struct stores the values of the fields but also the revisions when diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index 00093e342..7d3d8d984 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -309,8 +309,8 @@ impl SalsaStruct { #(#attrs)* #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] #visibility struct #ident #generics ( - *const salsa::tracked_struct::TrackedStructValue < #config_ident >, - std::marker::PhantomData < & #lifetime salsa::tracked_struct::TrackedStructValue < #config_ident > > + *const salsa::tracked_struct::ValueStruct < #config_ident >, + std::marker::PhantomData < & #lifetime salsa::tracked_struct::ValueStruct < #config_ident > > ); }) } diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index d4db3f9fc..0540ece45 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -98,7 +98,7 @@ where /// Entries are added to this map when a new struct is created. /// They are removed when that struct is deleted /// (i.e., a query completes without having recreated the struct). - keys: FxDashMap, + keys: FxDashMap, /// The number of tracked structs created. counter: AtomicCell, @@ -119,7 +119,7 @@ where /// Defines the identity of a tracked struct. #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] -struct TrackedStructKey { +struct KeyStruct { /// The active query (i.e., tracked function) that created this tracked struct. query_key: DatabaseKeyIndex, @@ -132,13 +132,13 @@ struct TrackedStructKey { disambiguator: Disambiguator, } -impl crate::interned::Configuration for TrackedStructKey { - type Data<'db> = TrackedStructKey; +impl crate::interned::Configuration for KeyStruct { + type Data<'db> = KeyStruct; } -// ANCHOR: TrackedStructValue +// ANCHOR: ValueStruct #[derive(Debug)] -pub struct TrackedStructValue +pub struct ValueStruct where C: Configuration, { @@ -149,7 +149,7 @@ where id: Id, /// The key used to create the id. - key: TrackedStructKey, + key: KeyStruct, /// The durability minimum durability of all inputs consumed /// by the creator query prior to creating this tracked struct. @@ -175,7 +175,7 @@ where /// current revision if the value is different. revisions: C::Revisions, } -// ANCHOR_END: TrackedStructValue +// ANCHOR_END: ValueStruct #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] pub struct Disambiguator(pub u32); @@ -241,7 +241,7 @@ where /// Intern a tracked struct key to get a unique tracked struct id. /// Also returns a bool indicating whether this id was newly created or whether it already existed. - fn intern(&self, key: TrackedStructKey) -> (Id, bool) { + fn intern(&self, key: KeyStruct) -> (Id, bool) { let (id, new_id) = if let Some(g) = self.keys.get(&key) { (*g.value(), false) } else { @@ -262,13 +262,13 @@ where &'db self, runtime: &'db Runtime, fields: C::Fields<'db>, - ) -> &'db TrackedStructValue { + ) -> &'db ValueStruct { let data_hash = crate::hash::hash(&C::id_fields(&fields)); let (query_key, current_deps, disambiguator) = runtime.disambiguate_entity(self.ingredient_index, Revision::start(), data_hash); - let entity_key = TrackedStructKey { + let entity_key = KeyStruct { query_key, disambiguator, data_hash, @@ -283,7 +283,7 @@ where self.struct_map.insert( runtime, - TrackedStructValue { + ValueStruct { id, key: entity_key, struct_ingredient_index: self.ingredient_index, @@ -441,7 +441,7 @@ where const RESET_ON_NEW_REVISION: bool = true; } -impl TrackedStructValue +impl ValueStruct where C: Configuration, { diff --git a/components/salsa-2022/src/tracked_struct/struct_map.rs b/components/salsa-2022/src/tracked_struct/struct_map.rs index b95cd294e..b9607b05f 100644 --- a/components/salsa-2022/src/tracked_struct/struct_map.rs +++ b/components/salsa-2022/src/tracked_struct/struct_map.rs @@ -12,27 +12,27 @@ use crate::{ Id, Runtime, }; -use super::{Configuration, TrackedStructKey, TrackedStructValue}; +use super::{Configuration, KeyStruct, ValueStruct}; pub(crate) struct StructMap where C: Configuration, { - map: Arc>>>, + map: Arc>>>, /// 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>>, + deleted_entries: SegQueue>>, } pub(crate) struct StructMapView where C: Configuration, { - map: Arc>>>, + map: Arc>>>, } /// Return value for [`StructMap`][]'s `update` method. @@ -49,7 +49,7 @@ where /// to this struct creation were up-to-date, and therefore the field contents /// ought not to have changed (barring user error). Returns a shared reference /// because caller cannot safely modify fields at this point. - Current(&'db TrackedStructValue), + Current(&'db ValueStruct), } impl StructMap @@ -76,11 +76,7 @@ where /// /// * If value with same `value.id` is already present in the map. /// * If value not created in current revision. - pub fn insert<'db>( - &'db self, - runtime: &'db Runtime, - value: TrackedStructValue, - ) -> &TrackedStructValue { + pub fn insert<'db>(&'db self, runtime: &'db Runtime, value: ValueStruct) -> &ValueStruct { assert_eq!(value.created_at, runtime.current_revision()); let boxed_value = Box::new(value); @@ -159,12 +155,12 @@ where /// * If the value is not present in the map. /// * If the value has not been updated in this revision. fn get_from_map<'db>( - map: &'db FxDashMap>>, + map: &'db FxDashMap>>, runtime: &'db Runtime, id: Id, - ) -> &'db TrackedStructValue { + ) -> &'db ValueStruct { let data = map.get(&id).unwrap(); - let data: &TrackedStructValue = &**data; + let data: &ValueStruct = &**data; // Before we drop the lock, check that the value has // been updated in this revision. This is what allows us to return a `` @@ -186,7 +182,7 @@ where /// Remove the entry for `id` from the map. /// /// NB. the data won't actually be freed until `drop_deleted_entries` is called. - pub fn delete(&self, id: Id) -> Option { + pub fn delete(&self, id: Id) -> Option { if let Some((_, data)) = self.map.remove(&id) { let key = data.key; self.deleted_entries.push(data); @@ -212,7 +208,7 @@ where /// /// * If the value is not present in the map. /// * If the value has not been updated in this revision. - pub fn get<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db TrackedStructValue { + pub fn get<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db ValueStruct { StructMap::get_from_map(&self.map, runtime, id) } } @@ -224,7 +220,7 @@ pub(crate) struct UpdateRef<'db, C> where C: Configuration, { - guard: RefMut<'db, Id, Box>, FxHasher>, + guard: RefMut<'db, Id, Box>, FxHasher>, } impl<'db, C> UpdateRef<'db, C> @@ -232,11 +228,11 @@ where C: Configuration, { /// Finalize this update, freezing the value for the rest of the revision. - pub fn freeze(self) -> &'db TrackedStructValue { + pub fn freeze(self) -> &'db ValueStruct { // Unsafety clause: // // see `get` above - let data: &TrackedStructValue = &*self.guard; + let data: &ValueStruct = &*self.guard; let dummy: &'db () = &(); unsafe { transmute_lifetime(dummy, data) } } @@ -246,7 +242,7 @@ impl Deref for UpdateRef<'_, C> where C: Configuration, { - type Target = TrackedStructValue; + type Target = ValueStruct; fn deref(&self) -> &Self::Target { &self.guard From 9d8a60b61756d98ca3a615cbcc2f37a4306016c2 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 15 May 2024 21:18:02 -0400 Subject: [PATCH 30/61] parameterize salsa_struct module --- components/salsa-2022-macros/src/input.rs | 2 +- components/salsa-2022-macros/src/interned.rs | 4 +++- components/salsa-2022-macros/src/salsa_struct.rs | 13 ++++++++++--- components/salsa-2022-macros/src/tracked_struct.rs | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/components/salsa-2022-macros/src/input.rs b/components/salsa-2022-macros/src/input.rs index 4f487e472..2ea18aabb 100644 --- a/components/salsa-2022-macros/src/input.rs +++ b/components/salsa-2022-macros/src/input.rs @@ -10,7 +10,7 @@ pub(crate) fn input( args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - match SalsaStruct::new(args, input).and_then(|el| InputStruct(el).generate_input()) { + match SalsaStruct::new(args, input, "input").and_then(|el| InputStruct(el).generate_input()) { Ok(s) => s.into(), Err(err) => err.into_compile_error().into(), } diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index b58634145..181108dc5 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -13,7 +13,9 @@ pub(crate) fn interned( args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - match SalsaStruct::new(args, input).and_then(|el| InternedStruct(el).generate_interned()) { + match SalsaStruct::new(args, input, "interned") + .and_then(|el| InternedStruct(el).generate_interned()) + { Ok(s) => s.into(), Err(err) => err.into_compile_error().into(), } diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index 7d3d8d984..0e921059a 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -41,6 +41,7 @@ pub(crate) struct SalsaStruct { struct_item: syn::ItemStruct, customizations: Vec, fields: Vec, + module: syn::Ident, } #[derive(PartialEq, Eq, Debug, Copy, Clone)] @@ -65,15 +66,18 @@ impl SalsaStruct { pub(crate) fn new( args: proc_macro::TokenStream, input: proc_macro::TokenStream, + module: &str, ) -> syn::Result { let struct_item = syn::parse(input)?; - Self::with_struct(args, struct_item) + Self::with_struct(args, struct_item, module) } pub(crate) fn with_struct( args: proc_macro::TokenStream, struct_item: syn::ItemStruct, + module: &str, ) -> syn::Result { + let module = syn::Ident::new(module, struct_item.ident.span()); let args: Options = syn::parse(args)?; let customizations = Self::extract_customizations(&struct_item)?; let fields = Self::extract_fields(&struct_item)?; @@ -82,6 +86,7 @@ impl SalsaStruct { struct_item, customizations, fields, + module, }) } @@ -305,12 +310,14 @@ impl SalsaStruct { .filter(|attr| !attr.path.is_ident("customize")) .collect(); + let module = &self.module; + Ok(parse_quote_spanned! { ident.span() => #(#attrs)* #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] #visibility struct #ident #generics ( - *const salsa::tracked_struct::ValueStruct < #config_ident >, - std::marker::PhantomData < & #lifetime salsa::tracked_struct::ValueStruct < #config_ident > > + *const salsa::#module::ValueStruct < #config_ident >, + std::marker::PhantomData < & #lifetime salsa::#module::ValueStruct < #config_ident > > ); }) } diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index c99ee87f3..01bc23145 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -13,7 +13,7 @@ pub(crate) fn tracked( ) -> syn::Result { let struct_name = struct_item.ident.clone(); - let tokens = SalsaStruct::with_struct(args, struct_item) + let tokens = SalsaStruct::with_struct(args, struct_item, "tracked_struct") .and_then(|el| TrackedStruct(el).generate_tracked())?; Ok(crate::debug::dump_tokens(&struct_name, tokens)) From 9607638d5d805a212c4c42b7fb9b8c31c0291a31 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 16 May 2024 05:18:56 -0400 Subject: [PATCH 31/61] permit interned structs with lifetimes --- components/salsa-2022-macros/src/interned.rs | 125 ++++++++++++++++-- components/salsa-2022/src/interned.rs | 14 +- .../tests/interned-struct-with-lifetime.rs | 57 ++++++++ 3 files changed, 178 insertions(+), 18 deletions(-) create mode 100644 salsa-2022-tests/tests/interned-struct-with-lifetime.rs diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 181108dc5..663fc089c 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -1,4 +1,4 @@ -use crate::salsa_struct::SalsaStruct; +use crate::salsa_struct::{SalsaStruct, TheStructKind}; use proc_macro2::TokenStream; // #[salsa::interned(jar = Jar0, data = TyData0)] @@ -84,7 +84,7 @@ impl InternedStruct { fn validate_interned(&self) -> syn::Result<()> { self.disallow_id_fields("interned")?; - self.require_no_generics()?; + self.require_db_lifetime()?; Ok(()) } @@ -113,16 +113,34 @@ impl InternedStruct { let visibility = self.visibility(); let all_field_names = self.all_field_names(); let all_field_tys = self.all_field_tys(); - parse_quote_spanned! { data_ident.span() => - /// Internal struct used for interned item - #[derive(Eq, PartialEq, Hash, Clone)] - #visibility struct #data_ident #impl_generics - where - #where_clause - { - #( - #all_field_names: #all_field_tys, - )* + + match self.the_struct_kind() { + TheStructKind::Id => { + parse_quote_spanned! { data_ident.span() => + #[derive(Eq, PartialEq, Hash, Clone)] + #visibility struct #data_ident #impl_generics + where + #where_clause + { + #( + #all_field_names: #all_field_tys, + )* + } + } + } + TheStructKind::Pointer(db_lt) => { + parse_quote_spanned! { data_ident.span() => + #[derive(Eq, PartialEq, Hash, Clone)] + #visibility struct #data_ident #impl_generics + where + #where_clause + { + #( + #all_field_names: #all_field_tys, + )* + __phantom: std::marker::PhantomData<& #db_lt ()>, + } + } } } } @@ -146,6 +164,89 @@ impl InternedStruct { /// If this is an interned struct, then generate methods to access each field, /// as well as a `new` method. fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl { + match self.the_struct_kind() { + TheStructKind::Id => self.inherent_impl_for_named_fields_id(), + TheStructKind::Pointer(db_lt) => self.inherent_impl_for_named_fields_lt(&db_lt), + } + } + + /// If this is an interned struct, then generate methods to access each field, + /// as well as a `new` method. + fn inherent_impl_for_named_fields_lt(&self, db_lt: &syn::Lifetime) -> syn::ItemImpl { + let vis: &syn::Visibility = self.visibility(); + let (the_ident, _, impl_generics, type_generics, where_clause) = + self.the_ident_and_generics(); + let db_dyn_ty = self.db_dyn_ty(); + let jar_ty = self.jar_ty(); + + let field_getters: Vec = self + .all_fields() + .map(|field| { + let field_name = field.name(); + let field_ty = field.ty(); + let field_vis = field.vis(); + let field_get_name = field.get_name(); + if field.is_clone_field() { + parse_quote_spanned! { field_get_name.span() => + #field_vis fn #field_get_name(self, _db: & #db_lt #db_dyn_ty) -> #field_ty { + std::clone::Clone::clone(&unsafe { &*self.0 }.data().#field_name) + } + } + } else { + parse_quote_spanned! { field_get_name.span() => + #field_vis fn #field_get_name(self, _db: & #db_lt #db_dyn_ty) -> & #db_lt #field_ty { + &unsafe { &*self.0 }.data().#field_name + } + } + } + }) + .collect(); + + let field_names = self.all_field_names(); + let field_tys = self.all_field_tys(); + let data_ident = self.data_ident(); + let constructor_name = self.constructor_name(); + let new_method: syn::ImplItemMethod = parse_quote_spanned! { constructor_name.span() => + #vis 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< #the_ident #type_generics >>::ingredient(jar); + Self( + ingredients.intern(runtime, #data_ident { + #(#field_names,)* + __phantom: std::marker::PhantomData, + }), + std::marker::PhantomData, + ) + } + }; + + let salsa_id = quote!( + pub fn salsa_id(&self) -> salsa::Id { + unsafe { &*self.0 }.salsa_id() + } + ); + + parse_quote! { + #[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] + impl #impl_generics #the_ident #type_generics + where + #where_clause + { + #(#field_getters)* + + #new_method + + #salsa_id + } + } + } + + /// If this is an interned struct, then generate methods to access each field, + /// as well as a `new` method. + fn inherent_impl_for_named_fields_id(&self) -> syn::ItemImpl { let vis: &syn::Visibility = self.visibility(); let (the_ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index 71f9417b3..6c276145d 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -39,7 +39,7 @@ pub struct InternedIngredient { /// Maps from an interned id to its data. /// /// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa. - value_map: FxDashMap>>, + value_map: FxDashMap>>, /// counter for the next id. counter: AtomicCell, @@ -54,7 +54,7 @@ pub struct InternedIngredient { } /// Struct storing the interned fields. -pub struct InternedValue +pub struct ValueStruct where C: Configuration, { @@ -90,7 +90,7 @@ where &'db self, runtime: &'db Runtime, data: C::Data<'db>, - ) -> &'db InternedValue { + ) -> &'db ValueStruct { runtime.report_tracked_read( DependencyIndex::for_table(self.ingredient_index), Durability::MAX, @@ -121,7 +121,7 @@ where let value = self .value_map .entry(next_id) - .or_insert(Box::new(InternedValue { + .or_insert(Box::new(ValueStruct { id: next_id, fields: internal_data, })); @@ -134,7 +134,7 @@ where } } - pub fn interned_value<'db>(&'db self, id: Id) -> &'db InternedValue { + pub fn interned_value<'db>(&'db self, id: Id) -> &'db ValueStruct { let r = self.value_map.get(&id).unwrap(); // SAFETY: Items are only removed from the `value_map` with an `&mut self` reference. @@ -247,7 +247,7 @@ where } } -impl InternedValue +impl ValueStruct where C: Configuration, { @@ -256,6 +256,8 @@ where } pub fn data<'db>(&'db self) -> &'db C::Data<'db> { + // SAFETY: The lifetime of `self` is tied to the interning ingredient; + // we never remove data without an `&mut self` access to the interning ingredient. unsafe { self.to_self_ref(&self.fields) } } diff --git a/salsa-2022-tests/tests/interned-struct-with-lifetime.rs b/salsa-2022-tests/tests/interned-struct-with-lifetime.rs new file mode 100644 index 000000000..a8fa3e4c3 --- /dev/null +++ b/salsa-2022-tests/tests/interned-struct-with-lifetime.rs @@ -0,0 +1,57 @@ +//! Test that a `tracked` fn on a `salsa::input` +//! compiles and executes successfully. +use salsa::DebugWithDb; +use salsa_2022_tests::{HasLogger, Logger}; + +use expect_test::expect; +use test_log::test; + +#[salsa::jar(db = Db)] +struct Jar(InternedString<'_>, InternedPair<'_>, intern_stuff); + +trait Db: salsa::DbWithJar + HasLogger {} + +#[salsa::interned] +struct InternedString<'db> { + data: String, +} + +#[salsa::interned] +struct InternedPair<'db> { + data: (InternedString<'db>, InternedString<'db>), +} + +#[salsa::tracked] +fn intern_stuff(db: &dyn Db) -> String { + let s1 = InternedString::new(db, format!("Hello, ")); + let s2 = InternedString::new(db, format!("World, ")); + let s3 = InternedPair::new(db, (s1, s2)); + format!("{:?}", s3.debug(db)) +} + +#[salsa::db(Jar)] +#[derive(Default)] +struct Database { + storage: salsa::Storage, + logger: Logger, +} + +impl salsa::Database for Database {} + +impl Db for Database {} + +impl HasLogger for Database { + fn logger(&self) -> &Logger { + &self.logger + } +} + +#[test] +fn execute() { + let mut db = Database::default(); + + expect![[r#" + "InternedPair { [salsa id]: 0, data: (InternedString { [salsa id]: 0, data: \"Hello, \" }, InternedString { [salsa id]: 1, data: \"World, \" }) }" + "#]].assert_debug_eq(&intern_stuff(&db)); + db.assert_logs(expect!["[]"]); +} From d361e8adfb4c716617b1f6394e1614b0f6efe623 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 17 May 2024 05:29:57 -0400 Subject: [PATCH 32/61] add a `'db` argument to `SalsaStruct` --- components/salsa-2022-macros/src/configuration.rs | 8 +++++--- components/salsa-2022-macros/src/tracked_fn.rs | 6 +++++- components/salsa-2022/src/function.rs | 6 +++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/components/salsa-2022-macros/src/configuration.rs b/components/salsa-2022-macros/src/configuration.rs index 02276aafa..df16fe4f0 100644 --- a/components/salsa-2022-macros/src/configuration.rs +++ b/components/salsa-2022-macros/src/configuration.rs @@ -1,4 +1,5 @@ pub(crate) struct Configuration { + pub(crate) db_lt: syn::Lifetime, pub(crate) jar_ty: syn::Type, pub(crate) salsa_struct_ty: syn::Type, pub(crate) input_ty: syn::Type, @@ -12,6 +13,7 @@ pub(crate) struct Configuration { impl Configuration { pub(crate) fn to_impl(&self, self_ty: &syn::Type) -> syn::ItemImpl { let Configuration { + db_lt, jar_ty, salsa_struct_ty, input_ty, @@ -24,9 +26,9 @@ impl Configuration { parse_quote! { impl salsa::function::Configuration for #self_ty { type Jar = #jar_ty; - type SalsaStruct = #salsa_struct_ty; - type Input<'db> = #input_ty; - type Value<'db> = #value_ty; + type SalsaStruct<#db_lt> = #salsa_struct_ty; + type Input<#db_lt> = #input_ty; + type Value<#db_lt> = #value_ty; const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = #cycle_strategy; #backdate_fn #execute_fn diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index bc0ed847f..cbc1211fd 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -4,13 +4,15 @@ use syn::visit_mut::VisitMut; use syn::{ReturnType, Token}; use crate::configuration::{self, Configuration, CycleRecoveryStrategy}; -use crate::db_lifetime::{db_lifetime, require_db_lifetime}; +use crate::db_lifetime::{self, db_lifetime, require_db_lifetime}; use crate::options::Options; pub(crate) fn tracked_fn( args: proc_macro::TokenStream, mut item_fn: syn::ItemFn, ) -> syn::Result { + db_lifetime::require_db_lifetime(&item_fn.sig.generics)?; + let fn_ident = item_fn.sig.ident.clone(); let args: FnArgs = syn::parse(args)?; @@ -390,6 +392,7 @@ fn salsa_struct_ty(item_fn: &syn::ItemFn) -> syn::Type { fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { let jar_ty = args.jar_ty(); + let db_lt = db_lifetime(&item_fn.sig.generics); let salsa_struct_ty = salsa_struct_ty(item_fn); let key_ty = match function_type(item_fn) { FunctionType::Constant => parse_quote!(()), @@ -466,6 +469,7 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { }; Configuration { + db_lt, jar_ty, salsa_struct_ty, input_ty: key_ty, diff --git a/components/salsa-2022/src/function.rs b/components/salsa-2022/src/function.rs index 469235a8e..336ac6e9c 100644 --- a/components/salsa-2022/src/function.rs +++ b/components/salsa-2022/src/function.rs @@ -82,7 +82,7 @@ pub trait Configuration { /// The "salsa struct type" that this function is associated with. /// This can be just `salsa::Id` for functions that intern their arguments /// and are not clearly associated with any one salsa struct. - type SalsaStruct: for<'db> SalsaStructInDb>; + type SalsaStruct<'db>: SalsaStructInDb>; /// The input to the function type Input<'db>; @@ -194,9 +194,9 @@ where /// Register this function as a dependent fn of the given salsa struct. /// When instances of that salsa struct are deleted, we'll get a callback /// so we can remove any data keyed by them. - fn register(&self, db: &DynDb<'_, C>) { + fn register<'db>(&self, db: &'db DynDb<'db, C>) { if !self.registered.fetch_or(true) { - >::register_dependent_fn(db, self.index) + as SalsaStructInDb<_>>::register_dependent_fn(db, self.index) } } } From 8d0f8fccbf0d8bce419d1c5140f695d9e48cbe76 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 17 May 2024 06:19:32 -0400 Subject: [PATCH 33/61] remove unnecessary uses of AsId --- components/salsa-2022/src/function.rs | 4 ++-- components/salsa-2022/src/function/fetch.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/salsa-2022/src/function.rs b/components/salsa-2022/src/function.rs index 336ac6e9c..e989bd2b3 100644 --- a/components/salsa-2022/src/function.rs +++ b/components/salsa-2022/src/function.rs @@ -14,7 +14,7 @@ use crate::{ use self::delete::DeletedEntries; -use super::{ingredient::Ingredient, routes::IngredientIndex, AsId}; +use super::{ingredient::Ingredient, routes::IngredientIndex}; mod accumulated; mod backdate; @@ -147,7 +147,7 @@ where fn database_key_index(&self, k: Id) -> DatabaseKeyIndex { DatabaseKeyIndex { ingredient_index: self.index, - key_index: k.as_id(), + key_index: k, } } diff --git a/components/salsa-2022/src/function/fetch.rs b/components/salsa-2022/src/function/fetch.rs index 55aa0eae1..de2e90c9b 100644 --- a/components/salsa-2022/src/function/fetch.rs +++ b/components/salsa-2022/src/function/fetch.rs @@ -1,6 +1,6 @@ use arc_swap::Guard; -use crate::{database::AsSalsaDatabase, runtime::StampedValue, storage::HasJarsDyn, AsId, Id}; +use crate::{database::AsSalsaDatabase, runtime::StampedValue, storage::HasJarsDyn, Id}; use super::{Configuration, DynDb, FunctionIngredient}; @@ -19,8 +19,8 @@ where changed_at, } = self.compute_value(db, key); - if let Some(evicted) = self.lru.record_use(key.as_id()) { - self.evict(AsId::from_id(evicted)); + if let Some(evicted) = self.lru.record_use(key) { + self.evict(evicted); } db.runtime().report_tracked_read( From cf2fa671f5f93d9a645ec6f7124bd9e79f6045e7 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 18 May 2024 06:12:44 -0400 Subject: [PATCH 34/61] introduce IdLookup trait We are going to need it for tracked functions. --- .../salsa-2022-macros/src/tracked_struct.rs | 33 +++++++++++++++++ components/salsa-2022/src/id.rs | 37 ++++++++++++++++++- components/salsa-2022/src/tracked_struct.rs | 10 +++++ .../src/tracked_struct/struct_map.rs | 10 +++++ 4 files changed, 89 insertions(+), 1 deletion(-) diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 01bc23145..ab03eaeed 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -64,6 +64,7 @@ impl TrackedStruct { let tracked_struct_in_db_impl = self.tracked_struct_in_db_impl(); let update_impl = self.update_impl(); let as_id_impl = self.as_id_impl(); + let id_lookup_impl = self.id_lookup_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); Ok(quote! { #config_struct @@ -75,6 +76,7 @@ impl TrackedStruct { #tracked_struct_in_db_impl #update_impl #as_id_impl + #id_lookup_impl #as_debug_with_db_impl }) } @@ -327,6 +329,37 @@ impl TrackedStruct { } } + /// Implementation of `IdLookup`. + pub(crate) fn id_lookup_impl(&self) -> Option { + match self.the_struct_kind() { + TheStructKind::Id => None, + TheStructKind::Pointer(db_lt) => { + let (ident, parameters, _, type_generics, where_clause) = + self.the_ident_and_generics(); + let db = syn::Ident::new("DB", ident.span()); + let jar_ty = self.jar_ty(); + let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); + Some(parse_quote_spanned! { ident.span() => + impl<#db, #parameters> salsa::id::IdLookup<& #db_lt #db> for #ident #type_generics + where + #db: ?Sized + salsa::DbWithJar<#jar_ty>, + #where_clause + { + fn into_id(self) -> salsa::Id { + unsafe { &*self.0 }.id() + } + + fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self { + let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); + Self(ingredients.#tracked_struct_ingredient.lookup_struct(runtime, id), std::marker::PhantomData) + } + } + }) + } + } + } + /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics(); diff --git a/components/salsa-2022/src/id.rs b/components/salsa-2022/src/id.rs index c1db29b36..155004f3a 100644 --- a/components/salsa-2022/src/id.rs +++ b/components/salsa-2022/src/id.rs @@ -65,7 +65,7 @@ impl From for usize { } } -/// Trait for types that can be interconverted to a salsa Id; +/// Internal Salsa trait for types that can be interconverted to a salsa Id; pub trait AsId: Sized + Copy + Eq + Hash + Debug { fn as_id(self) -> Id; fn from_id(id: Id) -> Self; @@ -92,3 +92,38 @@ impl AsId for () { assert_eq!(0, id.as_u32()); } } + +/// Internal Salsa trait for types that have a salsa id but require looking +/// up in the database to find it. This is different from +/// [`AsId`][] where what we have is literally a *newtype* +/// for an `Id`. +pub trait IdLookup { + /// Convert to an `Id` + fn into_id(self) -> Id; + + /// Lookup from an `Id` to get an instance of the type. + /// + /// # Panics + /// + /// This fn may panic if the value with this id has not been + /// produced in this revision already (e.g., for a tracked + /// struct, the function will panic if the tracked struct + /// has not yet been created in this revision). Salsa's + /// dependency tracking typically ensures this does not + /// occur, but it is possible for a user to violate this + /// rule. + fn lookup_id(id: Id, db: DB) -> Self; +} + +impl IdLookup for ID +where + ID: AsId, +{ + fn into_id(self) -> Id { + self.as_id() + } + + fn lookup_id(id: Id, _db: DB) -> Self { + Self::from_id(id) + } +} diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 0540ece45..21429792d 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -341,6 +341,16 @@ where } } + /// Given the id of a tracked struct created in this revision, + /// returns a pointer to the struct. + /// + /// # Panics + /// + /// If the struct has not been created in this revision. + pub fn lookup_struct<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db ValueStruct { + self.struct_map.get(runtime, id) + } + /// Deletes the given entities. This is used after a query `Q` executes and we can compare /// the entities `E_now` that it produced in this revision vs the entities /// `E_prev` it produced in the last revision. Any missing entities `E_prev - E_new` can be diff --git a/components/salsa-2022/src/tracked_struct/struct_map.rs b/components/salsa-2022/src/tracked_struct/struct_map.rs index b9607b05f..399eec27a 100644 --- a/components/salsa-2022/src/tracked_struct/struct_map.rs +++ b/components/salsa-2022/src/tracked_struct/struct_map.rs @@ -148,6 +148,16 @@ where Update::Outdated(UpdateRef { guard: data }) } + /// Lookup an existing tracked struct from the map. + /// + /// # Panics + /// + /// * If the value is not present in the map. + /// * If the value has not been updated in this revision. + pub fn get<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db ValueStruct { + Self::get_from_map(&self.map, runtime, id) + } + /// Helper function, provides shared functionality for [`StructMapView`][] /// /// # Panics From ab707865369358d3046d24999634b5b907cfe002 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 18 May 2024 06:16:52 -0400 Subject: [PATCH 35/61] introduce LookupId trait We are going to need it for tracked functions. --- components/salsa-2022-macros/src/interned.rs | 32 +++++++++++++++++++ .../salsa-2022-macros/src/tracked_struct.rs | 10 +++--- components/salsa-2022/src/id.rs | 4 +-- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 663fc089c..0955d39e6 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -62,6 +62,7 @@ impl InternedStruct { 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 lookup_id_impl = self.lookup_id_impl(); let named_fields_impl = self.inherent_impl_for_named_fields(); let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); @@ -75,6 +76,7 @@ impl InternedStruct { #data_struct #ingredients_for_impl #as_id_impl + #lookup_id_impl #named_fields_impl #salsa_struct_in_db_impl #as_debug_with_db_impl @@ -356,6 +358,36 @@ impl InternedStruct { } } + /// Implementation of `LookupId`. + fn lookup_id_impl(&self) -> Option { + match self.the_struct_kind() { + TheStructKind::Id => None, + TheStructKind::Pointer(db_lt) => { + let (ident, parameters, _, type_generics, where_clause) = + self.the_ident_and_generics(); + let db = syn::Ident::new("DB", ident.span()); + let jar_ty = self.jar_ty(); + Some(parse_quote_spanned! { ident.span() => + impl<#db, #parameters> salsa::id::LookupId<& #db_lt #db> for #ident #type_generics + where + #db: ?Sized + salsa::DbWithJar<#jar_ty>, + #where_clause + { + fn into_id(self) -> salsa::Id { + unsafe { &*self.0 }.salsa_id() + } + + fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self { + let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); + Self(ingredients.interned_value(id), std::marker::PhantomData) + } + } + }) + } + } + } + /// Implementation of `SalsaStructInDb`. fn salsa_struct_in_db_impl(&self) -> syn::ItemImpl { let (the_ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics(); diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index ab03eaeed..381dabda2 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -64,7 +64,7 @@ impl TrackedStruct { let tracked_struct_in_db_impl = self.tracked_struct_in_db_impl(); let update_impl = self.update_impl(); let as_id_impl = self.as_id_impl(); - let id_lookup_impl = self.id_lookup_impl(); + let lookup_id_impl = self.lookup_id_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); Ok(quote! { #config_struct @@ -76,7 +76,7 @@ impl TrackedStruct { #tracked_struct_in_db_impl #update_impl #as_id_impl - #id_lookup_impl + #lookup_id_impl #as_debug_with_db_impl }) } @@ -329,8 +329,8 @@ impl TrackedStruct { } } - /// Implementation of `IdLookup`. - pub(crate) fn id_lookup_impl(&self) -> Option { + /// Implementation of `LookupId`. + fn lookup_id_impl(&self) -> Option { match self.the_struct_kind() { TheStructKind::Id => None, TheStructKind::Pointer(db_lt) => { @@ -340,7 +340,7 @@ impl TrackedStruct { let jar_ty = self.jar_ty(); let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); Some(parse_quote_spanned! { ident.span() => - impl<#db, #parameters> salsa::id::IdLookup<& #db_lt #db> for #ident #type_generics + impl<#db, #parameters> salsa::id::LookupId<& #db_lt #db> for #ident #type_generics where #db: ?Sized + salsa::DbWithJar<#jar_ty>, #where_clause diff --git a/components/salsa-2022/src/id.rs b/components/salsa-2022/src/id.rs index 155004f3a..55015330c 100644 --- a/components/salsa-2022/src/id.rs +++ b/components/salsa-2022/src/id.rs @@ -97,7 +97,7 @@ impl AsId for () { /// up in the database to find it. This is different from /// [`AsId`][] where what we have is literally a *newtype* /// for an `Id`. -pub trait IdLookup { +pub trait LookupId { /// Convert to an `Id` fn into_id(self) -> Id; @@ -115,7 +115,7 @@ pub trait IdLookup { fn lookup_id(id: Id, db: DB) -> Self; } -impl IdLookup for ID +impl LookupId for ID where ID: AsId, { From 7519c3e2a6cc1ceaeb11b541acb677778a295ab8 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 18 May 2024 06:27:08 -0400 Subject: [PATCH 36/61] extend IdentityInterner to be based on LookupId --- .../salsa-2022-macros/src/tracked_fn.rs | 4 ++-- components/salsa-2022/src/interned.rs | 20 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index cbc1211fd..0e67af52c 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -431,7 +431,7 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); let __ingredients = <_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar); - let #key_var = __ingredients.intern_map.data(__id).clone(); + let #key_var = __ingredients.intern_map.data_with_db(__id, __db).clone(); #recovery_fn(__db, __cycle, #key_splat) } }; @@ -463,7 +463,7 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); let __ingredients = <_ as salsa::storage::HasIngredientsFor<#fn_ty>>::ingredient(__jar); - let #key_var = __ingredients.intern_map.data(__id).clone(); + let #key_var = __ingredients.intern_map.data_with_db(__id, __db).clone(); #inner_fn_name(__db, #key_splat) } }; diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index 6c276145d..295b6adcb 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -4,7 +4,7 @@ use std::hash::Hash; use std::marker::PhantomData; use crate::durability::Durability; -use crate::id::AsId; +use crate::id::{AsId, LookupId}; use crate::ingredient::{fmt_index, IngredientRequiresReset}; use crate::key::DependencyIndex; use crate::plumbing::transmute_lifetime; @@ -141,10 +141,20 @@ where unsafe { transmute_lifetime(self, &**r) } } + /// Lookup the data for an interned value based on its id. + /// Rarely used since end-users generally carry a struct with a pointer directly + /// to the interned item. pub fn data<'db>(&'db self, id: Id) -> &'db C::Data<'db> { self.interned_value(id).data() } + /// Variant of `data` that takes a (unnecessary) database argument. + /// This exists because tracked functions sometimes use true interning and sometimes use + /// [`IdentityInterner`][], which requires the database argument. + pub fn data_with_db<'db, DB: ?Sized>(&'db self, id: Id, _db: &'db DB) -> &'db C::Data<'db> { + self.data(id) + } + pub fn reset(&mut self, revision: Revision) { assert!(revision > self.reset_at); self.reset_at = revision; @@ -242,8 +252,12 @@ where id.as_id() } - pub fn data<'db>(&'db self, id: crate::Id) -> C::Data<'db> { - >::from_id(id) + pub fn data_with_db<'db, DB>(&'db self, id: crate::Id, db: &'db DB) -> C::Data<'db> + where + DB: ?Sized, + C::Data<'db>: LookupId<&'db DB>, + { + >::lookup_id(id, db) } } From b4b49fbd1bfb5ea7a261ac1ad4ac7b5cd557f6ba Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 18 May 2024 06:49:37 -0400 Subject: [PATCH 37/61] split the Id conversion traits We now have AsId (always present) and FromId/LookupId (sometimes present). AsId is no longer part of the salsa public interface. Replace the ad-hoc `salsa_id` etc methods with calls to `AsId::as_id`. --- components/salsa-2022-macros/src/input.rs | 2 + components/salsa-2022-macros/src/interned.rs | 12 ++-- .../salsa-2022-macros/src/salsa_struct.rs | 44 ++++++++++-- .../salsa-2022-macros/src/tracked_struct.rs | 8 +-- components/salsa-2022/src/id.rs | 71 ++++++++++--------- components/salsa-2022/src/input.rs | 7 +- components/salsa-2022/src/input_field.rs | 5 +- components/salsa-2022/src/interned.rs | 24 ++++--- components/salsa-2022/src/lib.rs | 1 - components/salsa-2022/src/setter.rs | 3 +- components/salsa-2022/src/tracked_struct.rs | 14 ++-- 11 files changed, 119 insertions(+), 72 deletions(-) diff --git a/components/salsa-2022-macros/src/input.rs b/components/salsa-2022-macros/src/input.rs index 2ea18aabb..154eda990 100644 --- a/components/salsa-2022-macros/src/input.rs +++ b/components/salsa-2022-macros/src/input.rs @@ -55,6 +55,7 @@ impl InputStruct { let inherent_impl = self.input_inherent_impl(); let ingredients_for_impl = self.input_ingredients(); let as_id_impl = self.as_id_impl(); + let from_id_impl = self.from_id_impl(); let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); @@ -63,6 +64,7 @@ impl InputStruct { #inherent_impl #ingredients_for_impl #as_id_impl + #from_id_impl #as_debug_with_db_impl #salsa_struct_in_db_impl }) diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 0955d39e6..ae3939445 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -62,6 +62,7 @@ impl InternedStruct { 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 from_id_impl = self.from_id_impl(); let lookup_id_impl = self.lookup_id_impl(); let named_fields_impl = self.inherent_impl_for_named_fields(); let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl(); @@ -76,6 +77,7 @@ impl InternedStruct { #data_struct #ingredients_for_impl #as_id_impl + #from_id_impl #lookup_id_impl #named_fields_impl #salsa_struct_in_db_impl @@ -227,7 +229,7 @@ impl InternedStruct { let salsa_id = quote!( pub fn salsa_id(&self) -> salsa::Id { - unsafe { &*self.0 }.salsa_id() + salsa::id::AsId::as_id(unsafe { &*self }) } ); @@ -293,9 +295,9 @@ impl InternedStruct { ) -> Self { let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #the_ident #type_generics >>::ingredient(jar); - Self(ingredients.intern(runtime, #data_ident { + salsa::id::FromId::from_as_id(ingredients.intern(runtime, #data_ident { #(#field_names,)* - }).salsa_id()) + })) } }; @@ -373,10 +375,6 @@ impl InternedStruct { #db: ?Sized + salsa::DbWithJar<#jar_ty>, #where_clause { - fn into_id(self) -> salsa::Id { - unsafe { &*self.0 }.salsa_id() - } - fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self { let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index 0e921059a..c65567bcd 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -327,7 +327,7 @@ impl SalsaStruct { pub(crate) fn access_salsa_id_from_self(&self) -> syn::Expr { match self.the_struct_kind() { TheStructKind::Id => parse_quote!(self.0), - TheStructKind::Pointer(_) => parse_quote!(unsafe { &*self.0 }.id()), + TheStructKind::Pointer(_) => parse_quote!(salsa::id::AsId::as_id(unsafe { &*self.0 })), } } @@ -374,21 +374,53 @@ impl SalsaStruct { default_db_lifetime(self.struct_item.generics.span()) } - /// Generate `impl salsa::AsId for Foo` - pub(crate) fn as_id_impl(&self) -> Option { + /// Generate `impl salsa::id::AsId for Foo` + pub(crate) fn as_id_impl(&self) -> syn::ItemImpl { match self.the_struct_kind() { TheStructKind::Id => { let ident = self.the_ident(); let (impl_generics, type_generics, where_clause) = self.struct_item.generics.split_for_impl(); - Some(parse_quote_spanned! { ident.span() => - impl #impl_generics salsa::AsId for #ident #type_generics + parse_quote_spanned! { ident.span() => + impl #impl_generics salsa::id::AsId for #ident #type_generics #where_clause { - fn as_id(self) -> salsa::Id { + fn as_id(&self) -> salsa::Id { self.0 } + } + } + } + TheStructKind::Pointer(_) => { + let ident = self.the_ident(); + let (impl_generics, type_generics, where_clause) = + self.struct_item.generics.split_for_impl(); + parse_quote_spanned! { ident.span() => + impl #impl_generics salsa::id::AsId for #ident #type_generics + #where_clause + { + fn as_id(&self) -> salsa::Id { + salsa::id::AsId::as_id(unsafe { &*self.0 }) + } + } + + } + } + } + } + + /// Generate `impl salsa::id::AsId for Foo` + pub(crate) fn from_id_impl(&self) -> Option { + match self.the_struct_kind() { + TheStructKind::Id => { + let ident = self.the_ident(); + let (impl_generics, type_generics, where_clause) = + self.struct_item.generics.split_for_impl(); + Some(parse_quote_spanned! { ident.span() => + impl #impl_generics salsa::id::FromId for #ident #type_generics + #where_clause + { fn from_id(id: salsa::Id) -> Self { #ident(id) } diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 381dabda2..df3974f88 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -64,6 +64,7 @@ impl TrackedStruct { let tracked_struct_in_db_impl = self.tracked_struct_in_db_impl(); let update_impl = self.update_impl(); let as_id_impl = self.as_id_impl(); + let from_id_impl = self.from_id_impl(); let lookup_id_impl = self.lookup_id_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); Ok(quote! { @@ -76,6 +77,7 @@ impl TrackedStruct { #tracked_struct_in_db_impl #update_impl #as_id_impl + #from_id_impl #lookup_id_impl #as_debug_with_db_impl }) @@ -227,7 +229,7 @@ impl TrackedStruct { let salsa_id = self.access_salsa_id_from_self(); let ctor = match the_kind { - TheStructKind::Id => quote!(salsa::AsId::from_id(#data.id())), + TheStructKind::Id => quote!(salsa::id::FromId::from_as_id(#data)), TheStructKind::Pointer(_) => quote!(Self(#data, std::marker::PhantomData)), }; @@ -345,10 +347,6 @@ impl TrackedStruct { #db: ?Sized + salsa::DbWithJar<#jar_ty>, #where_clause { - fn into_id(self) -> salsa::Id { - unsafe { &*self.0 }.id() - } - fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self { let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); diff --git a/components/salsa-2022/src/id.rs b/components/salsa-2022/src/id.rs index 55015330c..2249aface 100644 --- a/components/salsa-2022/src/id.rs +++ b/components/salsa-2022/src/id.rs @@ -65,17 +65,46 @@ impl From for usize { } } -/// Internal Salsa trait for types that can be interconverted to a salsa Id; -pub trait AsId: Sized + Copy + Eq + Hash + Debug { - fn as_id(self) -> Id; +/// Internal salsa trait for types that can be represented as a salsa id. +pub trait AsId: Sized { + fn as_id(&self) -> Id; +} + +/// Internal Salsa trait for types that have a salsa id but require looking +/// up in the database to find it. This is different from +/// [`AsId`][] where what we have is literally a *newtype* +/// for an `Id`. +pub trait LookupId: AsId { + /// Lookup from an `Id` to get an instance of the type. + /// + /// # Panics + /// + /// This fn may panic if the value with this id has not been + /// produced in this revision already (e.g., for a tracked + /// struct, the function will panic if the tracked struct + /// has not yet been created in this revision). Salsa's + /// dependency tracking typically ensures this does not + /// occur, but it is possible for a user to violate this + /// rule. + fn lookup_id(id: Id, db: DB) -> Self; +} + +/// Internal Salsa trait for types that are just a newtype'd [`Id`][]. +pub trait FromId: AsId + Copy + Eq + Hash + Debug { fn from_id(id: Id) -> Self; + + fn from_as_id(id: &impl AsId) -> Self { + Self::from_id(id.as_id()) + } } impl AsId for Id { - fn as_id(self) -> Id { - self + fn as_id(&self) -> Id { + *self } +} +impl FromId for Id { fn from_id(id: Id) -> Self { id } @@ -84,45 +113,21 @@ impl AsId for Id { /// As a special case, we permit `()` to be converted to an `Id`. /// This is useful for declaring functions with no arguments. impl AsId for () { - fn as_id(self) -> Id { + fn as_id(&self) -> Id { Id::from_u32(0) } +} +impl FromId for () { fn from_id(id: Id) -> Self { assert_eq!(0, id.as_u32()); } } -/// Internal Salsa trait for types that have a salsa id but require looking -/// up in the database to find it. This is different from -/// [`AsId`][] where what we have is literally a *newtype* -/// for an `Id`. -pub trait LookupId { - /// Convert to an `Id` - fn into_id(self) -> Id; - - /// Lookup from an `Id` to get an instance of the type. - /// - /// # Panics - /// - /// This fn may panic if the value with this id has not been - /// produced in this revision already (e.g., for a tracked - /// struct, the function will panic if the tracked struct - /// has not yet been created in this revision). Salsa's - /// dependency tracking typically ensures this does not - /// occur, but it is possible for a user to violate this - /// rule. - fn lookup_id(id: Id, db: DB) -> Self; -} - impl LookupId for ID where - ID: AsId, + ID: FromId, { - fn into_id(self) -> Id { - self.as_id() - } - fn lookup_id(id: Id, _db: DB) -> Self { Self::from_id(id) } diff --git a/components/salsa-2022/src/input.rs b/components/salsa-2022/src/input.rs index 6c6900c05..52c7f862f 100644 --- a/components/salsa-2022/src/input.rs +++ b/components/salsa-2022/src/input.rs @@ -5,14 +5,15 @@ use std::{ use crate::{ cycle::CycleRecoveryStrategy, + id::FromId, ingredient::{fmt_index, Ingredient, IngredientRequiresReset}, key::{DatabaseKeyIndex, DependencyIndex}, runtime::{local_state::QueryOrigin, Runtime}, - AsId, IngredientIndex, Revision, + IngredientIndex, Revision, }; -pub trait InputId: AsId {} -impl InputId for T {} +pub trait InputId: FromId {} +impl InputId for T {} pub struct InputIngredient where diff --git a/components/salsa-2022/src/input_field.rs b/components/salsa-2022/src/input_field.rs index 187dafa72..a63a5fc58 100644 --- a/components/salsa-2022/src/input_field.rs +++ b/components/salsa-2022/src/input_field.rs @@ -1,10 +1,11 @@ use crate::cycle::CycleRecoveryStrategy; +use crate::id::{AsId, FromId}; 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}; +use crate::{DatabaseKeyIndex, Durability, Id, IngredientIndex, Revision, Runtime}; use dashmap::mapref::entry::Entry; use dashmap::DashMap; use std::fmt; @@ -107,7 +108,7 @@ where impl Ingredient for InputFieldIngredient where - K: AsId, + K: FromId, { fn ingredient_index(&self) -> IngredientIndex { self.index diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index 295b6adcb..3739c6841 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -82,7 +82,7 @@ where } pub fn intern_id<'db>(&'db self, runtime: &'db Runtime, data: C::Data<'db>) -> crate::Id { - self.intern(runtime, data).salsa_id() + self.intern(runtime, data).as_id() } /// Intern data to a unique reference. @@ -117,7 +117,7 @@ where // We won any races so should intern the data dashmap::mapref::entry::Entry::Vacant(entry) => { let next_id = self.counter.fetch_add(1); - let next_id = Id::from_id(crate::id::Id::from_u32(next_id)); + let next_id = crate::id::Id::from_u32(next_id); let value = self .value_map .entry(next_id) @@ -233,7 +233,6 @@ where pub struct IdentityInterner where C: Configuration, - for<'db> C::Data<'db>: AsId, { data: PhantomData, } @@ -241,14 +240,16 @@ where impl IdentityInterner where C: Configuration, - for<'db> C::Data<'db>: AsId, { #[allow(clippy::new_without_default)] pub fn new() -> Self { IdentityInterner { data: PhantomData } } - pub fn intern_id<'db>(&'db self, _runtime: &'db Runtime, id: C::Data<'db>) -> crate::Id { + pub fn intern_id<'db>(&'db self, _runtime: &'db Runtime, id: C::Data<'db>) -> crate::Id + where + C::Data<'db>: AsId, + { id.as_id() } @@ -265,10 +266,6 @@ impl ValueStruct where C: Configuration, { - pub fn salsa_id(&self) -> Id { - self.id - } - pub fn data<'db>(&'db self) -> &'db C::Data<'db> { // SAFETY: The lifetime of `self` is tied to the interning ingredient; // we never remove data without an `&mut self` access to the interning ingredient. @@ -279,3 +276,12 @@ where unsafe { std::mem::transmute(fields) } } } + +impl AsId for ValueStruct +where + C: Configuration, +{ + fn as_id(&self) -> Id { + self.id + } +} diff --git a/components/salsa-2022/src/lib.rs b/components/salsa-2022/src/lib.rs index b21b47d21..b4ca065dc 100644 --- a/components/salsa-2022/src/lib.rs +++ b/components/salsa-2022/src/lib.rs @@ -35,7 +35,6 @@ pub use self::debug::DebugWithDb; pub use self::durability::Durability; pub use self::event::Event; pub use self::event::EventKind; -pub use self::id::AsId; pub use self::id::Id; pub use self::key::DatabaseKeyIndex; pub use self::revision::Revision; diff --git a/components/salsa-2022/src/setter.rs b/components/salsa-2022/src/setter.rs index f23289a2a..564be092d 100644 --- a/components/salsa-2022/src/setter.rs +++ b/components/salsa-2022/src/setter.rs @@ -1,5 +1,6 @@ +use crate::id::AsId; use crate::input_field::InputFieldIngredient; -use crate::{AsId, Durability, Runtime}; +use crate::{Durability, Runtime}; use std::hash::Hash; #[must_use] diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 21429792d..b5640e413 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -455,11 +455,6 @@ impl ValueStruct where C: Configuration, { - /// The id of this struct in the ingredient. - pub fn id(&self) -> Id { - self.id - } - /// 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. @@ -485,3 +480,12 @@ where unsafe { std::mem::transmute(fields) } } } + +impl AsId for ValueStruct +where + C: Configuration, +{ + fn as_id(&self) -> Id { + self.id + } +} From 56030df782790240b124760c8b262258814fa7d3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 18 May 2024 06:56:04 -0400 Subject: [PATCH 38/61] convert a test to use 'db in tracked functions It works! Huzzah. --- salsa-2022-tests/tests/deletion-cascade.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/salsa-2022-tests/tests/deletion-cascade.rs b/salsa-2022-tests/tests/deletion-cascade.rs index db935f8e4..036e4adcb 100644 --- a/salsa-2022-tests/tests/deletion-cascade.rs +++ b/salsa-2022-tests/tests/deletion-cascade.rs @@ -11,7 +11,7 @@ use test_log::test; #[salsa::jar(db = Db)] struct Jar( MyInput, - MyTracked, + MyTracked<'_>, final_result, create_tracked_structs, contribution_from_struct, @@ -36,12 +36,12 @@ fn final_result(db: &dyn Db, input: MyInput) -> u32 { } #[salsa::tracked] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked] -fn create_tracked_structs(db: &dyn Db, input: MyInput) -> Vec { +fn create_tracked_structs<'db>(db: &'db dyn Db, input: MyInput) -> Vec> { db.push_log(format!("intermediate_result({:?})", input)); (0..input.field(db)) .map(|i| MyTracked::new(db, i)) @@ -49,13 +49,13 @@ fn create_tracked_structs(db: &dyn Db, input: MyInput) -> Vec { } #[salsa::tracked] -fn contribution_from_struct(db: &dyn Db, tracked: MyTracked) -> u32 { +fn contribution_from_struct<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> u32 { let m = MyTracked::new(db, tracked.field(db)); copy_field(db, m) * 2 } #[salsa::tracked] -fn copy_field(db: &dyn Db, tracked: MyTracked) -> u32 { +fn copy_field<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> u32 { tracked.field(db) } From 06b70975e917ff0e7b8406b2625d9ab8db0f3022 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 19 May 2024 07:42:17 -0400 Subject: [PATCH 39/61] impl Update/Send/Sync and add dedicated tests for that --- components/salsa-2022-macros/src/interned.rs | 4 ++ .../salsa-2022-macros/src/salsa_struct.rs | 43 +++++++++++++++ .../salsa-2022-macros/src/tracked_struct.rs | 20 +------ salsa-2022-tests/tests/is_send_sync.rs | 52 +++++++++++++++++++ salsa-2022-tests/tests/tracked_with_intern.rs | 37 +++++++++++++ 5 files changed, 138 insertions(+), 18 deletions(-) create mode 100644 salsa-2022-tests/tests/is_send_sync.rs create mode 100644 salsa-2022-tests/tests/tracked_with_intern.rs diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index ae3939445..45ce0b853 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -64,9 +64,11 @@ impl InternedStruct { let as_id_impl = self.as_id_impl(); let from_id_impl = self.from_id_impl(); let lookup_id_impl = self.lookup_id_impl(); + let send_sync_impls = self.send_sync_impls(); let named_fields_impl = self.inherent_impl_for_named_fields(); let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); + let update_impl = self.update_impl(); Ok(crate::debug::dump_tokens( self.the_ident(), @@ -79,9 +81,11 @@ impl InternedStruct { #as_id_impl #from_id_impl #lookup_id_impl + #(#send_sync_impls)* #named_fields_impl #salsa_struct_in_db_impl #as_debug_with_db_impl + #update_impl }, )) } diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index c65567bcd..5380b9c64 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -110,6 +110,30 @@ impl SalsaStruct { db_lifetime::require_db_lifetime(&self.struct_item.generics) } + pub(crate) fn send_sync_impls(&self) -> Vec { + match self.the_struct_kind() { + TheStructKind::Id => vec![], + TheStructKind::Pointer(_db) => { + let (the_ident, _, impl_generics, type_generics, where_clauses) = + self.the_ident_and_generics(); + vec![ + parse_quote! { + unsafe impl #impl_generics std::marker::Send for #the_ident #type_generics + where + #where_clauses + {} + }, + parse_quote! { + unsafe impl #impl_generics std::marker::Sync for #the_ident #type_generics + where + #where_clauses + {} + }, + ] + } + } + } + /// Some salsa structs require a "Configuration" struct /// because they make use of GATs. This function /// synthesizes a name and generates the struct declaration. @@ -485,6 +509,25 @@ impl SalsaStruct { }) } + /// Implementation of `salsa::update::Update`. + pub(crate) fn update_impl(&self) -> syn::ItemImpl { + let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); + parse_quote! { + unsafe impl #impl_generics salsa::update::Update for #ident #type_generics + #where_clause + { + unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { + if unsafe { *old_pointer } != new_value { + unsafe { *old_pointer = new_value }; + true + } else { + false + } + } + } + } + } + /// Disallow `#[id]` attributes on the fields of this struct. /// /// If an `#[id]` field is found, return an error. diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index df3974f88..1d26ee8e5 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -64,6 +64,7 @@ impl TrackedStruct { let tracked_struct_in_db_impl = self.tracked_struct_in_db_impl(); let update_impl = self.update_impl(); let as_id_impl = self.as_id_impl(); + let send_sync_impls = self.send_sync_impls(); let from_id_impl = self.from_id_impl(); let lookup_id_impl = self.lookup_id_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); @@ -78,6 +79,7 @@ impl TrackedStruct { #update_impl #as_id_impl #from_id_impl + #(#send_sync_impls)* #lookup_id_impl #as_debug_with_db_impl }) @@ -400,24 +402,6 @@ impl TrackedStruct { } } - /// Implementation of `Update`. - fn update_impl(&self) -> syn::ItemImpl { - let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); - parse_quote! { - unsafe impl #impl_generics salsa::update::Update for #ident #type_generics - #where_clause - { - unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { - if unsafe { *old_pointer } != new_value { - unsafe { *old_pointer = new_value }; - true - } else { - false - } - } - } - } - } /// The index of the tracked struct ingredient in the ingredient tuple. fn tracked_struct_ingredient_index(&self) -> Literal { Literal::usize_unsuffixed(0) diff --git a/salsa-2022-tests/tests/is_send_sync.rs b/salsa-2022-tests/tests/is_send_sync.rs new file mode 100644 index 000000000..f110b1e64 --- /dev/null +++ b/salsa-2022-tests/tests/is_send_sync.rs @@ -0,0 +1,52 @@ +//! Test that a setting a field on a `#[salsa::input]` +//! overwrites and returns the old value. + +use test_log::test; + +#[salsa::jar(db = Db)] +struct Jar(MyInput, MyTracked<'_>, MyInterned<'_>, test); + +trait Db: salsa::DbWithJar {} + +#[salsa::db(Jar)] +#[derive(Default)] +struct Database { + storage: salsa::Storage, +} + +impl salsa::Database for Database {} + +impl Db for Database {} + +#[salsa::input] +struct MyInput { + field: String, +} + +#[salsa::tracked] +struct MyTracked<'db> { + field: MyInterned<'db>, +} + +#[salsa::interned] +struct MyInterned<'db> { + field: String, +} + +#[salsa::tracked] +fn test(db: &dyn crate::Db, input: MyInput) { + let input = is_send_sync(input); + let interned = is_send_sync(MyInterned::new(db, input.field(db).clone())); + let _tracked_struct = is_send_sync(MyTracked::new(db, interned)); +} + +fn is_send_sync(t: T) -> T { + t +} + +#[test] +fn execute() { + let db = Database::default(); + let input = MyInput::new(&db, "Hello".to_string()); + test(&db, input); +} diff --git a/salsa-2022-tests/tests/tracked_with_intern.rs b/salsa-2022-tests/tests/tracked_with_intern.rs new file mode 100644 index 000000000..d344ee3e0 --- /dev/null +++ b/salsa-2022-tests/tests/tracked_with_intern.rs @@ -0,0 +1,37 @@ +//! Test that a setting a field on a `#[salsa::input]` +//! overwrites and returns the old value. + +use test_log::test; + +#[salsa::jar(db = Db)] +struct Jar(MyInput, MyTracked<'_>, MyInterned<'_>); + +trait Db: salsa::DbWithJar {} + +#[salsa::db(Jar)] +#[derive(Default)] +struct Database { + storage: salsa::Storage, +} + +impl salsa::Database for Database {} + +impl Db for Database {} + +#[salsa::input] +struct MyInput { + field: String, +} + +#[salsa::tracked] +struct MyTracked<'db> { + field: MyInterned<'db>, +} + +#[salsa::interned] +struct MyInterned<'db> { + field: String, +} + +#[test] +fn execute() {} From 2800076857a481d184717579a8887ccbf5ffefcd Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 20 May 2024 06:26:13 -0400 Subject: [PATCH 40/61] update to syn 2.0 --- components/salsa-2022-macros/Cargo.toml | 2 +- .../salsa-2022-macros/src/configuration.rs | 10 ++--- components/salsa-2022-macros/src/input.rs | 10 ++--- components/salsa-2022-macros/src/interned.rs | 8 ++-- components/salsa-2022-macros/src/jar.rs | 11 +++++- .../salsa-2022-macros/src/salsa_struct.rs | 23 ++++++++---- .../salsa-2022-macros/src/tracked_fn.rs | 37 ++++++++++--------- .../salsa-2022-macros/src/tracked_struct.rs | 2 +- 8 files changed, 61 insertions(+), 42 deletions(-) diff --git a/components/salsa-2022-macros/Cargo.toml b/components/salsa-2022-macros/Cargo.toml index 2cc459838..a917bf6da 100644 --- a/components/salsa-2022-macros/Cargo.toml +++ b/components/salsa-2022-macros/Cargo.toml @@ -10,5 +10,5 @@ proc-macro = true heck = "0.4" proc-macro2 = "1.0" quote = "1.0" -syn = { version = "1.0", features = ["full", "extra-traits", "visit-mut"] } eyre = "0.6.5" +syn = { version = "2.0.64", features = ["visit-mut"] } diff --git a/components/salsa-2022-macros/src/configuration.rs b/components/salsa-2022-macros/src/configuration.rs index df16fe4f0..65ad7f37d 100644 --- a/components/salsa-2022-macros/src/configuration.rs +++ b/components/salsa-2022-macros/src/configuration.rs @@ -5,9 +5,9 @@ pub(crate) struct Configuration { pub(crate) input_ty: syn::Type, pub(crate) value_ty: syn::Type, pub(crate) cycle_strategy: CycleRecoveryStrategy, - pub(crate) backdate_fn: syn::ImplItemMethod, - pub(crate) execute_fn: syn::ImplItemMethod, - pub(crate) recover_fn: syn::ImplItemMethod, + pub(crate) backdate_fn: syn::ImplItemFn, + pub(crate) execute_fn: syn::ImplItemFn, + pub(crate) recover_fn: syn::ImplItemFn, } impl Configuration { @@ -58,7 +58,7 @@ impl quote::ToTokens for CycleRecoveryStrategy { /// Returns an appropriate definition for `should_backdate_value` depending on /// whether this value is memoized or not. -pub(crate) fn should_backdate_value_fn(should_backdate: bool) -> syn::ImplItemMethod { +pub(crate) fn should_backdate_value_fn(should_backdate: bool) -> syn::ImplItemFn { if should_backdate { parse_quote! { fn should_backdate_value(v1: &Self::Value<'_>, v2: &Self::Value<'_>) -> bool { @@ -76,7 +76,7 @@ pub(crate) fn should_backdate_value_fn(should_backdate: bool) -> syn::ImplItemMe /// Returns an appropriate definition for `recover_from_cycle` for cases where /// the cycle recovery is panic. -pub(crate) fn panic_cycle_recovery_fn() -> syn::ImplItemMethod { +pub(crate) fn panic_cycle_recovery_fn() -> syn::ImplItemFn { parse_quote! { fn recover_from_cycle<'db>( _db: &'db salsa::function::DynDb<'db, Self>, diff --git a/components/salsa-2022-macros/src/input.rs b/components/salsa-2022-macros/src/input.rs index 154eda990..9c043daf3 100644 --- a/components/salsa-2022-macros/src/input.rs +++ b/components/salsa-2022-macros/src/input.rs @@ -83,7 +83,7 @@ impl InputStruct { let field_tys: Vec<_> = self.all_field_tys(); let field_clones: Vec<_> = self.all_fields().map(SalsaField::is_clone_field).collect(); let get_field_names: Vec<_> = self.all_get_field_names(); - let field_getters: Vec = field_indices.iter().zip(&get_field_names).zip(&field_vises).zip(&field_tys).zip(&field_clones).map(|((((field_index, get_field_name), field_vis), field_ty), is_clone_field)| + let field_getters: Vec = field_indices.iter().zip(&get_field_names).zip(&field_vises).zip(&field_tys).zip(&field_clones).map(|((((field_index, get_field_name), field_vis), field_ty), is_clone_field)| if !*is_clone_field { parse_quote_spanned! { get_field_name.span() => #field_vis fn #get_field_name<'db>(self, __db: &'db #db_dyn_ty) -> &'db #field_ty @@ -108,7 +108,7 @@ impl InputStruct { // setters let set_field_names = self.all_set_field_names(); - let field_setters: Vec = field_indices.iter() + let field_setters: Vec = field_indices.iter() .zip(&set_field_names) .zip(&field_vises) .zip(&field_tys) @@ -128,7 +128,7 @@ impl InputStruct { let constructor_name = self.constructor_name(); let singleton = self.0.is_isingleton(); - let constructor: syn::ImplItemMethod = if singleton { + let constructor: syn::ImplItemFn = if singleton { parse_quote_spanned! { constructor_name.span() => /// Creates a new singleton input /// @@ -168,7 +168,7 @@ impl InputStruct { ); if singleton { - let get: syn::ImplItemMethod = parse_quote! { + let get: syn::ImplItemFn = parse_quote! { #[track_caller] pub fn get(__db: &#db_dyn_ty) -> Self { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); @@ -177,7 +177,7 @@ impl InputStruct { } }; - let try_get: syn::ImplItemMethod = parse_quote! { + let try_get: syn::ImplItemFn = parse_quote! { #[track_caller] pub fn try_get(__db: &#db_dyn_ty) -> Option { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 45ce0b853..027e1d923 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -187,7 +187,7 @@ impl InternedStruct { let db_dyn_ty = self.db_dyn_ty(); let jar_ty = self.jar_ty(); - let field_getters: Vec = self + let field_getters: Vec = self .all_fields() .map(|field| { let field_name = field.name(); @@ -214,7 +214,7 @@ impl InternedStruct { let field_tys = self.all_field_tys(); let data_ident = self.data_ident(); let constructor_name = self.constructor_name(); - let new_method: syn::ImplItemMethod = parse_quote_spanned! { constructor_name.span() => + let new_method: syn::ImplItemFn = parse_quote_spanned! { constructor_name.span() => #vis fn #constructor_name( db: &#db_dyn_ty, #(#field_names: #field_tys,)* @@ -261,7 +261,7 @@ impl InternedStruct { let db_dyn_ty = self.db_dyn_ty(); let jar_ty = self.jar_ty(); - let field_getters: Vec = self + let field_getters: Vec = self .all_fields() .map(|field| { let field_name = field.name(); @@ -292,7 +292,7 @@ impl InternedStruct { let field_tys = self.all_field_tys(); let data_ident = self.data_ident(); let constructor_name = self.constructor_name(); - let new_method: syn::ImplItemMethod = parse_quote_spanned! { constructor_name.span() => + let new_method: syn::ImplItemFn = parse_quote_spanned! { constructor_name.span() => #vis fn #constructor_name( db: &#db_dyn_ty, #(#field_names: #field_tys,)* diff --git a/components/salsa-2022-macros/src/jar.rs b/components/salsa-2022-macros/src/jar.rs index 14fd134d2..cfba9229d 100644 --- a/components/salsa-2022-macros/src/jar.rs +++ b/components/salsa-2022-macros/src/jar.rs @@ -1,4 +1,5 @@ -use proc_macro2::Literal; +use proc_macro2::extra::DelimSpan; +use proc_macro2::{Delimiter, Group, Literal, TokenStream}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::visit_mut::VisitMut; @@ -166,7 +167,7 @@ fn generate_fields(input: &ItemStruct) -> FieldsUnnamed { }, syn::Fields::Unnamed(f) => f.paren_token, syn::Fields::Unit => syn::token::Paren { - span: input.ident.span(), + span: to_delim_span(input), }, }; @@ -175,3 +176,9 @@ fn generate_fields(input: &ItemStruct) -> FieldsUnnamed { unnamed: output_fields, } } + +fn to_delim_span(s: &impl Spanned) -> DelimSpan { + let mut group = Group::new(Delimiter::None, TokenStream::new()); + group.set_span(s.span()); + group.delim_span() +} diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index 5380b9c64..cb7a0c0ad 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -53,7 +53,7 @@ const BANNED_FIELD_NAMES: &[&str] = &["from", "new"]; /// Classifies the kind of field stored in this salsa /// struct. -#[derive(Debug, PartialEq, Eq)] +#[derive(PartialEq, Eq)] pub enum TheStructKind { /// Stores an "id" Id, @@ -62,6 +62,15 @@ pub enum TheStructKind { Pointer(syn::Lifetime), } +impl std::fmt::Debug for TheStructKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TheStructKind::Id => write!(f, "Id"), + TheStructKind::Pointer(lt) => write!(f, "Pointer({lt})"), + } + } +} + impl SalsaStruct { pub(crate) fn new( args: proc_macro::TokenStream, @@ -164,7 +173,7 @@ impl SalsaStruct { .attrs .iter() .map(|attr| { - if attr.path.is_ident("customize") { + if attr.path().is_ident("customize") { // FIXME: this should be a comma separated list but I couldn't // be bothered to remember how syn does this. let args: syn::Ident = attr.parse_args()?; @@ -294,8 +303,8 @@ impl SalsaStruct { .struct_item .attrs .iter() - .filter(|attr| !attr.path.is_ident("derive")) - .filter(|attr| !attr.path.is_ident("customize")) + .filter(|attr| !attr.path().is_ident("derive")) + .filter(|attr| !attr.path().is_ident("customize")) .collect(); parse_quote_spanned! { ident.span() => @@ -330,8 +339,8 @@ impl SalsaStruct { .struct_item .attrs .iter() - .filter(|attr| !attr.path.is_ident("derive")) - .filter(|attr| !attr.path.is_ident("customize")) + .filter(|attr| !attr.path().is_ident("derive")) + .filter(|attr| !attr.path().is_ident("customize")) .collect(); let module = &self.module; @@ -600,7 +609,7 @@ impl SalsaField { // Scan the attributes and look for the salsa attributes: for attr in &field.attrs { for (fa, func) in FIELD_OPTION_ATTRIBUTES { - if attr.path.is_ident(fa) { + if attr.path().is_ident(fa) { func(attr, &mut result); } } diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index 0e67af52c..8236cd590 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -1,4 +1,4 @@ -use proc_macro2::{Literal, TokenStream}; +use proc_macro2::{Literal, Span, TokenStream}; use syn::spanned::Spanned; use syn::visit_mut::VisitMut; use syn::{ReturnType, Token}; @@ -119,23 +119,23 @@ pub(crate) fn tracked_impl( .iter_mut() .filter_map(|item| { let item_method = match item { - syn::ImplItem::Method(item_method) => item_method, + syn::ImplItem::Fn(item_method) => item_method, _ => return None, }; let salsa_tracked_attr = item_method.attrs.iter().position(|attr| { - let path = &attr.path.segments; + let path = &attr.path().segments; path.len() == 2 - && path[0].arguments == syn::PathArguments::None + && path[0].arguments.is_none() && path[0].ident == "salsa" - && path[1].arguments == syn::PathArguments::None + && path[1].arguments.is_none() && path[1].ident == "tracked" })?; let salsa_tracked_attr = item_method.attrs.remove(salsa_tracked_attr); - let inner_args = if !salsa_tracked_attr.tokens.is_empty() { - salsa_tracked_attr.parse_args() - } else { - Ok(FnArgs::default()) + let inner_args = match salsa_tracked_attr.meta { + syn::Meta::Path(_) => Ok(FnArgs::default()), + syn::Meta::List(_) | syn::Meta::NameValue(_) => salsa_tracked_attr.parse_args(), }; + let inner_args = match inner_args { Ok(inner_args) => inner_args, Err(err) => return Some(Err(err)), @@ -194,7 +194,7 @@ impl crate::options::AllowedOptions for TrackedImpl { fn tracked_method( outer_args: &ImplArgs, mut args: FnArgs, - item_method: &mut syn::ImplItemMethod, + item_method: &mut syn::ImplItemFn, self_type: &syn::TypePath, name: &str, ) -> syn::Result { @@ -670,7 +670,7 @@ fn setter_fn( args: &FnArgs, item_fn: &syn::ItemFn, config_ty: &syn::Type, -) -> syn::Result { +) -> syn::Result { // The setter has *always* the same signature as the original: // but it takes a value arg and has no return type. let jar_ty = args.jar_ty(); @@ -691,7 +691,7 @@ fn setter_fn( let value_arg = syn::Ident::new("__value", item_fn.sig.output.span()); setter_sig.inputs.push(parse_quote!(#value_arg: #value_ty)); setter_sig.output = ReturnType::Default; - Ok(syn::ImplItemMethod { + Ok(syn::ImplItemFn { attrs: vec![], vis: item_fn.vis.clone(), defaultness: None, @@ -722,7 +722,7 @@ fn setter_fn( fn set_lru_capacity_fn( args: &FnArgs, config_ty: &syn::Type, -) -> syn::Result> { +) -> syn::Result> { if args.lru.is_none() { return Ok(None); } @@ -744,7 +744,7 @@ fn specify_fn( args: &FnArgs, item_fn: &syn::ItemFn, config_ty: &syn::Type, -) -> syn::Result> { +) -> syn::Result> { if args.specify.is_none() { return Ok(None); } @@ -759,7 +759,7 @@ fn specify_fn( let value_arg = syn::Ident::new("__value", item_fn.sig.output.span()); setter_sig.inputs.push(parse_quote!(#value_arg: #value_ty)); setter_sig.output = ReturnType::Default; - Ok(Some(syn::ImplItemMethod { + Ok(Some(syn::ImplItemFn { attrs: vec![], vis: item_fn.vis.clone(), defaultness: None, @@ -784,7 +784,10 @@ fn make_fn_return_ref(fn_sig: &mut syn::Signature) -> syn::Result<()> { let (db_lifetime, _) = db_lifetime_and_ty(fn_sig)?; let (right_arrow, elem) = match fn_sig.output.clone() { - ReturnType::Default => (syn::Token![->](fn_sig.paren_token.span), parse_quote!(())), + ReturnType::Default => ( + syn::Token![->]([Span::call_site(), Span::call_site()]), + parse_quote!(()), + ), ReturnType::Type(rarrow, ty) => (rarrow, ty), }; @@ -821,7 +824,7 @@ fn db_lifetime_and_ty(func: &mut syn::Signature) -> syn::Result<(syn::Lifetime, let ident = syn::Ident::new("__db", and_token_span); func.generics.params.insert( 0, - syn::LifetimeDef { + syn::LifetimeParam { attrs: vec![], lifetime: syn::Lifetime { apostrophe: and_token_span, diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 1d26ee8e5..e86ca6c45 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -173,7 +173,7 @@ impl TrackedStruct { 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 = 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)| + let field_getters: Vec = 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)| match the_kind { TheStructKind::Id => { if !*is_clone_field { From d98485d3cb84dc09e67f1e3c2fcf31dc96390a1e Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 20 May 2024 07:28:28 -0400 Subject: [PATCH 41/61] add a derive for salsa::Update And a test for it. This is required if you want to have structures in your tracked structs. Looks we need one for salsa::DebugWithDb, too. --- components/salsa-2022-macros/Cargo.toml | 1 + components/salsa-2022-macros/src/lib.rs | 10 +++ components/salsa-2022-macros/src/update.rs | 86 +++++++++++++++++++ components/salsa-2022/src/lib.rs | 1 + .../tests/tracked_with_struct_db.rs | 68 +++++++++++++++ 5 files changed, 166 insertions(+) create mode 100644 components/salsa-2022-macros/src/update.rs create mode 100644 salsa-2022-tests/tests/tracked_with_struct_db.rs diff --git a/components/salsa-2022-macros/Cargo.toml b/components/salsa-2022-macros/Cargo.toml index a917bf6da..d44ad29c3 100644 --- a/components/salsa-2022-macros/Cargo.toml +++ b/components/salsa-2022-macros/Cargo.toml @@ -12,3 +12,4 @@ proc-macro2 = "1.0" quote = "1.0" eyre = "0.6.5" syn = { version = "2.0.64", features = ["visit-mut"] } +synstructure = "0.13.1" diff --git a/components/salsa-2022-macros/src/lib.rs b/components/salsa-2022-macros/src/lib.rs index 596cb18b7..52b429f02 100644 --- a/components/salsa-2022-macros/src/lib.rs +++ b/components/salsa-2022-macros/src/lib.rs @@ -49,6 +49,7 @@ mod salsa_struct; mod tracked; mod tracked_fn; mod tracked_struct; +mod update; mod xform; #[proc_macro_attribute] @@ -80,3 +81,12 @@ pub fn input(args: TokenStream, input: TokenStream) -> TokenStream { pub fn tracked(args: TokenStream, input: TokenStream) -> TokenStream { tracked::tracked(args, input) } + +#[proc_macro_derive(Update)] +pub fn update(input: TokenStream) -> TokenStream { + let item = syn::parse_macro_input!(input as syn::DeriveInput); + match update::update_derive(item) { + Ok(tokens) => tokens.into(), + Err(err) => err.to_compile_error().into(), + } +} diff --git a/components/salsa-2022-macros/src/update.rs b/components/salsa-2022-macros/src/update.rs new file mode 100644 index 000000000..56ae868a7 --- /dev/null +++ b/components/salsa-2022-macros/src/update.rs @@ -0,0 +1,86 @@ +use proc_macro2::{Literal, Span, TokenStream}; +use synstructure::BindStyle; + +pub(crate) fn update_derive(input: syn::DeriveInput) -> syn::Result { + if let syn::Data::Union(_) = &input.data { + return Err(syn::Error::new_spanned( + &input.ident, + "`derive(Update)` does not support `union`", + )); + } + + let mut structure = synstructure::Structure::new(&input); + + for v in structure.variants_mut() { + v.bind_with(|_| BindStyle::Move); + } + + let old_pointer = syn::Ident::new("old_pointer", Span::call_site()); + let new_value = syn::Ident::new("new_value", Span::call_site()); + + let fields: TokenStream = structure + .variants() + .iter() + .map(|variant| { + let variant_pat = variant.pat(); + + // First check that the `new_value` has same variant. + // Extract its fields and convert to a tuple. + let make_tuple = variant + .bindings() + .iter() + .fold(quote!(), |tokens, binding| quote!(#tokens #binding,)); + let make_new_value = quote! { + let #new_value = if let #variant_pat = #new_value { + (#make_tuple) + } else { + *#old_pointer = #new_value; + return true; + }; + }; + + // For each field, invoke `maybe_update` recursively to update its value. + // Or the results together (using `|`, not `||`, to avoid shortcircuiting) + // to get the final return value. + let update_fields = variant.bindings().iter().zip(0..).fold( + quote!(false), + |tokens, (binding, index)| { + let field_ty = &binding.ast().ty; + let field_index = Literal::usize_unsuffixed(index); + + quote! { + #tokens | + unsafe { + salsa::update::helper::Dispatch::<#field_ty>::maybe_update( + #binding, + #new_value.#field_index, + ) + } + } + }, + ); + + quote!( + #variant_pat => { + #make_new_value + #update_fields + } + ) + }) + .collect(); + + let ident = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let tokens = quote! { + unsafe impl #impl_generics salsa::update::Update for #ident #ty_generics #where_clause { + unsafe fn maybe_update(#old_pointer: *mut Self, #new_value: Self) -> bool { + let old_pointer = unsafe { &mut *#old_pointer }; + match #old_pointer { + #fields + } + } + } + }; + + Ok(crate::debug::dump_tokens(&input.ident, tokens)) +} diff --git a/components/salsa-2022/src/lib.rs b/components/salsa-2022/src/lib.rs index b4ca065dc..e43ec974f 100644 --- a/components/salsa-2022/src/lib.rs +++ b/components/salsa-2022/src/lib.rs @@ -48,3 +48,4 @@ pub use salsa_2022_macros::input; pub use salsa_2022_macros::interned; pub use salsa_2022_macros::jar; pub use salsa_2022_macros::tracked; +pub use salsa_2022_macros::Update; diff --git a/salsa-2022-tests/tests/tracked_with_struct_db.rs b/salsa-2022-tests/tests/tracked_with_struct_db.rs new file mode 100644 index 000000000..38fae9129 --- /dev/null +++ b/salsa-2022-tests/tests/tracked_with_struct_db.rs @@ -0,0 +1,68 @@ +//! Test that a setting a field on a `#[salsa::input]` +//! overwrites and returns the old value. + +use salsa::DebugWithDb; +use test_log::test; + +#[salsa::jar(db = Db)] +struct Jar(MyInput, MyTracked<'_>, create_tracked_list); + +trait Db: salsa::DbWithJar {} + +#[salsa::db(Jar)] +#[derive(Default)] +struct Database { + storage: salsa::Storage, +} + +impl salsa::Database for Database {} + +impl Db for Database {} + +#[salsa::input] +struct MyInput { + field: String, +} + +#[salsa::tracked] +struct MyTracked<'db> { + data: MyInput, + next: MyList<'db>, +} + +#[derive(PartialEq, Eq, Clone, Debug, salsa::Update)] +enum MyList<'db> { + None, + Next(MyTracked<'db>), +} + +#[salsa::tracked] +fn create_tracked_list<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { + let t0 = MyTracked::new(db, input, MyList::None); + let t1 = MyTracked::new(db, input, MyList::Next(t0)); + t1 +} + +#[test] +fn execute() { + let mut db = Database::default(); + let input = MyInput::new(&mut db, "foo".to_string()); + let t0: MyTracked = create_tracked_list(&db, input); + let t1 = create_tracked_list(&db, input); + expect_test::expect![[r#" + MyTracked { + [salsa id]: 1, + data: MyInput { + [salsa id]: 0, + field: "foo", + }, + next: Next( + MyTracked( + 0x00007fc15c011010, + PhantomData<&salsa_2022::tracked_struct::ValueStruct>, + ), + ), + } + "#]].assert_debug_eq(&t0.debug(&db)); + assert_eq!(t0, t1); +} From 4f4d01958f4b5de3f4757d4be7dcefc926609d46 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 21 May 2024 05:47:13 -0400 Subject: [PATCH 42/61] generate a custom `std::fmt::Debug` impl This leads less representation information and in particular avoids serializing pointers. --- components/salsa-2022-macros/src/input.rs | 2 + components/salsa-2022-macros/src/interned.rs | 2 + .../salsa-2022-macros/src/salsa_struct.rs | 25 +- .../salsa-2022-macros/src/tracked_struct.rs | 2 + examples-2022/calc/src/parser.rs | 232 +++++++----------- .../tests/accumulate-reuse-workaround.rs | 10 +- salsa-2022-tests/tests/accumulate-reuse.rs | 8 +- salsa-2022-tests/tests/deletion-cascade.rs | 8 +- salsa-2022-tests/tests/deletion.rs | 8 +- ...truct_changes_but_fn_depends_on_field_y.rs | 6 +- ...input_changes_but_fn_depends_on_field_y.rs | 6 +- salsa-2022-tests/tests/hello_world.rs | 14 +- .../specify_tracked_fn_in_rev_1_but_not_2.rs | 78 +++--- .../tests/tracked_fn_read_own_entity.rs | 14 +- .../tests/tracked_with_struct_db.rs | 7 +- 15 files changed, 195 insertions(+), 227 deletions(-) diff --git a/components/salsa-2022-macros/src/input.rs b/components/salsa-2022-macros/src/input.rs index 9c043daf3..de273d480 100644 --- a/components/salsa-2022-macros/src/input.rs +++ b/components/salsa-2022-macros/src/input.rs @@ -58,6 +58,7 @@ impl InputStruct { let from_id_impl = self.from_id_impl(); let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); + let debug_impl = self.debug_impl(); Ok(quote! { #id_struct @@ -67,6 +68,7 @@ impl InputStruct { #from_id_impl #as_debug_with_db_impl #salsa_struct_in_db_impl + #debug_impl }) } diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 027e1d923..f9b749ce8 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -68,6 +68,7 @@ impl InternedStruct { let named_fields_impl = self.inherent_impl_for_named_fields(); let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); + let debug_impl = self.debug_impl(); let update_impl = self.update_impl(); Ok(crate::debug::dump_tokens( @@ -86,6 +87,7 @@ impl InternedStruct { #salsa_struct_in_db_impl #as_debug_with_db_impl #update_impl + #debug_impl }, )) } diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index cb7a0c0ad..9eec63eaa 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -309,7 +309,7 @@ impl SalsaStruct { parse_quote_spanned! { ident.span() => #(#attrs)* - #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] + #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #visibility struct #ident(salsa::Id); } } @@ -347,7 +347,7 @@ impl SalsaStruct { Ok(parse_quote_spanned! { ident.span() => #(#attrs)* - #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] + #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #visibility struct #ident #generics ( *const salsa::#module::ValueStruct < #config_ident >, std::marker::PhantomData < & #lifetime salsa::#module::ValueStruct < #config_ident > > @@ -465,6 +465,27 @@ impl SalsaStruct { } } + /// Generate `impl salsa::DebugWithDb for Foo`, but only if this is an id struct. + pub(crate) fn debug_impl(&self) -> syn::ItemImpl { + let ident = self.the_ident(); + let (impl_generics, type_generics, where_clause) = + self.struct_item.generics.split_for_impl(); + let ident_string = ident.to_string(); + + // `use ::salsa::debug::helper::Fallback` is needed for the fallback to `Debug` impl + parse_quote_spanned! {ident.span()=> + impl #impl_generics ::std::fmt::Debug for #ident #type_generics + #where_clause + { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + f.debug_struct(#ident_string) + .field("[salsa id]", &self.salsa_id().as_u32()) + .finish() + } + } + } + } + /// Generate `impl salsa::DebugWithDb for Foo`, but only if this is an id struct. pub(crate) fn as_debug_with_db_impl(&self) -> Option { if self.customizations.contains(&Customization::DebugWithDb) { diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index e86ca6c45..c6a491202 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -67,6 +67,7 @@ impl TrackedStruct { let send_sync_impls = self.send_sync_impls(); let from_id_impl = self.from_id_impl(); let lookup_id_impl = self.lookup_id_impl(); + let debug_impl = self.debug_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); Ok(quote! { #config_struct @@ -82,6 +83,7 @@ impl TrackedStruct { #(#send_sync_impls)* #lookup_id_impl #as_debug_with_db_impl + #debug_impl }) } diff --git a/examples-2022/calc/src/parser.rs b/examples-2022/calc/src/parser.rs index 38439c04d..1b4ec00a0 100644 --- a/examples-2022/calc/src/parser.rs +++ b/examples-2022/calc/src/parser.rs @@ -381,25 +381,19 @@ fn parse_print() { [salsa id]: 0, statements: [ Statement { - span: Span( - Id { - value: 5, - }, - ), + span: Span { + [salsa id]: 4, + }, data: Print( Expression { - span: Span( - Id { - value: 4, - }, - ), + span: Span { + [salsa id]: 3, + }, data: Op( Expression { - span: Span( - Id { - value: 1, - }, - ), + span: Span { + [salsa id]: 0, + }, data: Number( OrderedFloat( 1.0, @@ -408,11 +402,9 @@ fn parse_print() { }, Add, Expression { - span: Span( - Id { - value: 3, - }, - ), + span: Span { + [salsa id]: 2, + }, data: Number( OrderedFloat( 2.0, @@ -448,59 +440,43 @@ fn parse_example() { [salsa id]: 0, statements: [ Statement { - span: Span( - Id { - value: 10, - }, - ), + span: Span { + [salsa id]: 9, + }, data: Function( - Function( - Id { - value: 1, - }, - ), + Function { + [salsa id]: 0, + }, ), }, Statement { - span: Span( - Id { - value: 22, - }, - ), + span: Span { + [salsa id]: 21, + }, data: Function( - Function( - Id { - value: 2, - }, - ), + Function { + [salsa id]: 1, + }, ), }, Statement { - span: Span( - Id { - value: 29, - }, - ), + span: Span { + [salsa id]: 28, + }, data: Print( Expression { - span: Span( - Id { - value: 28, - }, - ), + span: Span { + [salsa id]: 27, + }, data: Call( - FunctionId( - Id { - value: 1, - }, - ), + FunctionId { + [salsa id]: 0, + }, [ Expression { - span: Span( - Id { - value: 24, - }, - ), + span: Span { + [salsa id]: 23, + }, data: Number( OrderedFloat( 3.0, @@ -508,11 +484,9 @@ fn parse_example() { ), }, Expression { - span: Span( - Id { - value: 26, - }, - ), + span: Span { + [salsa id]: 25, + }, data: Number( OrderedFloat( 4.0, @@ -525,31 +499,23 @@ fn parse_example() { ), }, Statement { - span: Span( - Id { - value: 34, - }, - ), + span: Span { + [salsa id]: 33, + }, data: Print( Expression { - span: Span( - Id { - value: 33, - }, - ), + span: Span { + [salsa id]: 32, + }, data: Call( - FunctionId( - Id { - value: 2, - }, - ), + FunctionId { + [salsa id]: 1, + }, [ Expression { - span: Span( - Id { - value: 31, - }, - ), + span: Span { + [salsa id]: 30, + }, data: Number( OrderedFloat( 1.0, @@ -562,25 +528,19 @@ fn parse_example() { ), }, Statement { - span: Span( - Id { - value: 39, - }, - ), + span: Span { + [salsa id]: 38, + }, data: Print( Expression { - span: Span( - Id { - value: 38, - }, - ), + span: Span { + [salsa id]: 37, + }, data: Op( Expression { - span: Span( - Id { - value: 35, - }, - ), + span: Span { + [salsa id]: 34, + }, data: Number( OrderedFloat( 11.0, @@ -589,11 +549,9 @@ fn parse_example() { }, Multiply, Expression { - span: Span( - Id { - value: 37, - }, - ), + span: Span { + [salsa id]: 36, + }, data: Number( OrderedFloat( 2.0, @@ -644,32 +602,24 @@ fn parse_precedence() { [salsa id]: 0, statements: [ Statement { - span: Span( - Id { - value: 11, - }, - ), + span: Span { + [salsa id]: 10, + }, data: Print( Expression { - span: Span( - Id { - value: 10, - }, - ), + span: Span { + [salsa id]: 9, + }, data: Op( Expression { - span: Span( - Id { - value: 7, - }, - ), + span: Span { + [salsa id]: 6, + }, data: Op( Expression { - span: Span( - Id { - value: 1, - }, - ), + span: Span { + [salsa id]: 0, + }, data: Number( OrderedFloat( 1.0, @@ -678,18 +628,14 @@ fn parse_precedence() { }, Add, Expression { - span: Span( - Id { - value: 6, - }, - ), + span: Span { + [salsa id]: 5, + }, data: Op( Expression { - span: Span( - Id { - value: 3, - }, - ), + span: Span { + [salsa id]: 2, + }, data: Number( OrderedFloat( 2.0, @@ -698,11 +644,9 @@ fn parse_precedence() { }, Multiply, Expression { - span: Span( - Id { - value: 5, - }, - ), + span: Span { + [salsa id]: 4, + }, data: Number( OrderedFloat( 3.0, @@ -715,11 +659,9 @@ fn parse_precedence() { }, Add, Expression { - span: Span( - Id { - value: 9, - }, - ), + span: Span { + [salsa id]: 8, + }, data: Number( OrderedFloat( 4.0, diff --git a/salsa-2022-tests/tests/accumulate-reuse-workaround.rs b/salsa-2022-tests/tests/accumulate-reuse-workaround.rs index e960db1bb..4a81e85eb 100644 --- a/salsa-2022-tests/tests/accumulate-reuse-workaround.rs +++ b/salsa-2022-tests/tests/accumulate-reuse-workaround.rs @@ -75,9 +75,9 @@ fn test1() { assert_eq!(compute(&db, l2), 2); db.assert_logs(expect![[r#" [ - "compute(List(Id { value: 2 }))", - "accumulated(List(Id { value: 1 }))", - "compute(List(Id { value: 1 }))", + "compute(List { [salsa id]: 1 })", + "accumulated(List { [salsa id]: 0 })", + "compute(List { [salsa id]: 0 })", ]"#]]); // When we mutate `l1`, we should re-execute `compute` for `l1`, @@ -87,7 +87,7 @@ fn test1() { assert_eq!(compute(&db, l2), 2); db.assert_logs(expect![[r#" [ - "accumulated(List(Id { value: 1 }))", - "compute(List(Id { value: 1 }))", + "accumulated(List { [salsa id]: 0 })", + "compute(List { [salsa id]: 0 })", ]"#]]); } diff --git a/salsa-2022-tests/tests/accumulate-reuse.rs b/salsa-2022-tests/tests/accumulate-reuse.rs index 9790d6ba0..ca164c57a 100644 --- a/salsa-2022-tests/tests/accumulate-reuse.rs +++ b/salsa-2022-tests/tests/accumulate-reuse.rs @@ -70,8 +70,8 @@ fn test1() { assert_eq!(compute(&db, l2), 2); db.assert_logs(expect![[r#" [ - "compute(List(Id { value: 2 }))", - "compute(List(Id { value: 1 }))", + "compute(List { [salsa id]: 1 })", + "compute(List { [salsa id]: 0 })", ]"#]]); // When we mutate `l1`, we should re-execute `compute` for `l1`, @@ -82,7 +82,7 @@ fn test1() { assert_eq!(compute(&db, l2), 2); db.assert_logs(expect![[r#" [ - "compute(List(Id { value: 2 }))", - "compute(List(Id { value: 1 }))", + "compute(List { [salsa id]: 1 })", + "compute(List { [salsa id]: 0 })", ]"#]]); } diff --git a/salsa-2022-tests/tests/deletion-cascade.rs b/salsa-2022-tests/tests/deletion-cascade.rs index 036e4adcb..932336205 100644 --- a/salsa-2022-tests/tests/deletion-cascade.rs +++ b/salsa-2022-tests/tests/deletion-cascade.rs @@ -95,8 +95,8 @@ fn basic() { assert_eq!(final_result(&db, input), 2 * 2 + 2); db.assert_logs(expect![[r#" [ - "final_result(MyInput(Id { value: 1 }))", - "intermediate_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", + "intermediate_result(MyInput { [salsa id]: 0 })", ]"#]]); // Creates only 2 tracked structs in this revision, should delete 1 @@ -117,12 +117,12 @@ fn basic() { assert_eq!(final_result(&db, input), 2); db.assert_logs(expect![[r#" [ - "intermediate_result(MyInput(Id { value: 1 }))", + "intermediate_result(MyInput { [salsa id]: 0 })", "salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: MyTracked(2) })", "salsa_event(DidDiscard { key: MyTracked(2) })", "salsa_event(DidDiscard { key: contribution_from_struct(2) })", "salsa_event(DidDiscard { key: MyTracked(5) })", "salsa_event(DidDiscard { key: copy_field(5) })", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", ]"#]]); } diff --git a/salsa-2022-tests/tests/deletion.rs b/salsa-2022-tests/tests/deletion.rs index 5d981ff53..89cfd412c 100644 --- a/salsa-2022-tests/tests/deletion.rs +++ b/salsa-2022-tests/tests/deletion.rs @@ -88,8 +88,8 @@ fn basic() { assert_eq!(final_result(&db, input), 2 * 2 + 2); db.assert_logs(expect![[r#" [ - "final_result(MyInput(Id { value: 1 }))", - "intermediate_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", + "intermediate_result(MyInput { [salsa id]: 0 })", ]"#]]); // Creates only 2 tracked structs in this revision, should delete 1 @@ -103,10 +103,10 @@ fn basic() { assert_eq!(final_result(&db, input), 2); db.assert_logs(expect![[r#" [ - "intermediate_result(MyInput(Id { value: 1 }))", + "intermediate_result(MyInput { [salsa id]: 0 })", "salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(0), output_key: MyTracked(2) })", "salsa_event(DidDiscard { key: MyTracked(2) })", "salsa_event(DidDiscard { key: contribution_from_struct(2) })", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", ]"#]]); } diff --git a/salsa-2022-tests/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs b/salsa-2022-tests/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs index 822945c0b..f5058b97e 100644 --- a/salsa-2022-tests/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs +++ b/salsa-2022-tests/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs @@ -78,13 +78,13 @@ fn execute() { assert_eq!(final_result_depends_on_x(&db, input), 22); db.assert_logs(expect![[r#" [ - "final_result_depends_on_x(MyInput(Id { value: 1 }))", + "final_result_depends_on_x(MyInput { [salsa id]: 0 })", ]"#]]); assert_eq!(final_result_depends_on_y(&db, input), 22); db.assert_logs(expect![[r#" [ - "final_result_depends_on_y(MyInput(Id { value: 1 }))", + "final_result_depends_on_y(MyInput { [salsa id]: 0 })", ]"#]]); input.set_field(&mut db).to(23); @@ -94,7 +94,7 @@ fn execute() { assert_eq!(final_result_depends_on_x(&db, input), 24); db.assert_logs(expect![[r#" [ - "final_result_depends_on_x(MyInput(Id { value: 1 }))", + "final_result_depends_on_x(MyInput { [salsa id]: 0 })", ]"#]]); // y = 23 / 2 = 11 diff --git a/salsa-2022-tests/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs b/salsa-2022-tests/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs index 0cd9da7b7..0096210bd 100644 --- a/salsa-2022-tests/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs +++ b/salsa-2022-tests/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs @@ -57,13 +57,13 @@ fn execute() { assert_eq!(result_depends_on_x(&db, input), 23); db.assert_logs(expect![[r#" [ - "result_depends_on_x(MyInput(Id { value: 1 }))", + "result_depends_on_x(MyInput { [salsa id]: 0 })", ]"#]]); assert_eq!(result_depends_on_y(&db, input), 32); db.assert_logs(expect![[r#" [ - "result_depends_on_y(MyInput(Id { value: 1 }))", + "result_depends_on_y(MyInput { [salsa id]: 0 })", ]"#]]); input.set_x(&mut db).to(23); @@ -71,7 +71,7 @@ fn execute() { assert_eq!(result_depends_on_x(&db, input), 24); db.assert_logs(expect![[r#" [ - "result_depends_on_x(MyInput(Id { value: 1 }))", + "result_depends_on_x(MyInput { [salsa id]: 0 })", ]"#]]); // input y is the same, so result depends on y diff --git a/salsa-2022-tests/tests/hello_world.rs b/salsa-2022-tests/tests/hello_world.rs index b3471b5bc..d47263064 100644 --- a/salsa-2022-tests/tests/hello_world.rs +++ b/salsa-2022-tests/tests/hello_world.rs @@ -58,8 +58,8 @@ fn execute() { assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ - "final_result(MyInput(Id { value: 1 }))", - "intermediate_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", + "intermediate_result(MyInput { [salsa id]: 0 })", ]"#]]); // Intermediate result is the same, so final result does @@ -68,15 +68,15 @@ fn execute() { assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ - "intermediate_result(MyInput(Id { value: 1 }))", + "intermediate_result(MyInput { [salsa id]: 0 })", ]"#]]); input.set_field(&mut db).to(24); assert_eq!(final_result(&db, input), 24); db.assert_logs(expect![[r#" [ - "intermediate_result(MyInput(Id { value: 1 }))", - "final_result(MyInput(Id { value: 1 }))", + "intermediate_result(MyInput { [salsa id]: 0 })", + "final_result(MyInput { [salsa id]: 0 })", ]"#]]); } @@ -89,8 +89,8 @@ fn red_herring() { assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ - "final_result(MyInput(Id { value: 1 }))", - "intermediate_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", + "intermediate_result(MyInput { [salsa id]: 0 })", ]"#]]); // Create a distinct input and mutate it. diff --git a/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs b/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs index 00ddef1a2..f6e33b9e3 100644 --- a/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs +++ b/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs @@ -95,13 +95,13 @@ fn test_run_0() { [ "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: final_result(0) } }", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "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 }))", + "read_maybe_specified(MyTracked { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", ]"#]]); } @@ -116,13 +116,13 @@ fn test_run_5() { [ "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: final_result(0) } }", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "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 }))", + "read_maybe_specified(MyTracked { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", ]"#]]); } @@ -137,16 +137,16 @@ fn test_run_10() { [ "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: final_result(0) } }", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "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 }))", + "read_maybe_specified(MyTracked { [salsa id]: 0 })", "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 }))", + "maybe_specified(MyTracked { [salsa id]: 0 })", ]"#]]); } @@ -160,16 +160,16 @@ fn test_run_20() { [ "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: final_result(0) } }", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "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 }))", + "read_maybe_specified(MyTracked { [salsa id]: 0 })", "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 }))", + "maybe_specified(MyTracked { [salsa id]: 0 })", ]"#]]); } @@ -187,13 +187,13 @@ 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: final_result(0) } }", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "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 }))", + "read_maybe_specified(MyTracked { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", ]"#]]); @@ -208,7 +208,7 @@ 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: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "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: read_maybe_specified(0) } }", @@ -227,17 +227,17 @@ 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: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }", "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 }))", + "maybe_specified(MyTracked { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }", - "read_maybe_specified(MyTracked(Id { value: 1 }))", + "read_maybe_specified(MyTracked { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: final_result(0) } }", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", ]"#]]); @@ -257,13 +257,13 @@ 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: final_result(0) } }", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "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 }))", + "read_maybe_specified(MyTracked { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", ]"#]]); @@ -278,7 +278,7 @@ 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: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "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: read_maybe_specified(0) } }", @@ -297,12 +297,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: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }", "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 }))", + "maybe_specified(MyTracked { [salsa id]: 0 })", "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) } }", ]"#]]); @@ -317,16 +317,16 @@ 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: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "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 }))", + "maybe_specified(MyTracked { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }", - "read_maybe_specified(MyTracked(Id { value: 1 }))", + "read_maybe_specified(MyTracked { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: final_result(0) } }", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", ]"#]]); @@ -342,13 +342,13 @@ fn test_run_5_then_20() { [ "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: final_result(0) } }", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "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 }))", + "read_maybe_specified(MyTracked { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", ]"#]]); @@ -359,17 +359,17 @@ 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: WillExecute { database_key: create_tracked(0) } }", - "create_tracked(MyInput(Id { value: 1 }))", + "create_tracked(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillDiscardStaleOutput { execute_key: create_tracked(0), output_key: maybe_specified(0) } }", "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 }))", + "maybe_specified(MyTracked { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: read_maybe_specified(0) } }", - "read_maybe_specified(MyTracked(Id { value: 1 }))", + "read_maybe_specified(MyTracked { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: final_result(0) } }", - "final_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillCheckCancellation }", ]"#]]); diff --git a/salsa-2022-tests/tests/tracked_fn_read_own_entity.rs b/salsa-2022-tests/tests/tracked_fn_read_own_entity.rs index a79860f8c..d4621586b 100644 --- a/salsa-2022-tests/tests/tracked_fn_read_own_entity.rs +++ b/salsa-2022-tests/tests/tracked_fn_read_own_entity.rs @@ -59,8 +59,8 @@ fn one_entity() { assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ - "final_result(MyInput(Id { value: 1 }))", - "intermediate_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", + "intermediate_result(MyInput { [salsa id]: 0 })", ]"#]]); // Intermediate result is the same, so final result does @@ -69,15 +69,15 @@ fn one_entity() { assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ - "intermediate_result(MyInput(Id { value: 1 }))", + "intermediate_result(MyInput { [salsa id]: 0 })", ]"#]]); input.set_field(&mut db).to(24); assert_eq!(final_result(&db, input), 24); db.assert_logs(expect![[r#" [ - "intermediate_result(MyInput(Id { value: 1 }))", - "final_result(MyInput(Id { value: 1 }))", + "intermediate_result(MyInput { [salsa id]: 0 })", + "final_result(MyInput { [salsa id]: 0 })", ]"#]]); } @@ -90,8 +90,8 @@ fn red_herring() { assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ - "final_result(MyInput(Id { value: 1 }))", - "intermediate_result(MyInput(Id { value: 1 }))", + "final_result(MyInput { [salsa id]: 0 })", + "intermediate_result(MyInput { [salsa id]: 0 })", ]"#]]); // Create a distinct input and mutate it. diff --git a/salsa-2022-tests/tests/tracked_with_struct_db.rs b/salsa-2022-tests/tests/tracked_with_struct_db.rs index 38fae9129..e3890948b 100644 --- a/salsa-2022-tests/tests/tracked_with_struct_db.rs +++ b/salsa-2022-tests/tests/tracked_with_struct_db.rs @@ -57,10 +57,9 @@ fn execute() { field: "foo", }, next: Next( - MyTracked( - 0x00007fc15c011010, - PhantomData<&salsa_2022::tracked_struct::ValueStruct>, - ), + MyTracked { + [salsa id]: 0, + }, ), } "#]].assert_debug_eq(&t0.debug(&db)); From b0058204949e075a54acf7ac350a5af1446ee403 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 21 May 2024 05:57:04 -0400 Subject: [PATCH 43/61] add a derive for `DebugWithDb` --- .../salsa-2022-macros/src/debug_with_db.rs | 94 +++++++++++++++++++ components/salsa-2022-macros/src/lib.rs | 10 ++ components/salsa-2022/src/lib.rs | 1 + .../tests/tracked_with_struct_db.rs | 10 +- 4 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 components/salsa-2022-macros/src/debug_with_db.rs diff --git a/components/salsa-2022-macros/src/debug_with_db.rs b/components/salsa-2022-macros/src/debug_with_db.rs new file mode 100644 index 000000000..167e95406 --- /dev/null +++ b/components/salsa-2022-macros/src/debug_with_db.rs @@ -0,0 +1,94 @@ +use proc_macro2::{Literal, Span, TokenStream}; + +pub(crate) fn debug_with_db(input: syn::DeriveInput) -> syn::Result { + // Figure out the lifetime to use for the `dyn Db` that we will expect. + // We allow structs to have at most one lifetime -- if a lifetime parameter is present, + // it should be `'db`. We may want to generalize this later. + + let num_lifetimes = input.generics.lifetimes().count(); + if num_lifetimes > 1 { + return syn::Result::Err(syn::Error::new( + input.generics.lifetimes().nth(1).unwrap().lifetime.span(), + "only one lifetime is supported", + )); + } + + let db_lt = match input.generics.lifetimes().next() { + Some(lt) => lt.lifetime.clone(), + None => syn::Lifetime::new("'_", Span::call_site()), + }; + + // Generate the type of database we expect. This hardcodes the convention of using `jar::Jar`. + // That's not great and should be fixed but we'd have to add a custom attribute and I am too lazy. + + #[allow(non_snake_case)] + let DB: syn::Type = parse_quote! { + >::DynDb + }; + + let structure: synstructure::Structure = synstructure::Structure::new(&input); + + let fmt = syn::Ident::new("fmt", Span::call_site()); + let db = syn::Ident::new("db", Span::call_site()); + + // Generic the match arm for each variant. + let fields: TokenStream = structure + .variants() + .iter() + .map(|variant| { + let variant_name = &variant.ast().ident; + let variant_name = Literal::string(&variant_name.to_string()); + + // Closure: given a binding, generate a call to the `salsa_debug` helper to either + // print its "debug with db" value or just use `std::fmt::Debug`. This is a nice hack that + // lets us use `debug_with_db` when available; it won't work great for generic types unless we add + // `DebugWithDb` bounds though. + let binding_tokens = |binding: &synstructure::BindingInfo| { + let field_ty = &binding.ast().ty; + quote!( + &::salsa::debug::helper::SalsaDebug::<#field_ty, #DB>::salsa_debug( + #binding, + #db, + ) + ) + }; + + // Create something like `fmt.debug_struct(...).field().field().finish()` + // for each struct field; the values to be debugged are created by + // the `binding_tokens` closure above. + let fields = match variant.ast().fields { + syn::Fields::Named(_) => variant.fold( + quote!(#fmt.debug_struct(#variant_name)), + |tokens, binding| { + let binding_name = + Literal::string(&binding.ast().ident.as_ref().unwrap().to_string()); + let binding_data = binding_tokens(&binding); + quote!(#tokens . field(#binding_name, #binding_data)) + }, + ), + + syn::Fields::Unnamed(_) | syn::Fields::Unit => variant.fold( + quote!(#fmt.debug_tuple(#variant_name)), + |tokens, binding| { + let binding_data = binding_tokens(&binding); + quote!(#tokens . field(#binding_data)) + }, + ), + }; + + quote!(#fields . finish(),) + }) + .collect(); + + let tokens = structure.gen_impl(quote! { + gen impl ::salsa::debug::DebugWithDb<#DB> for @Self { + fn fmt(&self, #fmt: &mut std::fmt::Formatter<'_>, #db: & #DB) -> std::fmt::Result { + match self { + #fields + } + } + } + }); + + Ok(crate::debug::dump_tokens(&input.ident, tokens)) +} diff --git a/components/salsa-2022-macros/src/lib.rs b/components/salsa-2022-macros/src/lib.rs index 52b429f02..3cd83f824 100644 --- a/components/salsa-2022-macros/src/lib.rs +++ b/components/salsa-2022-macros/src/lib.rs @@ -41,6 +41,7 @@ mod configuration; mod db; mod db_lifetime; mod debug; +mod debug_with_db; mod input; mod interned; mod jar; @@ -90,3 +91,12 @@ pub fn update(input: TokenStream) -> TokenStream { Err(err) => err.to_compile_error().into(), } } + +#[proc_macro_derive(DebugWithDb)] +pub fn debug(input: TokenStream) -> TokenStream { + let item = syn::parse_macro_input!(input as syn::DeriveInput); + match debug_with_db::debug_with_db(item) { + Ok(tokens) => tokens.into(), + Err(err) => err.to_compile_error().into(), + } +} diff --git a/components/salsa-2022/src/lib.rs b/components/salsa-2022/src/lib.rs index e43ec974f..c2c811d6d 100644 --- a/components/salsa-2022/src/lib.rs +++ b/components/salsa-2022/src/lib.rs @@ -48,4 +48,5 @@ pub use salsa_2022_macros::input; pub use salsa_2022_macros::interned; pub use salsa_2022_macros::jar; pub use salsa_2022_macros::tracked; +pub use salsa_2022_macros::DebugWithDb; pub use salsa_2022_macros::Update; diff --git a/salsa-2022-tests/tests/tracked_with_struct_db.rs b/salsa-2022-tests/tests/tracked_with_struct_db.rs index e3890948b..737aef256 100644 --- a/salsa-2022-tests/tests/tracked_with_struct_db.rs +++ b/salsa-2022-tests/tests/tracked_with_struct_db.rs @@ -30,7 +30,7 @@ struct MyTracked<'db> { next: MyList<'db>, } -#[derive(PartialEq, Eq, Clone, Debug, salsa::Update)] +#[derive(PartialEq, Eq, Clone, Debug, salsa::Update, salsa::DebugWithDb)] enum MyList<'db> { None, Next(MyTracked<'db>), @@ -59,9 +59,15 @@ fn execute() { next: Next( MyTracked { [salsa id]: 0, + data: MyInput { + [salsa id]: 0, + field: "foo", + }, + next: None, }, ), } - "#]].assert_debug_eq(&t0.debug(&db)); + "#]] + .assert_debug_eq(&t0.debug(&db)); assert_eq!(t0, t1); } From 1560634f96d637483e7729ddd51787ea69d542bb Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 23 May 2024 20:52:06 -0400 Subject: [PATCH 44/61] support methods with 'db lifetimes --- .../salsa-2022-macros/src/tracked_fn.rs | 20 +++++++++++++++---- .../tests/warnings/needless_lifetimes.rs | 10 +++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index 8236cd590..48bc980f8 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -142,6 +142,7 @@ pub(crate) fn tracked_impl( }; let name = format!("{}_{}", name_prefix, item_method.sig.ident); Some(tracked_method( + &item_impl.generics, &args, inner_args, item_method, @@ -160,11 +161,14 @@ pub(crate) fn tracked_impl( acc })?; - Ok(quote! { - #item_impl + Ok(crate::debug::dump_tokens( + self_type_name, + quote! { + #item_impl - #(#extra_impls)* - }) + #(#extra_impls)* + }, + )) } struct TrackedImpl; @@ -192,6 +196,7 @@ impl crate::options::AllowedOptions for TrackedImpl { } fn tracked_method( + impl_generics: &syn::Generics, outer_args: &ImplArgs, mut args: FnArgs, item_method: &mut syn::ImplItemFn, @@ -214,6 +219,12 @@ fn tracked_method( block: Box::new(rename_self_in_block(item_method.block.clone())?), }; item_fn.sig.ident = syn::Ident::new(name, item_fn.sig.ident.span()); + + // Insert the generics from impl at the start of the fn generics + for parameter in impl_generics.params.iter().rev() { + item_fn.sig.generics.params.insert(0, parameter.clone()); + } + // Flip the first and second arguments as the rest of the code expects the // database to come first and the struct to come second. We also need to // change the self argument to a normal typed argument called __salsa_self. @@ -222,6 +233,7 @@ fn tracked_method( syn::FnArg::Receiver(r) if r.reference.is_none() => r, arg => return Err(syn::Error::new(arg.span(), "first argument must be self")), }; + let db_param = original_inputs.next().unwrap().into_value(); let mut inputs = syn::punctuated::Punctuated::new(); inputs.push(db_param); diff --git a/salsa-2022-tests/tests/warnings/needless_lifetimes.rs b/salsa-2022-tests/tests/warnings/needless_lifetimes.rs index 19b322cf1..3612d39b8 100644 --- a/salsa-2022-tests/tests/warnings/needless_lifetimes.rs +++ b/salsa-2022-tests/tests/warnings/needless_lifetimes.rs @@ -1,24 +1,24 @@ pub trait Db: salsa::DbWithJar {} #[salsa::jar(db = Db)] -pub struct Jar(SourceTree, SourceTree_all_items, use_tree); +pub struct Jar(SourceTree<'_>, SourceTree_all_items, use_tree); #[derive(Debug, PartialEq, Eq, Hash)] pub struct Item {} #[salsa::tracked(jar = Jar)] -pub struct SourceTree {} +pub struct SourceTree<'db> {} #[salsa::tracked(jar = Jar)] -impl SourceTree { +impl<'db> SourceTree<'db> { #[salsa::tracked(return_ref)] - pub fn all_items(self, _db: &dyn Db) -> Vec { + pub fn all_items(self, _db: &'db dyn Db) -> Vec { todo!() } } #[salsa::tracked(jar = Jar, return_ref)] -fn use_tree(_db: &dyn Db, _tree: SourceTree) {} +fn use_tree<'db>(_db: &'db dyn Db, _tree: SourceTree<'db>) {} #[allow(unused)] fn use_it(db: &dyn Db, tree: SourceTree) { From 68502ab27f7a2b8d8be829f569874c54079f8a55 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 23 May 2024 21:16:30 -0400 Subject: [PATCH 45/61] 'db all the things --- .../salsa-2022-macros/src/db_lifetime.rs | 16 +- .../salsa-2022-macros/src/debug_with_db.rs | 1 + .../salsa-2022-macros/src/salsa_struct.rs | 2 +- .../salsa-2022-macros/src/tracked_fn.rs | 6 +- components/salsa-2022-macros/src/update.rs | 1 + components/salsa-2022/src/update.rs | 11 + examples-2022/calc/src/ir.rs | 56 ++--- examples-2022/calc/src/main.rs | 10 +- examples-2022/calc/src/parser.rs | 212 ++++++++++++++++-- examples-2022/calc/src/type_check.rs | 28 ++- examples-2022/lazy-input/src/main.rs | 10 +- ...of-tracked-structs-from-older-revisions.rs | 6 +- ...racked-structs-from-older-revisions.stderr | 5 + .../tests/compile-fail/span-tracked-getter.rs | 4 +- .../compile-fail/span-tracked-getter.stderr | 2 +- ...es-not-work-if-the-key-is-a-salsa-input.rs | 6 +- ...ot-work-if-the-key-is-a-salsa-input.stderr | 2 +- ...not-work-if-the-key-is-a-salsa-interned.rs | 9 +- ...work-if-the-key-is-a-salsa-interned.stderr | 10 +- .../tracked_impl_incompatibles.rs | 62 ++--- .../tracked_impl_incompatibles.stderr | 42 ++-- .../tracked_method_incompatibles.rs | 17 +- .../tracked_method_incompatibles.stderr | 4 +- .../tests/create-large-jar-database.rs | 4 +- salsa-2022-tests/tests/debug.rs | 8 +- salsa-2022-tests/tests/deletion.rs | 8 +- ...truct_changes_but_fn_depends_on_field_y.rs | 6 +- .../tests/override_new_get_set.rs | 18 +- ...ng-tracked-struct-outside-of-tracked-fn.rs | 4 +- .../preverify-struct-with-leaked-data.rs | 4 +- ...the-key-is-created-in-the-current-query.rs | 10 +- .../specify_tracked_fn_in_rev_1_but_not_2.rs | 10 +- .../tests/tracked-struct-id-field-bad-eq.rs | 4 +- .../tests/tracked-struct-id-field-bad-hash.rs | 4 +- .../tracked-struct-unchanged-in-new-rev.rs | 6 +- .../tracked-struct-value-field-bad-eq.rs | 8 +- .../tracked-struct-value-field-not-eq.rs | 4 +- .../tests/tracked_fn_on_tracked.rs | 6 +- .../tests/tracked_fn_on_tracked_specify.rs | 8 +- .../tests/tracked_fn_read_own_entity.rs | 14 +- .../tests/tracked_fn_read_own_specify.rs | 8 +- .../tests/warnings/needless_borrow.rs | 4 +- .../tests/warnings/unused_variable_db.rs | 4 +- 43 files changed, 429 insertions(+), 235 deletions(-) rename salsa-2022-tests/tests/{ => compile-fail}/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs (85%) create mode 100644 salsa-2022-tests/tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.stderr diff --git a/components/salsa-2022-macros/src/db_lifetime.rs b/components/salsa-2022-macros/src/db_lifetime.rs index 7e3d94942..d6bafaa08 100644 --- a/components/salsa-2022-macros/src/db_lifetime.rs +++ b/components/salsa-2022-macros/src/db_lifetime.rs @@ -15,11 +15,25 @@ pub(crate) fn default_db_lifetime(span: Span) -> syn::Lifetime { } /// Require that either there are no generics or exactly one lifetime parameter. -pub(crate) fn require_db_lifetime(generics: &syn::Generics) -> syn::Result<()> { +pub(crate) fn require_optional_db_lifetime(generics: &syn::Generics) -> syn::Result<()> { if generics.params.len() == 0 { return Ok(()); } + require_db_lifetime(generics)?; + + Ok(()) +} + +/// Require that either there is exactly one lifetime parameter. +pub(crate) fn require_db_lifetime(generics: &syn::Generics) -> syn::Result<()> { + if generics.params.len() == 0 { + return Err(syn::Error::new_spanned( + generics, + "this definition must have a `'db` lifetime", + )); + } + for (param, index) in generics.params.iter().zip(0..) { let error = match param { syn::GenericParam::Lifetime(_) => index > 0, diff --git a/components/salsa-2022-macros/src/debug_with_db.rs b/components/salsa-2022-macros/src/debug_with_db.rs index 167e95406..17c5c535f 100644 --- a/components/salsa-2022-macros/src/debug_with_db.rs +++ b/components/salsa-2022-macros/src/debug_with_db.rs @@ -83,6 +83,7 @@ pub(crate) fn debug_with_db(input: syn::DeriveInput) -> syn::Result for @Self { fn fmt(&self, #fmt: &mut std::fmt::Formatter<'_>, #db: & #DB) -> std::fmt::Result { + use ::salsa::debug::helper::Fallback as _; match self { #fields } diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index 9eec63eaa..4baccb3ed 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -467,7 +467,7 @@ impl SalsaStruct { /// Generate `impl salsa::DebugWithDb for Foo`, but only if this is an id struct. pub(crate) fn debug_impl(&self) -> syn::ItemImpl { - let ident = self.the_ident(); + let ident: &Ident = self.the_ident(); let (impl_generics, type_generics, where_clause) = self.struct_item.generics.split_for_impl(); let ident_string = ident.to_string(); diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index 48bc980f8..c1733b7f8 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -4,14 +4,14 @@ use syn::visit_mut::VisitMut; use syn::{ReturnType, Token}; use crate::configuration::{self, Configuration, CycleRecoveryStrategy}; -use crate::db_lifetime::{self, db_lifetime, require_db_lifetime}; +use crate::db_lifetime::{self, db_lifetime, require_optional_db_lifetime}; use crate::options::Options; pub(crate) fn tracked_fn( args: proc_macro::TokenStream, mut item_fn: syn::ItemFn, ) -> syn::Result { - db_lifetime::require_db_lifetime(&item_fn.sig.generics)?; + db_lifetime::require_optional_db_lifetime(&item_fn.sig.generics)?; let fn_ident = item_fn.sig.ident.clone(); @@ -303,7 +303,7 @@ fn rename_self_in_block(mut block: syn::Block) -> syn::Result { /// /// This returns the name of the constructed type and the code defining everything. fn fn_struct(args: &FnArgs, item_fn: &syn::ItemFn) -> syn::Result<(syn::Type, TokenStream)> { - require_db_lifetime(&item_fn.sig.generics)?; + require_optional_db_lifetime(&item_fn.sig.generics)?; let db_lt = &db_lifetime(&item_fn.sig.generics); let struct_item = configuration_struct(item_fn); let configuration = fn_configuration(args, item_fn); diff --git a/components/salsa-2022-macros/src/update.rs b/components/salsa-2022-macros/src/update.rs index 56ae868a7..cd2e7194a 100644 --- a/components/salsa-2022-macros/src/update.rs +++ b/components/salsa-2022-macros/src/update.rs @@ -74,6 +74,7 @@ pub(crate) fn update_derive(input: syn::DeriveInput) -> syn::Result let tokens = quote! { unsafe impl #impl_generics salsa::update::Update for #ident #ty_generics #where_clause { unsafe fn maybe_update(#old_pointer: *mut Self, #new_value: Self) -> bool { + use ::salsa::update::helper::Fallback as _; let old_pointer = unsafe { &mut *#old_pointer }; match #old_pointer { #fields diff --git a/components/salsa-2022/src/update.rs b/components/salsa-2022/src/update.rs index 9e6868a8f..5a013a86e 100644 --- a/components/salsa-2022/src/update.rs +++ b/components/salsa-2022/src/update.rs @@ -146,6 +146,17 @@ where } } +unsafe impl Update for Box +where + T: Update, +{ + unsafe fn maybe_update(old_pointer: *mut Self, new_box: Self) -> bool { + let old_box: &mut Box = unsafe { &mut *old_pointer }; + + T::maybe_update(&mut **old_box, *new_box) + } +} + unsafe impl Update for [T; N] where T: Update, diff --git a/examples-2022/calc/src/ir.rs b/examples-2022/calc/src/ir.rs index 3c2a3aea7..44ad6651b 100644 --- a/examples-2022/calc/src/ir.rs +++ b/examples-2022/calc/src/ir.rs @@ -13,13 +13,13 @@ pub struct SourceProgram { // ANCHOR: interned_ids #[salsa::interned] -pub struct VariableId { +pub struct VariableId<'db> { #[return_ref] pub text: String, } #[salsa::interned] -pub struct FunctionId { +pub struct FunctionId<'db> { #[return_ref] pub text: String, } @@ -27,44 +27,44 @@ pub struct FunctionId { // ANCHOR: program #[salsa::tracked] -pub struct Program { +pub struct Program<'db> { #[return_ref] - pub statements: Vec, + pub statements: Vec>, } // ANCHOR_END: program // ANCHOR: statements_and_expressions -#[derive(Eq, PartialEq, Debug, Hash, new)] -pub struct Statement { - pub span: Span, +#[derive(Eq, PartialEq, Debug, Hash, new, salsa::Update, salsa::DebugWithDb)] +pub struct Statement<'db> { + pub span: Span<'db>, - pub data: StatementData, + pub data: StatementData<'db>, } -#[derive(Eq, PartialEq, Debug, Hash)] -pub enum StatementData { +#[derive(Eq, PartialEq, Debug, Hash, salsa::Update, salsa::DebugWithDb)] +pub enum StatementData<'db> { /// Defines `fn () = ` - Function(Function), + Function(Function<'db>), /// Defines `print ` - Print(Expression), + Print(Expression<'db>), } -#[derive(Eq, PartialEq, Debug, Hash, new)] -pub struct Expression { - pub span: Span, +#[derive(Eq, PartialEq, Debug, Hash, new, salsa::Update, salsa::DebugWithDb)] +pub struct Expression<'db> { + pub span: Span<'db>, - pub data: ExpressionData, + pub data: ExpressionData<'db>, } -#[derive(Eq, PartialEq, Debug, Hash)] -pub enum ExpressionData { - Op(Box, Op, Box), +#[derive(Eq, PartialEq, Debug, Hash, salsa::Update, salsa::DebugWithDb)] +pub enum ExpressionData<'db> { + Op(Box>, Op, Box>), Number(OrderedFloat), - Variable(VariableId), - Call(FunctionId, Vec), + Variable(VariableId<'db>), + Call(FunctionId<'db>, Vec>), } -#[derive(Eq, PartialEq, Copy, Clone, Hash, Debug)] +#[derive(Eq, PartialEq, Copy, Clone, Hash, Debug, salsa::Update, salsa::DebugWithDb)] pub enum Op { Add, Subtract, @@ -75,22 +75,22 @@ pub enum Op { // ANCHOR: functions #[salsa::tracked] -pub struct Function { +pub struct Function<'db> { #[id] - pub name: FunctionId, + pub name: FunctionId<'db>, - name_span: Span, + name_span: Span<'db>, #[return_ref] - pub args: Vec, + pub args: Vec>, #[return_ref] - pub body: Expression, + pub body: Expression<'db>, } // ANCHOR_END: functions #[salsa::tracked] -pub struct Span { +pub struct Span<'db> { pub start: usize, pub end: usize, } diff --git a/examples-2022/calc/src/main.rs b/examples-2022/calc/src/main.rs index 23d4443a1..75eeab5ce 100644 --- a/examples-2022/calc/src/main.rs +++ b/examples-2022/calc/src/main.rs @@ -5,12 +5,12 @@ use ir::{Diagnostics, SourceProgram}; pub struct Jar( crate::compile::compile, crate::ir::SourceProgram, - crate::ir::Program, - crate::ir::VariableId, - crate::ir::FunctionId, - crate::ir::Function, + crate::ir::Program<'_>, + crate::ir::VariableId<'_>, + crate::ir::FunctionId<'_>, + crate::ir::Function<'_>, crate::ir::Diagnostics, - crate::ir::Span, + crate::ir::Span<'_>, crate::parser::parse_statements, crate::type_check::type_check_program, crate::type_check::type_check_function, diff --git a/examples-2022/calc/src/parser.rs b/examples-2022/calc/src/parser.rs index 1b4ec00a0..5bc181e4d 100644 --- a/examples-2022/calc/src/parser.rs +++ b/examples-2022/calc/src/parser.rs @@ -7,7 +7,7 @@ use crate::ir::{ // ANCHOR: parse_statements #[salsa::tracked] -pub fn parse_statements(db: &dyn crate::Db, source: SourceProgram) -> Program { +pub fn parse_statements<'db>(db: &'db dyn crate::Db, source: SourceProgram) -> Program<'db> { // Get the source text from the database let source_text = source.text(db); @@ -58,13 +58,13 @@ pub fn parse_statements(db: &dyn crate::Db, source: SourceProgram) -> Program { /// or [`Parser::word`]). These methods guarantee that, when they return `None`, the position /// is not changed apart from consuming whitespace. This allows them to be used to probe ahead /// and test the next token. -struct Parser<'p> { - db: &'p dyn crate::Db, - source_text: &'p str, +struct Parser<'source, 'db> { + db: &'db dyn crate::Db, + source_text: &'source str, position: usize, } -impl Parser<'_> { +impl<'db> Parser<'_, 'db> { // Invoke `f` and, if it returns `None`, then restore the parsing position. fn probe(&mut self, f: impl FnOnce(&mut Self) -> Option) -> Option { let p = self.position; @@ -99,7 +99,7 @@ impl Parser<'_> { } // Returns a span ranging from `start_position` until the current position (exclusive) - fn span_from(&self, start_position: usize) -> Span { + fn span_from(&self, start_position: usize) -> Span<'db> { Span::new(self.db, start_position, self.position) } @@ -121,7 +121,7 @@ impl Parser<'_> { } // ANCHOR: parse_statement - fn parse_statement(&mut self) -> Option { + fn parse_statement(&mut self) -> Option> { let start_position = self.skip_whitespace(); let word = self.word()?; if word == "fn" { @@ -143,7 +143,7 @@ impl Parser<'_> { // ANCHOR_END: parse_statement // ANCHOR: parse_function - fn parse_function(&mut self) -> Option { + fn parse_function(&mut self) -> Option> { let start_position = self.skip_whitespace(); let name = self.word()?; let name_span = self.span_from(start_position); @@ -161,7 +161,7 @@ impl Parser<'_> { } // ANCHOR_END: parse_function - fn parse_expression(&mut self) -> Option { + fn parse_expression(&mut self) -> Option> { self.parse_op_expression(Self::parse_expression1, Self::low_op) } @@ -178,7 +178,7 @@ impl Parser<'_> { /// Parses a high-precedence expression (times, div). /// /// On failure, skips arbitrary tokens. - fn parse_expression1(&mut self) -> Option { + fn parse_expression1(&mut self) -> Option> { self.parse_op_expression(Self::parse_expression2, Self::high_op) } @@ -194,9 +194,9 @@ impl Parser<'_> { fn parse_op_expression( &mut self, - mut parse_expr: impl FnMut(&mut Self) -> Option, + mut parse_expr: impl FnMut(&mut Self) -> Option>, mut op: impl FnMut(&mut Self) -> Option, - ) -> Option { + ) -> Option> { let start_position = self.skip_whitespace(); let mut expr1 = parse_expr(self)?; @@ -214,7 +214,7 @@ impl Parser<'_> { /// Parses a "base expression" (no operators). /// /// On failure, skips arbitrary tokens. - fn parse_expression2(&mut self) -> Option { + fn parse_expression2(&mut self) -> Option> { let start_position = self.skip_whitespace(); if let Some(w) = self.word() { if self.ch('(').is_some() { @@ -246,7 +246,7 @@ impl Parser<'_> { } } - fn parse_expressions(&mut self) -> Option> { + fn parse_expressions(&mut self) -> Option>> { let mut r = vec![]; loop { let expr = self.parse_expression()?; @@ -261,7 +261,7 @@ impl Parser<'_> { /// No trailing commas because I am lazy. /// /// On failure, skips arbitrary tokens. - fn parameters(&mut self) -> Option> { + fn parameters(&mut self) -> Option>> { let mut r = vec![]; loop { let name = self.word()?; @@ -277,7 +277,7 @@ impl Parser<'_> { /// Parses a single character. /// /// Even on failure, only skips whitespace. - fn ch(&mut self, c: char) -> Option { + fn ch(&mut self, c: char) -> Option> { let start_position = self.skip_whitespace(); match self.peek() { Some(p) if c == p => { @@ -383,16 +383,22 @@ fn parse_print() { Statement { span: Span { [salsa id]: 4, + start: 0, + end: 11, }, data: Print( Expression { span: Span { [salsa id]: 3, + start: 6, + end: 11, }, data: Op( Expression { span: Span { [salsa id]: 0, + start: 6, + end: 7, }, data: Number( OrderedFloat( @@ -404,6 +410,8 @@ fn parse_print() { Expression { span: Span { [salsa id]: 2, + start: 10, + end: 11, }, data: Number( OrderedFloat( @@ -442,40 +450,179 @@ fn parse_example() { Statement { span: Span { [salsa id]: 9, + start: 13, + end: 57, }, data: Function( Function { [salsa id]: 0, + name: FunctionId { + [salsa id]: 0, + text: "area_rectangle", + }, + name_span: Span { + [salsa id]: 0, + start: 16, + end: 30, + }, + args: [ + VariableId { + [salsa id]: 0, + text: "w", + }, + VariableId { + [salsa id]: 1, + text: "h", + }, + ], + body: Expression { + span: Span { + [salsa id]: 8, + start: 39, + end: 57, + }, + data: Op( + Expression { + span: Span { + [salsa id]: 5, + start: 39, + end: 41, + }, + data: Variable( + VariableId { + [salsa id]: 0, + text: "w", + }, + ), + }, + Multiply, + Expression { + span: Span { + [salsa id]: 7, + start: 43, + end: 57, + }, + data: Variable( + VariableId { + [salsa id]: 1, + text: "h", + }, + ), + }, + ), + }, }, ), }, Statement { span: Span { [salsa id]: 21, + start: 57, + end: 102, }, data: Function( Function { [salsa id]: 1, + name: FunctionId { + [salsa id]: 1, + text: "area_circle", + }, + name_span: Span { + [salsa id]: 10, + start: 60, + end: 71, + }, + args: [ + VariableId { + [salsa id]: 2, + text: "r", + }, + ], + body: Expression { + span: Span { + [salsa id]: 20, + start: 77, + end: 102, + }, + data: Op( + Expression { + span: Span { + [salsa id]: 17, + start: 77, + end: 86, + }, + data: Op( + Expression { + span: Span { + [salsa id]: 14, + start: 77, + end: 81, + }, + data: Number( + OrderedFloat( + 3.14, + ), + ), + }, + Multiply, + Expression { + span: Span { + [salsa id]: 16, + start: 84, + end: 86, + }, + data: Variable( + VariableId { + [salsa id]: 2, + text: "r", + }, + ), + }, + ), + }, + Multiply, + Expression { + span: Span { + [salsa id]: 19, + start: 88, + end: 102, + }, + data: Variable( + VariableId { + [salsa id]: 2, + text: "r", + }, + ), + }, + ), + }, }, ), }, Statement { span: Span { [salsa id]: 28, + start: 102, + end: 141, }, data: Print( Expression { span: Span { [salsa id]: 27, + start: 108, + end: 128, }, data: Call( FunctionId { [salsa id]: 0, + text: "area_rectangle", }, [ Expression { span: Span { [salsa id]: 23, + start: 123, + end: 124, }, data: Number( OrderedFloat( @@ -486,6 +633,8 @@ fn parse_example() { Expression { span: Span { [salsa id]: 25, + start: 126, + end: 127, }, data: Number( OrderedFloat( @@ -501,20 +650,27 @@ fn parse_example() { Statement { span: Span { [salsa id]: 33, + start: 141, + end: 174, }, data: Print( Expression { span: Span { [salsa id]: 32, + start: 147, + end: 161, }, data: Call( FunctionId { [salsa id]: 1, + text: "area_circle", }, [ Expression { span: Span { [salsa id]: 30, + start: 159, + end: 160, }, data: Number( OrderedFloat( @@ -530,16 +686,22 @@ fn parse_example() { Statement { span: Span { [salsa id]: 38, + start: 174, + end: 195, }, data: Print( Expression { span: Span { [salsa id]: 37, + start: 180, + end: 186, }, data: Op( Expression { span: Span { [salsa id]: 34, + start: 180, + end: 182, }, data: Number( OrderedFloat( @@ -551,6 +713,8 @@ fn parse_example() { Expression { span: Span { [salsa id]: 36, + start: 185, + end: 186, }, data: Number( OrderedFloat( @@ -604,21 +768,29 @@ fn parse_precedence() { Statement { span: Span { [salsa id]: 10, + start: 0, + end: 19, }, data: Print( Expression { span: Span { [salsa id]: 9, + start: 6, + end: 19, }, data: Op( Expression { span: Span { [salsa id]: 6, + start: 6, + end: 16, }, data: Op( Expression { span: Span { [salsa id]: 0, + start: 6, + end: 7, }, data: Number( OrderedFloat( @@ -630,11 +802,15 @@ fn parse_precedence() { Expression { span: Span { [salsa id]: 5, + start: 10, + end: 15, }, data: Op( Expression { span: Span { [salsa id]: 2, + start: 10, + end: 11, }, data: Number( OrderedFloat( @@ -646,6 +822,8 @@ fn parse_precedence() { Expression { span: Span { [salsa id]: 4, + start: 14, + end: 15, }, data: Number( OrderedFloat( @@ -661,6 +839,8 @@ fn parse_precedence() { Expression { span: Span { [salsa id]: 8, + start: 18, + end: 19, }, data: Number( OrderedFloat( diff --git a/examples-2022/calc/src/type_check.rs b/examples-2022/calc/src/type_check.rs index 62df3133b..8043597c1 100644 --- a/examples-2022/calc/src/type_check.rs +++ b/examples-2022/calc/src/type_check.rs @@ -10,7 +10,7 @@ use test_log::test; // ANCHOR: parse_statements #[salsa::tracked] -pub fn type_check_program(db: &dyn crate::Db, program: Program) { +pub fn type_check_program<'db>(db: &'db dyn crate::Db, program: Program<'db>) { for statement in program.statements(db) { match &statement.data { StatementData::Function(f) => type_check_function(db, *f, program), @@ -20,12 +20,20 @@ pub fn type_check_program(db: &dyn crate::Db, program: Program) { } #[salsa::tracked] -pub fn type_check_function(db: &dyn crate::Db, function: Function, program: Program) { +pub fn type_check_function<'db>( + db: &'db dyn crate::Db, + function: Function<'db>, + program: Program<'db>, +) { CheckExpression::new(db, program, function.args(db)).check(function.body(db)) } #[salsa::tracked] -pub fn find_function(db: &dyn crate::Db, program: Program, name: FunctionId) -> Option { +pub fn find_function<'db>( + db: &'db dyn crate::Db, + program: Program<'db>, + name: FunctionId<'db>, +) -> Option> { program .statements(db) .iter() @@ -37,14 +45,14 @@ pub fn find_function(db: &dyn crate::Db, program: Program, name: FunctionId) -> } #[derive(new)] -struct CheckExpression<'w> { - db: &'w dyn crate::Db, - program: Program, - names_in_scope: &'w [VariableId], +struct CheckExpression<'input, 'db> { + db: &'db dyn crate::Db, + program: Program<'db>, + names_in_scope: &'input [VariableId<'db>], } -impl CheckExpression<'_> { - fn check(&self, expression: &Expression) { +impl<'db> CheckExpression<'_, 'db> { + fn check(&self, expression: &Expression<'db>) { match &expression.data { crate::ir::ExpressionData::Op(left, _, right) => { self.check(left); @@ -73,7 +81,7 @@ impl CheckExpression<'_> { } } - fn find_function(&self, f: FunctionId) -> Option { + fn find_function(&self, f: FunctionId<'db>) -> Option> { find_function(self.db, self.program, f) } diff --git a/examples-2022/lazy-input/src/main.rs b/examples-2022/lazy-input/src/main.rs index 252adab0a..d31c25397 100644 --- a/examples-2022/lazy-input/src/main.rs +++ b/examples-2022/lazy-input/src/main.rs @@ -62,7 +62,7 @@ fn main() -> Result<()> { // ANCHOR_END: main #[salsa::jar(db = Db)] -struct Jar(Diagnostic, File, ParsedFile, compile, parse, sum); +struct Jar(Diagnostic, File, ParsedFile<'_>, compile, parse, sum); // ANCHOR: db #[salsa::input] @@ -155,10 +155,10 @@ impl Diagnostic { } #[salsa::tracked] -struct ParsedFile { +struct ParsedFile<'db> { value: u32, #[return_ref] - links: Vec, + links: Vec>, } #[salsa::tracked] @@ -168,7 +168,7 @@ fn compile(db: &dyn Db, input: File) -> u32 { } #[salsa::tracked] -fn parse(db: &dyn Db, input: File) -> ParsedFile { +fn parse<'db>(db: &'db dyn Db, input: File) -> ParsedFile<'db> { let mut lines = input.contents(db).lines(); let value = match lines.next().map(|line| (line.parse::(), line)) { Some((Ok(num), _)) => num, @@ -215,7 +215,7 @@ fn parse(db: &dyn Db, input: File) -> ParsedFile { } #[salsa::tracked] -fn sum(db: &dyn Db, input: ParsedFile) -> u32 { +fn sum<'db>(db: &'db dyn Db, input: ParsedFile<'db>) -> u32 { input.value(db) + input .links(db) diff --git a/salsa-2022-tests/tests/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs b/salsa-2022-tests/tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs similarity index 85% rename from salsa-2022-tests/tests/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs rename to salsa-2022-tests/tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs index aebb2f346..6a973a344 100644 --- a/salsa-2022-tests/tests/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs +++ b/salsa-2022-tests/tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs @@ -1,7 +1,7 @@ use test_log::test; #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, tracked_fn); +struct Jar(MyInput, MyTracked<'_>, tracked_fn); trait Db: salsa::DbWithJar {} @@ -11,12 +11,12 @@ struct MyInput { } #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked(jar = Jar)] -fn tracked_fn(db: &dyn Db, input: MyInput) -> MyTracked { +fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { MyTracked::new(db, input.field(db) / 2) } diff --git a/salsa-2022-tests/tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.stderr b/salsa-2022-tests/tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.stderr new file mode 100644 index 000000000..89b120819 --- /dev/null +++ b/salsa-2022-tests/tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.stderr @@ -0,0 +1,5 @@ +error[E0601]: `main` function not found in crate `$CRATE` + --> tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs:46:2 + | +46 | } + | ^ consider adding a `main` function to `$DIR/tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs` diff --git a/salsa-2022-tests/tests/compile-fail/span-tracked-getter.rs b/salsa-2022-tests/tests/compile-fail/span-tracked-getter.rs index 936ad170c..5ee98cc31 100644 --- a/salsa-2022-tests/tests/compile-fail/span-tracked-getter.rs +++ b/salsa-2022-tests/tests/compile-fail/span-tracked-getter.rs @@ -1,5 +1,5 @@ #[salsa::jar(db = Db)] -pub struct Jar(MyTracked, my_fn); +pub struct Jar(MyTracked<'_>, my_fn); pub trait Db: salsa::DbWithJar {} @@ -14,7 +14,7 @@ impl salsa::Database for Database {} impl Db for Database {} #[salsa::tracked] -pub struct MyTracked { +pub struct MyTracked<'db> { field: u32, } diff --git a/salsa-2022-tests/tests/compile-fail/span-tracked-getter.stderr b/salsa-2022-tests/tests/compile-fail/span-tracked-getter.stderr index 44e54086f..86d2d610d 100644 --- a/salsa-2022-tests/tests/compile-fail/span-tracked-getter.stderr +++ b/salsa-2022-tests/tests/compile-fail/span-tracked-getter.stderr @@ -13,6 +13,6 @@ note: method defined here | 16 | #[salsa::tracked] | ----------------- -17 | pub struct MyTracked { +17 | pub struct MyTracked<'db> { 18 | field: u32, | ^^^^^ diff --git a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.rs b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.rs index ac5811a32..981bc40b0 100644 --- a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.rs +++ b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.rs @@ -3,7 +3,7 @@ #![allow(warnings)] #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, tracked_fn); +struct Jar(MyInput, MyTracked<'_>, tracked_fn); trait Db: salsa::DbWithJar {} @@ -13,12 +13,12 @@ struct MyInput { } #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked(jar = Jar, specify)] -fn tracked_fn(db: &dyn Db, input: MyInput) -> MyTracked { +fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { MyTracked::new(db, input.field(db) * 2) } diff --git a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr index 9553bb0ea..f6eb291fc 100644 --- a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr +++ b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr @@ -4,7 +4,7 @@ error[E0277]: the trait bound `MyInput: TrackedStructInDb` is not satisf 20 | #[salsa::tracked(jar = Jar, specify)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TrackedStructInDb` is not implemented for `MyInput` | - = help: the trait `TrackedStructInDb` is implemented for `MyTracked` + = help: the trait `TrackedStructInDb` is implemented for `MyTracked<'db>` note: required by a bound in `function::specify::>::specify_and_record` --> $WORKSPACE/components/salsa-2022/src/function/specify.rs | diff --git a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs index a40e8c581..eaa56c606 100644 --- a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs +++ b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs @@ -3,23 +3,22 @@ #![allow(warnings)] #[salsa::jar(db = Db)] -struct Jar(MyInterned, MyTracked, tracked_fn); +struct Jar(MyInterned<'_>, MyTracked<'_>, tracked_fn); trait Db: salsa::DbWithJar {} #[salsa::interned(jar = Jar)] -struct MyInterned { +struct MyInterned<'db> { field: u32, } #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { field: u32, } - #[salsa::tracked(jar = Jar, specify)] -fn tracked_fn(db: &dyn Db, input: MyInterned) -> MyTracked { +fn tracked_fn<'db>(db: &'db dyn Db, input: MyInterned<'db>) -> MyTracked<'db> { MyTracked::new(db, input.field(db) * 2) } diff --git a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr index 1b5064381..2d9009269 100644 --- a/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr +++ b/salsa-2022-tests/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr @@ -1,10 +1,10 @@ -error[E0277]: the trait bound `MyInterned: TrackedStructInDb` is not satisfied - --> tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs:21:1 +error[E0277]: the trait bound `MyInterned<'_>: TrackedStructInDb` is not satisfied + --> tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs:20:1 | -21 | #[salsa::tracked(jar = Jar, specify)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TrackedStructInDb` is not implemented for `MyInterned` +20 | #[salsa::tracked(jar = Jar, specify)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TrackedStructInDb` is not implemented for `MyInterned<'_>` | - = help: the trait `TrackedStructInDb` is implemented for `MyTracked` + = help: the trait `TrackedStructInDb` is implemented for `MyTracked<'db>` note: required by a bound in `function::specify::>::specify_and_record` --> $WORKSPACE/components/salsa-2022/src/function/specify.rs | diff --git a/salsa-2022-tests/tests/compile-fail/tracked_impl_incompatibles.rs b/salsa-2022-tests/tests/compile-fail/tracked_impl_incompatibles.rs index 2aa238765..a332f2c58 100644 --- a/salsa-2022-tests/tests/compile-fail/tracked_impl_incompatibles.rs +++ b/salsa-2022-tests/tests/compile-fail/tracked_impl_incompatibles.rs @@ -1,74 +1,54 @@ #[salsa::jar(db = Db)] -struct Jar(MyTracked); - +struct Jar(MyTracked<'_>); #[salsa::tracked] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked(return_ref)] -impl std::default::Default for MyTracked { - fn default() -> Self { - - } +impl<'db> std::default::Default for MyTracked<'db> { + fn default() -> Self {} } #[salsa::tracked(specify)] -impl std::default::Default for MyTracked { - fn default() -> Self { - - } +impl<'db> std::default::Default for MyTracked<'db> { + fn default() -> Self {} } #[salsa::tracked(no_eq)] -impl std::default::Default for MyTracked { - fn default() -> Self { - - } +impl<'db> std::default::Default for MyTracked<'db> { + fn default() -> Self {} } #[salsa::tracked(data = Data)] -impl std::default::Default for MyTracked { - fn default() -> Self { - - } +impl<'db> std::default::Default for MyTracked<'db> { + fn default() -> Self {} } #[salsa::tracked(db = Db)] -impl std::default::Default for MyTracked { - fn default() -> Self { - - } +impl<'db> std::default::Default for MyTracked<'db> { + fn default() -> Self {} } #[salsa::tracked(recover_fn = recover)] -impl std::default::Default for MyTracked { - fn default() -> Self { - - } +impl<'db> std::default::Default for MyTracked<'db> { + fn default() -> Self {} } #[salsa::tracked(lru = 32)] -impl std::default::Default for MyTracked { - fn default() -> Self { - - } +impl<'db> std::default::Default for MyTracked<'db> { + fn default() -> Self {} } #[salsa::tracked(constructor = Constructor)] -impl std::default::Default for MyTracked { - fn default() -> Self { - - } +impl<'db> std::default::Default for MyTracked<'db> { + fn default() -> Self {} } #[salsa::tracked] -impl std::default::Default for [MyTracked; 12] { - fn default() -> Self { - - } +impl<'db> std::default::Default for [MyTracked<'db>; 12] { + fn default() -> Self {} } - trait Db: salsa::DbWithJar {} -fn main() {} \ No newline at end of file +fn main() {} diff --git a/salsa-2022-tests/tests/compile-fail/tracked_impl_incompatibles.stderr b/salsa-2022-tests/tests/compile-fail/tracked_impl_incompatibles.stderr index 28b902851..2074b7fd9 100644 --- a/salsa-2022-tests/tests/compile-fail/tracked_impl_incompatibles.stderr +++ b/salsa-2022-tests/tests/compile-fail/tracked_impl_incompatibles.stderr @@ -1,53 +1,53 @@ error: `return_ref` option not allowed here - --> tests/compile-fail/tracked_impl_incompatibles.rs:10:18 - | -10 | #[salsa::tracked(return_ref)] - | ^^^^^^^^^^ + --> tests/compile-fail/tracked_impl_incompatibles.rs:9:18 + | +9 | #[salsa::tracked(return_ref)] + | ^^^^^^^^^^ error: `specify` option not allowed here - --> tests/compile-fail/tracked_impl_incompatibles.rs:16:18 + --> tests/compile-fail/tracked_impl_incompatibles.rs:13:18 | -16 | #[salsa::tracked(specify)] +13 | #[salsa::tracked(specify)] | ^^^^^^^ error: `no_eq` option not allowed here - --> tests/compile-fail/tracked_impl_incompatibles.rs:23:18 + --> tests/compile-fail/tracked_impl_incompatibles.rs:18:18 | -23 | #[salsa::tracked(no_eq)] +18 | #[salsa::tracked(no_eq)] | ^^^^^ error: `data` option not allowed here - --> tests/compile-fail/tracked_impl_incompatibles.rs:30:18 + --> tests/compile-fail/tracked_impl_incompatibles.rs:23:18 | -30 | #[salsa::tracked(data = Data)] +23 | #[salsa::tracked(data = Data)] | ^^^^ error: `db` option not allowed here - --> tests/compile-fail/tracked_impl_incompatibles.rs:37:18 + --> tests/compile-fail/tracked_impl_incompatibles.rs:28:18 | -37 | #[salsa::tracked(db = Db)] +28 | #[salsa::tracked(db = Db)] | ^^ error: unrecognized option `recover_fn` - --> tests/compile-fail/tracked_impl_incompatibles.rs:44:18 + --> tests/compile-fail/tracked_impl_incompatibles.rs:33:18 | -44 | #[salsa::tracked(recover_fn = recover)] +33 | #[salsa::tracked(recover_fn = recover)] | ^^^^^^^^^^ error: `lru` option not allowed here - --> tests/compile-fail/tracked_impl_incompatibles.rs:51:18 + --> tests/compile-fail/tracked_impl_incompatibles.rs:38:18 | -51 | #[salsa::tracked(lru = 32)] +38 | #[salsa::tracked(lru = 32)] | ^^^ error: `constructor` option not allowed here - --> tests/compile-fail/tracked_impl_incompatibles.rs:58:18 + --> tests/compile-fail/tracked_impl_incompatibles.rs:43:18 | -58 | #[salsa::tracked(constructor = Constructor)] +43 | #[salsa::tracked(constructor = Constructor)] | ^^^^^^^^^^^ error: #[salsa::tracked] can only be applied to salsa structs - --> tests/compile-fail/tracked_impl_incompatibles.rs:65:32 + --> tests/compile-fail/tracked_impl_incompatibles.rs:48:37 | -65 | impl std::default::Default for [MyTracked; 12] { - | ^^^^^^^^^^^^^^^ +48 | impl<'db> std::default::Default for [MyTracked<'db>; 12] { + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/salsa-2022-tests/tests/compile-fail/tracked_method_incompatibles.rs b/salsa-2022-tests/tests/compile-fail/tracked_method_incompatibles.rs index 5c2fe0654..a3d1354d1 100644 --- a/salsa-2022-tests/tests/compile-fail/tracked_method_incompatibles.rs +++ b/salsa-2022-tests/tests/compile-fail/tracked_method_incompatibles.rs @@ -1,21 +1,16 @@ #[salsa::jar(db = Db)] -struct Jar(Tracked); - +struct Jar(Tracked<'_>); #[salsa::tracked(jar = Jar)] -struct Tracked { - field: u32, +struct Tracked<'db> { + field: u32, } - -impl Tracked { +impl<'db> Tracked<'db> { #[salsa::tracked] - fn use_tracked(&self) { - - } + fn use_tracked(&self) {} } trait Db: salsa::DbWithJar {} - -fn main() {} \ No newline at end of file +fn main() {} diff --git a/salsa-2022-tests/tests/compile-fail/tracked_method_incompatibles.stderr b/salsa-2022-tests/tests/compile-fail/tracked_method_incompatibles.stderr index e1ba28c1c..ea80844e8 100644 --- a/salsa-2022-tests/tests/compile-fail/tracked_method_incompatibles.stderr +++ b/salsa-2022-tests/tests/compile-fail/tracked_method_incompatibles.stderr @@ -1,5 +1,5 @@ error: #[salsa::tracked] must also be applied to the impl block for tracked methods - --> tests/compile-fail/tracked_method_incompatibles.rs:13:20 + --> tests/compile-fail/tracked_method_incompatibles.rs:11:20 | -13 | fn use_tracked(&self) { +11 | fn use_tracked(&self) {} | ^ diff --git a/salsa-2022-tests/tests/create-large-jar-database.rs b/salsa-2022-tests/tests/create-large-jar-database.rs index 8f753d887..b444d6bd4 100644 --- a/salsa-2022-tests/tests/create-large-jar-database.rs +++ b/salsa-2022-tests/tests/create-large-jar-database.rs @@ -34,14 +34,14 @@ macro_rules! make_jarX { ($jarX:ident, $JarX:ident) => { mod $jarX { #[salsa::jar(db = Db)] - pub(crate) struct $JarX(T1); + pub(crate) struct $JarX(T1<'_>); pub(crate) trait Db: salsa::DbWithJar<$JarX> {} impl Db for DB where DB: salsa::DbWithJar<$JarX> {} #[salsa::tracked(jar = $JarX)] - struct T1 { + struct T1<'db> { a0: String, a1: String, a2: String, diff --git a/salsa-2022-tests/tests/debug.rs b/salsa-2022-tests/tests/debug.rs index c173d79e7..a614d9b13 100644 --- a/salsa-2022-tests/tests/debug.rs +++ b/salsa-2022-tests/tests/debug.rs @@ -8,7 +8,7 @@ struct Jar( MyInput, ComplexStruct, leak_debug_string, - DerivedCustom, + DerivedCustom<'_>, leak_derived_custom, ); @@ -88,13 +88,13 @@ fn untracked_dependencies() { #[salsa::tracked] #[customize(DebugWithDb)] -struct DerivedCustom { +struct DerivedCustom<'db> { my_input: MyInput, value: u32, } -impl DebugWithDb for DerivedCustom { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &dyn Db) -> std::fmt::Result { +impl<'db> DebugWithDb for DerivedCustom<'db> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &(dyn Db + 'db)) -> std::fmt::Result { write!( f, "{:?} / {:?}", diff --git a/salsa-2022-tests/tests/deletion.rs b/salsa-2022-tests/tests/deletion.rs index 89cfd412c..cc0394151 100644 --- a/salsa-2022-tests/tests/deletion.rs +++ b/salsa-2022-tests/tests/deletion.rs @@ -11,7 +11,7 @@ use test_log::test; #[salsa::jar(db = Db)] struct Jar( MyInput, - MyTracked, + MyTracked<'_>, final_result, create_tracked_structs, contribution_from_struct, @@ -35,12 +35,12 @@ fn final_result(db: &dyn Db, input: MyInput) -> u32 { } #[salsa::tracked] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked] -fn create_tracked_structs(db: &dyn Db, input: MyInput) -> Vec { +fn create_tracked_structs<'db>(db: &'db dyn Db, input: MyInput) -> Vec> { db.push_log(format!("intermediate_result({:?})", input)); (0..input.field(db)) .map(|i| MyTracked::new(db, i)) @@ -48,7 +48,7 @@ fn create_tracked_structs(db: &dyn Db, input: MyInput) -> Vec { } #[salsa::tracked] -fn contribution_from_struct(db: &dyn Db, tracked: MyTracked) -> u32 { +fn contribution_from_struct<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> u32 { tracked.field(db) * 2 } diff --git a/salsa-2022-tests/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs b/salsa-2022-tests/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs index f5058b97e..d3b7c4654 100644 --- a/salsa-2022-tests/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs +++ b/salsa-2022-tests/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs @@ -10,7 +10,7 @@ use expect_test::expect; #[salsa::jar(db = Db)] struct Jar( MyInput, - MyTracked, + MyTracked<'_>, final_result_depends_on_x, final_result_depends_on_y, intermediate_result, @@ -36,13 +36,13 @@ fn final_result_depends_on_y(db: &dyn Db, input: MyInput) -> u32 { } #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { x: u32, y: u32, } #[salsa::tracked(jar = Jar)] -fn intermediate_result(db: &dyn Db, input: MyInput) -> MyTracked { +fn intermediate_result<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { MyTracked::new(db, (input.field(db) + 1) / 2, input.field(db) / 2) } diff --git a/salsa-2022-tests/tests/override_new_get_set.rs b/salsa-2022-tests/tests/override_new_get_set.rs index 10d449a76..7e12a63d4 100644 --- a/salsa-2022-tests/tests/override_new_get_set.rs +++ b/salsa-2022-tests/tests/override_new_get_set.rs @@ -6,7 +6,7 @@ use std::fmt::Display; #[salsa::jar(db = Db)] -struct Jar(MyInput, MyInterned, MyTracked); +struct Jar(MyInput, MyInterned<'_>, MyTracked<'_>); trait Db: salsa::DbWithJar {} @@ -32,34 +32,34 @@ impl MyInput { } #[salsa::interned(constructor = from_string)] -struct MyInterned { +struct MyInterned<'db> { #[get(text)] #[return_ref] field: String, } -impl MyInterned { - pub fn new(db: &dyn Db, s: impl Display) -> MyInterned { +impl<'db> MyInterned<'db> { + pub fn new(db: &'db dyn Db, s: impl Display) -> MyInterned<'db> { MyInterned::from_string(db, s.to_string()) } - pub fn field(self, db: &dyn Db) -> &str { + pub fn field(self, db: &'db dyn Db) -> &str { &self.text(db) } } #[salsa::tracked(constructor = from_string)] -struct MyTracked { +struct MyTracked<'db> { #[get(text)] field: String, } -impl MyTracked { - pub fn new(db: &dyn Db, s: impl Display) -> MyTracked { +impl<'db> MyTracked<'db> { + pub fn new(db: &'db dyn Db, s: impl Display) -> MyTracked<'db> { MyTracked::from_string(db, s.to_string()) } - pub fn field(self, db: &dyn Db) -> String { + pub fn field(self, db: &'db dyn Db) -> String { self.text(db) } } diff --git a/salsa-2022-tests/tests/panic-when-creating-tracked-struct-outside-of-tracked-fn.rs b/salsa-2022-tests/tests/panic-when-creating-tracked-struct-outside-of-tracked-fn.rs index 100b6bb54..9263ef800 100644 --- a/salsa-2022-tests/tests/panic-when-creating-tracked-struct-outside-of-tracked-fn.rs +++ b/salsa-2022-tests/tests/panic-when-creating-tracked-struct-outside-of-tracked-fn.rs @@ -2,12 +2,12 @@ //! tracked function panics with an assert message. #[salsa::jar(db = Db)] -struct Jar(MyTracked); +struct Jar(MyTracked<'_>); trait Db: salsa::DbWithJar {} #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { field: u32, } diff --git a/salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs b/salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs index 1add83d7b..9365a0011 100644 --- a/salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs +++ b/salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs @@ -13,7 +13,7 @@ thread_local! { } #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, function); +struct Jar(MyInput, MyTracked<'_>, function); trait Db: salsa::DbWithJar + HasLogger {} @@ -45,7 +45,7 @@ struct MyInput { } #[salsa::tracked] -struct MyTracked { +struct MyTracked<'db> { counter: usize, } diff --git a/salsa-2022-tests/tests/specify-only-works-if-the-key-is-created-in-the-current-query.rs b/salsa-2022-tests/tests/specify-only-works-if-the-key-is-created-in-the-current-query.rs index 5bbfa6783..e0c1c8955 100644 --- a/salsa-2022-tests/tests/specify-only-works-if-the-key-is-created-in-the-current-query.rs +++ b/salsa-2022-tests/tests/specify-only-works-if-the-key-is-created-in-the-current-query.rs @@ -5,7 +5,7 @@ #[salsa::jar(db = Db)] struct Jar( MyInput, - MyTracked, + MyTracked<'_>, tracked_fn, tracked_fn_extra, tracked_struct_created_in_another_query, @@ -19,17 +19,17 @@ struct MyInput { } #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked(jar = Jar)] -fn tracked_struct_created_in_another_query(db: &dyn Db, input: MyInput) -> MyTracked { +fn tracked_struct_created_in_another_query<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { MyTracked::new(db, input.field(db) * 2) } #[salsa::tracked(jar = Jar)] -fn tracked_fn(db: &dyn Db, input: MyInput) -> MyTracked { +fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { let t = tracked_struct_created_in_another_query(db, input); if input.field(db) != 0 { tracked_fn_extra::specify(db, t, 2222); @@ -38,7 +38,7 @@ fn tracked_fn(db: &dyn Db, input: MyInput) -> MyTracked { } #[salsa::tracked(jar = Jar, specify)] -fn tracked_fn_extra(_db: &dyn Db, _input: MyTracked) -> u32 { +fn tracked_fn_extra<'db>(_db: &'db dyn Db, _input: MyTracked<'db>) -> u32 { 0 } diff --git a/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs b/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs index f6e33b9e3..998815692 100644 --- a/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs +++ b/salsa-2022-tests/tests/specify_tracked_fn_in_rev_1_but_not_2.rs @@ -9,7 +9,7 @@ use test_log::test; #[salsa::jar(db = Db)] struct Jar( MyInput, - MyTracked, + MyTracked<'_>, maybe_specified, read_maybe_specified, create_tracked, @@ -24,14 +24,14 @@ struct MyInput { } #[salsa::tracked] -struct MyTracked { +struct MyTracked<'db> { input: MyInput, } /// If the input is in the range 0..10, this is specified to return 10. /// Otherwise, the default occurs, and it returns the input. #[salsa::tracked(specify)] -fn maybe_specified(db: &dyn Db, tracked: MyTracked) -> u32 { +fn maybe_specified<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> u32 { db.push_log(format!("maybe_specified({:?})", tracked)); tracked.input(db).field(db) } @@ -40,7 +40,7 @@ fn maybe_specified(db: &dyn Db, tracked: MyTracked) -> u32 { /// This is here to show whether we can detect when `maybe_specified` has changed /// and control down-stream work accordingly. #[salsa::tracked] -fn read_maybe_specified(db: &dyn Db, tracked: MyTracked) -> u32 { +fn read_maybe_specified<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> u32 { db.push_log(format!("read_maybe_specified({:?})", tracked)); maybe_specified(db, tracked) * 10 } @@ -48,7 +48,7 @@ fn read_maybe_specified(db: &dyn Db, tracked: MyTracked) -> u32 { /// Create a tracked value and *maybe* specify a value for /// `maybe_specified` #[salsa::tracked(jar = Jar)] -fn create_tracked(db: &dyn Db, input: MyInput) -> MyTracked { +fn create_tracked<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { db.push_log(format!("create_tracked({:?})", input)); let tracked = MyTracked::new(db, input); if input.field(db) < 10 { diff --git a/salsa-2022-tests/tests/tracked-struct-id-field-bad-eq.rs b/salsa-2022-tests/tests/tracked-struct-id-field-bad-eq.rs index fd9c38a28..609d9e753 100644 --- a/salsa-2022-tests/tests/tracked-struct-id-field-bad-eq.rs +++ b/salsa-2022-tests/tests/tracked-struct-id-field-bad-eq.rs @@ -3,7 +3,7 @@ use test_log::test; #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, the_fn); +struct Jar(MyInput, MyTracked<'_>, the_fn); trait Db: salsa::DbWithJar {} @@ -31,7 +31,7 @@ impl From for BadEq { } #[salsa::tracked] -struct MyTracked { +struct MyTracked<'db> { #[id] field: BadEq, } diff --git a/salsa-2022-tests/tests/tracked-struct-id-field-bad-hash.rs b/salsa-2022-tests/tests/tracked-struct-id-field-bad-hash.rs index 98c1458f8..fa8a556d8 100644 --- a/salsa-2022-tests/tests/tracked-struct-id-field-bad-hash.rs +++ b/salsa-2022-tests/tests/tracked-struct-id-field-bad-hash.rs @@ -8,7 +8,7 @@ use test_log::test; #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, the_fn); +struct Jar(MyInput, MyTracked<'_>, the_fn); trait Db: salsa::DbWithJar {} @@ -35,7 +35,7 @@ impl std::hash::Hash for BadHash { } #[salsa::tracked] -struct MyTracked { +struct MyTracked<'db> { #[id] field: BadHash, } diff --git a/salsa-2022-tests/tests/tracked-struct-unchanged-in-new-rev.rs b/salsa-2022-tests/tests/tracked-struct-unchanged-in-new-rev.rs index 26199a4a3..29bd973af 100644 --- a/salsa-2022-tests/tests/tracked-struct-unchanged-in-new-rev.rs +++ b/salsa-2022-tests/tests/tracked-struct-unchanged-in-new-rev.rs @@ -1,7 +1,7 @@ use test_log::test; #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, tracked_fn); +struct Jar(MyInput, MyTracked<'_>, tracked_fn); trait Db: salsa::DbWithJar {} @@ -11,12 +11,12 @@ struct MyInput { } #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked(jar = Jar)] -fn tracked_fn(db: &dyn Db, input: MyInput) -> MyTracked { +fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { MyTracked::new(db, input.field(db) / 2) } diff --git a/salsa-2022-tests/tests/tracked-struct-value-field-bad-eq.rs b/salsa-2022-tests/tests/tracked-struct-value-field-bad-eq.rs index 94651b0f9..e6665d254 100644 --- a/salsa-2022-tests/tests/tracked-struct-value-field-bad-eq.rs +++ b/salsa-2022-tests/tests/tracked-struct-value-field-bad-eq.rs @@ -10,7 +10,7 @@ use test_log::test; #[salsa::jar(db = Db)] struct Jar( MyInput, - MyTracked, + MyTracked<'_>, the_fn, make_tracked_struct, read_tracked_struct, @@ -42,7 +42,7 @@ impl From for BadEq { } #[salsa::tracked] -struct MyTracked { +struct MyTracked<'db> { field: BadEq, } @@ -53,12 +53,12 @@ fn the_fn(db: &dyn Db, input: MyInput) -> bool { } #[salsa::tracked] -fn make_tracked_struct(db: &dyn Db, input: MyInput) -> MyTracked { +fn make_tracked_struct<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { MyTracked::new(db, BadEq::from(input.field(db))) } #[salsa::tracked] -fn read_tracked_struct(db: &dyn Db, tracked: MyTracked) -> bool { +fn read_tracked_struct<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> bool { tracked.field(db).field } diff --git a/salsa-2022-tests/tests/tracked-struct-value-field-not-eq.rs b/salsa-2022-tests/tests/tracked-struct-value-field-not-eq.rs index 2822611b7..5a44009f8 100644 --- a/salsa-2022-tests/tests/tracked-struct-value-field-not-eq.rs +++ b/salsa-2022-tests/tests/tracked-struct-value-field-not-eq.rs @@ -5,7 +5,7 @@ use test_log::test; #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, the_fn); +struct Jar(MyInput, MyTracked<'_>, the_fn); trait Db: salsa::DbWithJar {} @@ -26,7 +26,7 @@ impl From for NotEq { } #[salsa::tracked] -struct MyTracked { +struct MyTracked<'db> { #[no_eq] field: NotEq, } diff --git a/salsa-2022-tests/tests/tracked_fn_on_tracked.rs b/salsa-2022-tests/tests/tracked_fn_on_tracked.rs index 61b944f50..02f213aee 100644 --- a/salsa-2022-tests/tests/tracked_fn_on_tracked.rs +++ b/salsa-2022-tests/tests/tracked_fn_on_tracked.rs @@ -2,7 +2,7 @@ //! compiles and executes successfully. #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, tracked_fn); +struct Jar(MyInput, MyTracked<'_>, tracked_fn); trait Db: salsa::DbWithJar {} @@ -12,12 +12,12 @@ struct MyInput { } #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked(jar = Jar)] -fn tracked_fn(db: &dyn Db, input: MyInput) -> MyTracked { +fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { MyTracked::new(db, input.field(db) * 2) } diff --git a/salsa-2022-tests/tests/tracked_fn_on_tracked_specify.rs b/salsa-2022-tests/tests/tracked_fn_on_tracked_specify.rs index 677463d8d..1b4dfbe41 100644 --- a/salsa-2022-tests/tests/tracked_fn_on_tracked_specify.rs +++ b/salsa-2022-tests/tests/tracked_fn_on_tracked_specify.rs @@ -3,7 +3,7 @@ #![allow(warnings)] #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, tracked_fn, tracked_fn_extra); +struct Jar(MyInput, MyTracked<'_>, tracked_fn, tracked_fn_extra); trait Db: salsa::DbWithJar {} @@ -13,12 +13,12 @@ struct MyInput { } #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked(jar = Jar)] -fn tracked_fn(db: &dyn Db, input: MyInput) -> MyTracked { +fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { let t = MyTracked::new(db, input.field(db) * 2); if input.field(db) != 0 { tracked_fn_extra::specify(db, t, 2222); @@ -27,7 +27,7 @@ fn tracked_fn(db: &dyn Db, input: MyInput) -> MyTracked { } #[salsa::tracked(jar = Jar, specify)] -fn tracked_fn_extra(_db: &dyn Db, _input: MyTracked) -> u32 { +fn tracked_fn_extra<'db>(_db: &'db dyn Db, _input: MyTracked<'db>) -> u32 { 0 } diff --git a/salsa-2022-tests/tests/tracked_fn_read_own_entity.rs b/salsa-2022-tests/tests/tracked_fn_read_own_entity.rs index d4621586b..0821c839c 100644 --- a/salsa-2022-tests/tests/tracked_fn_read_own_entity.rs +++ b/salsa-2022-tests/tests/tracked_fn_read_own_entity.rs @@ -6,28 +6,28 @@ use salsa_2022_tests::{HasLogger, Logger}; use test_log::test; #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, final_result, intermediate_result); +struct Jar(MyInput, MyTracked<'_>, final_result, intermediate_result); trait Db: salsa::DbWithJar + HasLogger {} -#[salsa::input(jar = Jar)] +#[salsa::input] struct MyInput { field: u32, } -#[salsa::tracked(jar = Jar)] +#[salsa::tracked] fn final_result(db: &dyn Db, input: MyInput) -> u32 { db.push_log(format!("final_result({:?})", input)); intermediate_result(db, input).field(db) * 2 } -#[salsa::tracked(jar = Jar)] -struct MyTracked { +#[salsa::tracked] +struct MyTracked<'db> { field: u32, } -#[salsa::tracked(jar = Jar)] -fn intermediate_result(db: &dyn Db, input: MyInput) -> MyTracked { +#[salsa::tracked] +fn intermediate_result<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { db.push_log(format!("intermediate_result({:?})", input)); let tracked = MyTracked::new(db, input.field(db) / 2); let _ = tracked.field(db); // read the field of an entity we created diff --git a/salsa-2022-tests/tests/tracked_fn_read_own_specify.rs b/salsa-2022-tests/tests/tracked_fn_read_own_specify.rs index 12257f641..8633572e5 100644 --- a/salsa-2022-tests/tests/tracked_fn_read_own_specify.rs +++ b/salsa-2022-tests/tests/tracked_fn_read_own_specify.rs @@ -3,7 +3,7 @@ use salsa::{Database as SalsaDatabase, DebugWithDb}; use salsa_2022_tests::{HasLogger, Logger}; #[salsa::jar(db = Db)] -struct Jar(MyInput, MyTracked, tracked_fn, tracked_fn_extra); +struct Jar(MyInput, MyTracked<'_>, tracked_fn, tracked_fn_extra); trait Db: salsa::DbWithJar + HasLogger {} @@ -13,12 +13,12 @@ struct MyInput { } #[salsa::tracked(jar = Jar)] -struct MyTracked { +struct MyTracked<'db> { field: u32, } #[salsa::tracked(jar = Jar)] -fn tracked_fn(db: &dyn Db, input: MyInput) -> u32 { +fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> u32 { db.push_log(format!("tracked_fn({:?})", input.debug(db))); let t = MyTracked::new(db, input.field(db) * 2); tracked_fn_extra::specify(db, t, 2222); @@ -26,7 +26,7 @@ fn tracked_fn(db: &dyn Db, input: MyInput) -> u32 { } #[salsa::tracked(jar = Jar, specify)] -fn tracked_fn_extra(db: &dyn Db, input: MyTracked) -> u32 { +fn tracked_fn_extra<'db>(db: &dyn Db, input: MyTracked<'db>) -> u32 { db.push_log(format!("tracked_fn_extra({:?})", input.debug(db))); 0 } diff --git a/salsa-2022-tests/tests/warnings/needless_borrow.rs b/salsa-2022-tests/tests/warnings/needless_borrow.rs index 502d23e20..787f69e7e 100644 --- a/salsa-2022-tests/tests/warnings/needless_borrow.rs +++ b/salsa-2022-tests/tests/warnings/needless_borrow.rs @@ -1,7 +1,7 @@ trait Db: salsa::DbWithJar {} #[salsa::jar(db = Db)] -struct Jar(TokenTree); +struct Jar(TokenTree<'_>); #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] enum Token {} @@ -13,7 +13,7 @@ impl salsa::DebugWithDb for Token { } #[salsa::tracked(jar = Jar)] -struct TokenTree { +struct TokenTree<'db> { #[return_ref] tokens: Vec, } diff --git a/salsa-2022-tests/tests/warnings/unused_variable_db.rs b/salsa-2022-tests/tests/warnings/unused_variable_db.rs index 3298481ef..5396918b8 100644 --- a/salsa-2022-tests/tests/warnings/unused_variable_db.rs +++ b/salsa-2022-tests/tests/warnings/unused_variable_db.rs @@ -1,7 +1,7 @@ trait Db: salsa::DbWithJar {} #[salsa::jar(db = Db)] -struct Jar(Keywords); +struct Jar(Keywords<'_>); #[salsa::interned(jar = Jar)] -struct Keywords {} +struct Keywords<'db> {} From ce88a8f9ca8f2dd6107e2bd51b6ae48461a9eace Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 25 May 2024 16:25:06 -0400 Subject: [PATCH 46/61] apply cargo fmt --- components/salsa-2022-macros/src/tracked_struct.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index c6a491202..86007b9cd 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -85,7 +85,7 @@ impl TrackedStruct { #as_debug_with_db_impl #debug_impl }) - } + } fn config_impl(&self, config_struct: &syn::ItemStruct) -> syn::ItemImpl { let config_ident = &config_struct.ident; @@ -198,7 +198,7 @@ impl TrackedStruct { } } } - + TheStructKind::Pointer(lt_db) => { if !*is_clone_field { parse_quote_spanned! { field_get_name.span() => From a7b2805b06d4693422e08bcb6d6c4f0369852724 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 26 May 2024 09:22:32 -0400 Subject: [PATCH 47/61] WIP: temporarily add expanded version of test --- .../tests/tracked_with_struct_db_expanded.rs | 997 ++++++++++++++++++ 1 file changed, 997 insertions(+) create mode 100644 salsa-2022-tests/tests/tracked_with_struct_db_expanded.rs diff --git a/salsa-2022-tests/tests/tracked_with_struct_db_expanded.rs b/salsa-2022-tests/tests/tracked_with_struct_db_expanded.rs new file mode 100644 index 000000000..b719f4eae --- /dev/null +++ b/salsa-2022-tests/tests/tracked_with_struct_db_expanded.rs @@ -0,0 +1,997 @@ +#![feature(prelude_import)] +#![feature(panic_internals)] +#![feature(fmt_helpers_for_derive)] +#![allow(warnings)] +#![feature(test)] +#![feature(derive_eq)] +#![feature(derive_clone_copy)] +#![feature(core_intrinsics)] +#![feature(structural_match)] +#![feature(coverage_attribute)] +#![feature(rustc_attrs)] +#![feature(raw_ref_op)] + +//! Test that a setting a field on a `#[salsa::input]` +//! overwrites and returns the old value. +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +use salsa::DebugWithDb; +use test_log::test; +struct Jar( + ::Ingredients, + as salsa::storage::IngredientsFor>::Ingredients, + ::Ingredients, +); +impl salsa::storage::HasIngredientsFor for Jar { + fn ingredient(&self) -> &::Ingredients { + &self.0 + } + fn ingredient_mut(&mut self) -> &mut ::Ingredients { + &mut self.0 + } +} +impl salsa::storage::HasIngredientsFor> for Jar { + fn ingredient(&self) -> & as salsa::storage::IngredientsFor>::Ingredients { + &self.1 + } + fn ingredient_mut( + &mut self, + ) -> &mut as salsa::storage::IngredientsFor>::Ingredients { + &mut self.1 + } +} +impl salsa::storage::HasIngredientsFor for Jar { + fn ingredient(&self) -> &::Ingredients { + &self.2 + } + fn ingredient_mut( + &mut self, + ) -> &mut ::Ingredients { + &mut self.2 + } +} +unsafe impl<'salsa_db> salsa::jar::Jar<'salsa_db> for Jar { + type DynDb = dyn Db + 'salsa_db; + unsafe fn init_jar(place: *mut Self, routes: &mut salsa::routes::Routes) + where + DB: salsa::storage::JarFromJars + salsa::storage::DbWithJar, + { + unsafe { + (&raw mut (*place).0) + .write(::create_ingredients(routes)); + } + unsafe { + (&raw mut (*place).1).write( + as salsa::storage::IngredientsFor>::create_ingredients(routes), + ); + } + unsafe { + (&raw mut (*place).2).write( + ::create_ingredients(routes), + ); + } + } +} +trait Db: salsa::DbWithJar {} +struct Database { + storage: salsa::Storage, +} +#[automatically_derived] +impl ::core::default::Default for Database { + #[inline] + fn default() -> Database { + Database { + storage: ::core::default::Default::default(), + } + } +} +impl salsa::database::AsSalsaDatabase for Database { + fn as_salsa_database(&self) -> &dyn salsa::Database { + self + } +} +impl salsa::storage::HasJars for Database { + type Jars = (Jar,); + fn jars(&self) -> (&Self::Jars, &salsa::Runtime) { + self.storage.jars() + } + fn jars_mut(&mut self) -> (&mut Self::Jars, &mut salsa::Runtime) { + self.storage.jars_mut() + } + fn create_jars(routes: &mut salsa::routes::Routes) -> Box { + unsafe { + salsa::plumbing::create_jars_inplace::(|jars| unsafe { + let place = &raw mut (*jars).0; + ::init_jar(place, routes); + }) + } + } +} +impl salsa::storage::HasJarsDyn for Database { + fn runtime(&self) -> &salsa::Runtime { + self.storage.runtime() + } + fn runtime_mut(&mut self) -> &mut salsa::Runtime { + self.storage.runtime_mut() + } + fn maybe_changed_after( + &self, + input: salsa::key::DependencyIndex, + revision: salsa::Revision, + ) -> bool { + let ingredient = self.storage.ingredient(input.ingredient_index()); + ingredient.maybe_changed_after(self, input, revision) + } + fn cycle_recovery_strategy( + &self, + ingredient_index: salsa::IngredientIndex, + ) -> salsa::cycle::CycleRecoveryStrategy { + let ingredient = self.storage.ingredient(ingredient_index); + ingredient.cycle_recovery_strategy() + } + fn origin( + &self, + index: salsa::DatabaseKeyIndex, + ) -> Option { + let ingredient = self.storage.ingredient(index.ingredient_index()); + ingredient.origin(index.key_index()) + } + fn mark_validated_output( + &self, + executor: salsa::DatabaseKeyIndex, + output: salsa::key::DependencyIndex, + ) { + let ingredient = self.storage.ingredient(output.ingredient_index()); + ingredient.mark_validated_output(self, executor, output.key_index()); + } + fn remove_stale_output( + &self, + executor: salsa::DatabaseKeyIndex, + stale_output: salsa::key::DependencyIndex, + ) { + let ingredient = self.storage.ingredient(stale_output.ingredient_index()); + ingredient.remove_stale_output(self, executor, stale_output.key_index()); + } + fn salsa_struct_deleted(&self, ingredient: salsa::IngredientIndex, id: salsa::Id) { + let ingredient = self.storage.ingredient(ingredient); + ingredient.salsa_struct_deleted(self, id); + } + fn fmt_index( + &self, + index: salsa::key::DependencyIndex, + fmt: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + let ingredient = self.storage.ingredient(index.ingredient_index()); + ingredient.fmt_index(index.key_index(), fmt) + } +} +impl salsa::storage::DbWithJar for Database { + fn as_jar_db<'db>(&'db self) -> &'db >::DynDb + where + 'db: 'db, + { + self as &'db >::DynDb + } +} +impl salsa::storage::HasJar for Database { + fn jar(&self) -> (&Jar, &salsa::Runtime) { + let (__jars, __runtime) = self.storage.jars(); + (&__jars.0, __runtime) + } + fn jar_mut(&mut self) -> (&mut Jar, &mut salsa::Runtime) { + let (__jars, __runtime) = self.storage.jars_mut(); + (&mut __jars.0, __runtime) + } +} +impl salsa::storage::JarFromJars for Database { + fn jar_from_jars<'db>(jars: &Self::Jars) -> &Jar { + &jars.0 + } + fn jar_from_jars_mut<'db>(jars: &mut Self::Jars) -> &mut Jar { + &mut jars.0 + } +} +impl salsa::Database for Database {} +impl Db for Database {} +struct MyInput(salsa::Id); +#[automatically_derived] +impl ::core::marker::Copy for MyInput {} +#[automatically_derived] +impl ::core::clone::Clone for MyInput { + #[inline] + fn clone(&self) -> MyInput { + let _: ::core::clone::AssertParamIsClone; + *self + } +} +#[automatically_derived] +impl ::core::marker::StructuralPartialEq for MyInput {} +#[automatically_derived] +impl ::core::cmp::PartialEq for MyInput { + #[inline] + fn eq(&self, other: &MyInput) -> bool { + self.0 == other.0 + } +} +#[automatically_derived] +impl ::core::cmp::PartialOrd for MyInput { + #[inline] + fn partial_cmp(&self, other: &MyInput) -> ::core::option::Option<::core::cmp::Ordering> { + ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0) + } +} +#[automatically_derived] +impl ::core::cmp::Eq for MyInput { + #[inline] + #[doc(hidden)] + #[coverage(off)] + fn assert_receiver_is_total_eq(&self) -> () { + let _: ::core::cmp::AssertParamIsEq; + } +} +#[automatically_derived] +impl ::core::cmp::Ord for MyInput { + #[inline] + fn cmp(&self, other: &MyInput) -> ::core::cmp::Ordering { + ::core::cmp::Ord::cmp(&self.0, &other.0) + } +} +#[automatically_derived] +impl ::core::hash::Hash for MyInput { + #[inline] + fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () { + ::core::hash::Hash::hash(&self.0, state) + } +} +#[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] +impl MyInput { + pub fn new(__db: &>::DynDb, field: String) -> Self { + let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); + let __ingredients = + >::ingredient(__jar); + let __id = __ingredients.1.new_input(__runtime); + __ingredients + .0 + .store_new(__runtime, __id, field, salsa::Durability::LOW); + __id + } + fn field<'db>(self, __db: &'db >::DynDb) -> String { + let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); + let __ingredients = + >::ingredient(__jar); + __ingredients.0.fetch(__runtime, self).clone() + } + fn set_field<'db>( + self, + __db: &'db mut >::DynDb, + ) -> salsa::setter::Setter<'db, MyInput, String> { + let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar_mut(__db); + let __ingredients = + >::ingredient_mut(__jar); + salsa::setter::Setter::new(__runtime, self, &mut __ingredients.0) + } + pub fn salsa_id(&self) -> salsa::Id { + self.0 + } +} +impl salsa::storage::IngredientsFor for MyInput { + type Jar = crate::Jar; + type Ingredients = ( + salsa::input_field::InputFieldIngredient, + salsa::input::InputIngredient, + ); + fn create_ingredients(routes: &mut salsa::routes::Routes) -> Self::Ingredients + where + DB: salsa::DbWithJar + salsa::storage::JarFromJars, + { + ( + { + let index = routes.push( + |jars| { + let jar = + >::jar_from_jars(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); + &ingredients.0 + }, + |jars| { + let jar = + >::jar_from_jars_mut(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(jar); + &mut ingredients.0 + }, + ); + salsa::input_field::InputFieldIngredient::new(index, "field") + }, + { + let index = routes.push( + |jars| { + let jar = + >::jar_from_jars(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); + &ingredients.1 + }, + |jars| { + let jar = + >::jar_from_jars_mut(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(jar); + &mut ingredients.1 + }, + ); + salsa::input::InputIngredient::new(index, "MyInput") + }, + ) + } +} +impl salsa::id::AsId for MyInput { + fn as_id(&self) -> salsa::Id { + self.0 + } +} +impl salsa::id::FromId for MyInput { + fn from_id(id: salsa::Id) -> Self { + MyInput(id) + } +} +impl ::salsa::DebugWithDb<>::DynDb> for MyInput { + fn fmt( + &self, + f: &mut ::std::fmt::Formatter<'_>, + _db: &>::DynDb, + ) -> ::std::fmt::Result { + #[allow(unused_imports)] + use ::salsa::debug::helper::Fallback; + #[allow(unused_mut)] + let mut debug_struct = &mut f.debug_struct("MyInput"); + debug_struct = debug_struct.field("[salsa id]", &self.salsa_id().as_u32()); + debug_struct = + debug_struct.field( + "field", + &::salsa::debug::helper::SalsaDebug::< + String, + >::DynDb, + >::salsa_debug( + #[allow(clippy::needless_borrow)] + &self.field(_db), + _db, + ), + ); + debug_struct.finish() + } +} +impl salsa::salsa_struct::SalsaStructInDb for MyInput +where + DB: ?Sized + salsa::DbWithJar, +{ + fn register_dependent_fn(_db: &DB, _index: salsa::routes::IngredientIndex) {} +} +impl ::std::fmt::Debug for MyInput { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + f.debug_struct("MyInput") + .field("[salsa id]", &self.salsa_id().as_u32()) + .finish() + } +} +struct __MyTrackedConfig { + _uninhabited: std::convert::Infallible, +} +impl salsa::tracked_struct::Configuration for __MyTrackedConfig { + type Fields<'db> = (MyInput, MyList<'db>); + type Revisions = [salsa::Revision; 2]; + #[allow(clippy::unused_unit)] + fn id_fields(fields: &Self::Fields<'_>) -> impl std::hash::Hash { + () + } + 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; 2] + } + unsafe fn update_fields<'db>( + current_revision_: salsa::Revision, + revisions_: &mut Self::Revisions, + old_fields_: *mut Self::Fields<'db>, + new_fields_: Self::Fields<'db>, + ) { + use salsa::update::helper::Fallback as _; + if salsa::update::helper::Dispatch::::maybe_update( + &raw mut (*old_fields_).0, + new_fields_.0, + ) { + revisions_[0] = current_revision_; + } + if salsa::update::helper::Dispatch::>::maybe_update( + &raw mut (*old_fields_).1, + new_fields_.1, + ) { + revisions_[1] = current_revision_; + } + } +} +struct MyTracked<'db>( + *const salsa::tracked_struct::ValueStruct<__MyTrackedConfig>, + std::marker::PhantomData<&'db salsa::tracked_struct::ValueStruct<__MyTrackedConfig>>, +); +#[automatically_derived] +impl<'db> ::core::marker::Copy for MyTracked<'db> {} +#[automatically_derived] +impl<'db> ::core::clone::Clone for MyTracked<'db> { + #[inline] + fn clone(&self) -> MyTracked<'db> { + let _: ::core::clone::AssertParamIsClone< + *const salsa::tracked_struct::ValueStruct<__MyTrackedConfig>, + >; + let _: ::core::clone::AssertParamIsClone< + std::marker::PhantomData<&'db salsa::tracked_struct::ValueStruct<__MyTrackedConfig>>, + >; + *self + } +} +#[automatically_derived] +impl<'db> ::core::marker::StructuralPartialEq for MyTracked<'db> {} +#[automatically_derived] +impl<'db> ::core::cmp::PartialEq for MyTracked<'db> { + #[inline] + fn eq(&self, other: &MyTracked<'db>) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} +#[automatically_derived] +impl<'db> ::core::cmp::PartialOrd for MyTracked<'db> { + #[inline] + fn partial_cmp(&self, other: &MyTracked<'db>) -> ::core::option::Option<::core::cmp::Ordering> { + match ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0) { + ::core::option::Option::Some(::core::cmp::Ordering::Equal) => { + ::core::cmp::PartialOrd::partial_cmp(&self.1, &other.1) + } + cmp => cmp, + } + } +} +#[automatically_derived] +impl<'db> ::core::cmp::Eq for MyTracked<'db> { + #[inline] + #[doc(hidden)] + #[coverage(off)] + fn assert_receiver_is_total_eq(&self) -> () { + let _: ::core::cmp::AssertParamIsEq< + *const salsa::tracked_struct::ValueStruct<__MyTrackedConfig>, + >; + let _: ::core::cmp::AssertParamIsEq< + std::marker::PhantomData<&'db salsa::tracked_struct::ValueStruct<__MyTrackedConfig>>, + >; + } +} +#[automatically_derived] +impl<'db> ::core::cmp::Ord for MyTracked<'db> { + #[inline] + fn cmp(&self, other: &MyTracked<'db>) -> ::core::cmp::Ordering { + match ::core::cmp::Ord::cmp(&self.0, &other.0) { + ::core::cmp::Ordering::Equal => ::core::cmp::Ord::cmp(&self.1, &other.1), + cmp => cmp, + } + } +} +#[automatically_derived] +impl<'db> ::core::hash::Hash for MyTracked<'db> { + #[inline] + fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () { + ::core::hash::Hash::hash(&self.0, state); + ::core::hash::Hash::hash(&self.1, state) + } +} +#[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] +impl<'db> MyTracked<'db> { + pub fn new( + __db: &'db >::DynDb, + data: MyInput, + next: MyList<'db>, + ) -> Self { + let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); + let __ingredients = + >::ingredient(__jar); + let __data = __ingredients.0.new_struct(__runtime, (data, next)); + Self(__data, std::marker::PhantomData) + } + pub fn salsa_id(&self) -> salsa::Id { + salsa::id::AsId::as_id(unsafe { &*self.0 }) + } + fn data(self, __db: &'db >::DynDb) -> MyInput { + let (_, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); + let fields = unsafe { &*self.0 }.field(__runtime, 0); + fields.0.clone() + } + fn next(self, __db: &'db >::DynDb) -> MyList<'db> { + let (_, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); + let fields = unsafe { &*self.0 }.field(__runtime, 1); + fields.1.clone() + } +} +impl<'db> salsa::storage::IngredientsFor for MyTracked<'db> { + type Jar = crate::Jar; + type Ingredients = ( + salsa::tracked_struct::TrackedStructIngredient<__MyTrackedConfig>, + [salsa::tracked_struct::TrackedFieldIngredient<__MyTrackedConfig>; 2], + ); + fn create_ingredients(routes: &mut salsa::routes::Routes) -> Self::Ingredients + where + DB: salsa::DbWithJar + salsa::storage::JarFromJars, + { + let struct_ingredient = { + let index = routes.push( + |jars| { + let jar = >::jar_from_jars(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); + &ingredients.0 + }, + |jars| { + let jar = + >::jar_from_jars_mut(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(jar); + &mut ingredients.0 + }, + ); + salsa::tracked_struct::TrackedStructIngredient::new(index, "MyTracked") + }; + let field_ingredients = [ + { + let index = routes.push( + |jars| { + let jar = + >::jar_from_jars(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); + &ingredients.1[0] + }, + |jars| { + let jar = + >::jar_from_jars_mut(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(jar); + &mut ingredients.1[0] + }, + ); + struct_ingredient.new_field_ingredient(index, 0, "data") + }, + { + let index = routes.push( + |jars| { + let jar = + >::jar_from_jars(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); + &ingredients.1[1] + }, + |jars| { + let jar = + >::jar_from_jars_mut(jars); + let ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(jar); + &mut ingredients.1[1] + }, + ); + struct_ingredient.new_field_ingredient(index, 1, "next") + }, + ]; + (struct_ingredient, field_ingredients) + } +} +impl<'db, DB> salsa::salsa_struct::SalsaStructInDb for MyTracked<'db> +where + DB: ?Sized + salsa::DbWithJar, +{ + fn register_dependent_fn(db: &DB, index: salsa::routes::IngredientIndex) { + let (jar, _) = <_ as salsa::storage::HasJar>::jar(db); + let ingredients = + >>::ingredient(jar); + ingredients.0.register_dependent_fn(index) + } +} +impl<'db, DB> salsa::tracked_struct::TrackedStructInDb for MyTracked<'db> +where + DB: ?Sized + salsa::DbWithJar, +{ + fn database_key_index(db: &DB, id: salsa::Id) -> salsa::DatabaseKeyIndex { + let (jar, _) = <_ as salsa::storage::HasJar>::jar(db); + let ingredients = + >>::ingredient(jar); + ingredients.0.database_key_index(id) + } +} +unsafe impl<'db> salsa::update::Update for MyTracked<'db> { + unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { + if unsafe { *old_pointer } != new_value { + unsafe { *old_pointer = new_value }; + true + } else { + false + } + } +} +impl<'db> salsa::id::AsId for MyTracked<'db> { + fn as_id(&self) -> salsa::Id { + salsa::id::AsId::as_id(unsafe { &*self.0 }) + } +} +unsafe impl<'db> std::marker::Send for MyTracked<'db> {} +unsafe impl<'db> std::marker::Sync for MyTracked<'db> {} +impl<'db, DB> salsa::id::LookupId<&'db DB> for MyTracked<'db> +where + DB: ?Sized + salsa::DbWithJar, +{ + fn lookup_id(id: salsa::Id, db: &'db DB) -> Self { + let (jar, runtime) = <_ as salsa::storage::HasJar>::jar(db); + let ingredients = + >>::ingredient(jar); + Self( + ingredients.0.lookup_struct(runtime, id), + std::marker::PhantomData, + ) + } +} +impl<'db> ::salsa::DebugWithDb<>::DynDb> for MyTracked<'db> { + fn fmt( + &self, + f: &mut ::std::fmt::Formatter<'_>, + _db: &>::DynDb, + ) -> ::std::fmt::Result { + #[allow(unused_imports)] + use ::salsa::debug::helper::Fallback; + #[allow(unused_mut)] + let mut debug_struct = &mut f.debug_struct("MyTracked"); + debug_struct = debug_struct.field("[salsa id]", &self.salsa_id().as_u32()); + debug_struct = debug_struct.field( + "data", + &::salsa::debug::helper::SalsaDebug::< + MyInput, + >::DynDb, + >::salsa_debug( + #[allow(clippy::needless_borrow)] + &self.data(_db), + _db, + ), + ); + debug_struct = debug_struct.field( + "next", + &::salsa::debug::helper::SalsaDebug::< + MyList<'_>, + >::DynDb, + >::salsa_debug( + #[allow(clippy::needless_borrow)] + &self.next(_db), + _db, + ), + ); + debug_struct.finish() + } +} +impl<'db> ::std::fmt::Debug for MyTracked<'db> { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + f.debug_struct("MyTracked") + .field("[salsa id]", &self.salsa_id().as_u32()) + .finish() + } +} +enum MyList<'db> { + None, + Next(MyTracked<'db>), +} +#[automatically_derived] +impl<'db> ::core::marker::StructuralPartialEq for MyList<'db> {} +#[automatically_derived] +impl<'db> ::core::cmp::PartialEq for MyList<'db> { + #[inline] + fn eq(&self, other: &MyList<'db>) -> bool { + let __self_discr = ::core::intrinsics::discriminant_value(self); + let __arg1_discr = ::core::intrinsics::discriminant_value(other); + __self_discr == __arg1_discr + && match (self, other) { + (MyList::Next(__self_0), MyList::Next(__arg1_0)) => __self_0 == __arg1_0, + _ => true, + } + } +} +#[automatically_derived] +impl<'db> ::core::cmp::Eq for MyList<'db> { + #[inline] + #[doc(hidden)] + #[coverage(off)] + fn assert_receiver_is_total_eq(&self) -> () { + let _: ::core::cmp::AssertParamIsEq>; + } +} +#[automatically_derived] +impl<'db> ::core::clone::Clone for MyList<'db> { + #[inline] + fn clone(&self) -> MyList<'db> { + match self { + MyList::None => MyList::None, + MyList::Next(__self_0) => MyList::Next(::core::clone::Clone::clone(__self_0)), + } + } +} +#[automatically_derived] +impl<'db> ::core::fmt::Debug for MyList<'db> { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + MyList::None => ::core::fmt::Formatter::write_str(f, "None"), + MyList::Next(__self_0) => { + ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Next", &__self_0) + } + } + } +} +unsafe impl<'db> salsa::update::Update for MyList<'db> { + unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { + use ::salsa::update::helper::Fallback as _; + let old_pointer = unsafe { &mut *old_pointer }; + match old_pointer { + MyList::None => { + let new_value = if let MyList::None = new_value { + () + } else { + *old_pointer = new_value; + return true; + }; + false + } + MyList::Next(__binding_0) => { + let new_value = if let MyList::Next(__binding_0) = new_value { + (__binding_0,) + } else { + *old_pointer = new_value; + return true; + }; + false + | unsafe { + salsa::update::helper::Dispatch::>::maybe_update( + __binding_0, + new_value.0, + ) + } + } + } + } +} +const _: () = { + impl<'db> ::salsa::debug::DebugWithDb<>::DynDb> for MyList<'db> { + fn fmt( + &self, + fmt: &mut std::fmt::Formatter<'_>, + db: &>::DynDb, + ) -> std::fmt::Result { + use ::salsa::debug::helper::Fallback as _; + match self { + MyList::None => { fmt.debug_tuple("None") }.finish(), + MyList::Next(ref __binding_0) => { + fmt.debug_tuple("Next") + .field(&::salsa::debug::helper::SalsaDebug::< + MyTracked<'db>, + >::DynDb, + >::salsa_debug(__binding_0, db)) + } + .finish(), + } + } + } +}; +#[allow(non_camel_case_types)] +struct create_tracked_list { + intern_map: salsa::interned::IdentityInterner, + function: salsa::function::FunctionIngredient, +} +impl salsa::function::Configuration for create_tracked_list { + type Jar = crate::Jar; + type SalsaStruct<'db> = MyInput; + type Input<'db> = MyInput; + type Value<'db> = MyTracked<'db>; + const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = + salsa::cycle::CycleRecoveryStrategy::Panic; + fn should_backdate_value(v1: &Self::Value<'_>, v2: &Self::Value<'_>) -> bool { + salsa::function::should_backdate_value(v1, v2) + } + fn execute<'db>( + __db: &'db salsa::function::DynDb<'db, Self>, + __id: salsa::Id, + ) -> Self::Value<'db> { + fn __fn<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { + let t0 = MyTracked::new(db, input, MyList::None); + let t1 = MyTracked::new(db, input, MyList::Next(t0)); + t1 + } + let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); + let __ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient(__jar); + let __key = __ingredients.intern_map.data_with_db(__id, __db).clone(); + __fn(__db, __key) + } + fn recover_from_cycle<'db>( + _db: &'db salsa::function::DynDb<'db, Self>, + _cycle: &salsa::Cycle, + _key: salsa::Id, + ) -> Self::Value<'db> { + { + #[cold] + #[track_caller] + #[inline(never)] + const fn panic_cold_explicit() -> ! { + ::core::panicking::panic_explicit() + } + panic_cold_explicit(); + } + } +} +impl salsa::interned::Configuration for create_tracked_list { + type Data<'db> = (MyInput); +} +impl salsa::storage::IngredientsFor for create_tracked_list { + type Ingredients = Self; + type Jar = crate::Jar; + fn create_ingredients(routes: &mut salsa::routes::Routes) -> Self::Ingredients + where + DB: salsa::DbWithJar + salsa::storage::JarFromJars, + { + Self { + intern_map: salsa::interned::IdentityInterner::new(), + function: { + let index = routes.push( + |jars| { + let jar = + >::jar_from_jars(jars); + let ingredients = <_ as salsa::storage::HasIngredientsFor< + Self::Ingredients, + >>::ingredient(jar); + &ingredients.function + }, + |jars| { + let jar = + >::jar_from_jars_mut(jars); + let ingredients = <_ as salsa::storage::HasIngredientsFor< + Self::Ingredients, + >>::ingredient_mut(jar); + &mut ingredients.function + }, + ); + let ingredient = + salsa::function::FunctionIngredient::new(index, "create_tracked_list"); + ingredient.set_capacity(0usize); + ingredient + }, + } + } +} +impl create_tracked_list { + #[allow(dead_code, clippy::needless_lifetimes)] + fn get<'db>(db: &'db dyn Db, input: MyInput) -> &'db MyTracked<'db> { + let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(db); + let __ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient(__jar); + let __key = __ingredients.intern_map.intern_id(__runtime, (input)); + __ingredients.function.fetch(db, __key) + } + #[allow(dead_code, clippy::needless_lifetimes)] + fn set<'db>(db: &'db mut dyn Db, input: MyInput, __value: MyTracked<'db>) { + let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar_mut(db); + let __ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(__jar); + let __key = __ingredients.intern_map.intern_id(__runtime, (input)); + __ingredients + .function + .store(__runtime, __key, __value, salsa::Durability::LOW) + } + #[allow(dead_code, clippy::needless_lifetimes)] + fn accumulated<'db, __A: salsa::accumulator::Accumulator>( + db: &'db dyn Db, + input: MyInput, + ) -> Vec<<__A as salsa::accumulator::Accumulator>::Data> + where + >::DynDb: + salsa::storage::HasJar<<__A as salsa::accumulator::Accumulator>::Jar>, + { + let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(db); + let __ingredients = + <_ as salsa::storage::HasIngredientsFor>::ingredient(__jar); + let __key = __ingredients.intern_map.intern_id(__runtime, (input)); + __ingredients.function.accumulated::<__A>(db, __key) + } +} +#[allow(clippy::needless_lifetimes)] +fn create_tracked_list<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { + Clone::clone(create_tracked_list::get(db, input)) +} +extern crate test; +#[cfg(test)] +#[rustc_test_marker = "execute"] +pub const execute: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("execute"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "salsa-2022-tests/tests/tracked_with_struct_db.rs", + start_line: 47usize, + start_col: 4usize, + end_line: 47usize, + end_col: 11usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(execute1()), + ), +}; +fn execute1() { + mod init { + pub fn init() { + { + let mut env_logger_builder = ::test_log::env_logger::builder(); + let _ = env_logger_builder.is_test(true).try_init(); + } + } + } + init::init(); + { + let mut db = Database::default(); + let input = MyInput::new(&mut db, "foo".to_string()); + let t0: MyTracked = create_tracked_list(&db, input); + let t1 = create_tracked_list(&db, input); + ::expect_test::Expect { + position: ::expect_test::Position { + file: "salsa-2022-tests/tests/tracked_with_struct_db.rs", + line: 52u32, + column: 5u32, + }, + data: r#" + MyTracked { + [salsa id]: 1, + data: MyInput { + [salsa id]: 0, + field: "foo", + }, + next: Next( + MyTracked { + [salsa id]: 0, + data: MyInput { + [salsa id]: 0, + field: "foo", + }, + next: None, + }, + ), + } + "#, + indent: true, + } + .assert_debug_eq(&t0.debug(&db)); + match (&t0, &t1) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::None, + ); + } + } + }; + } +} +#[rustc_main] +#[coverage(off)] +pub fn main() -> () { + extern crate test; + test::test_main_static(&[&execute]) +} From 81942f37e524949a191c92784c6bde4872a5d97e Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 27 May 2024 07:39:46 -0400 Subject: [PATCH 48/61] use Alloc not Box to avoid uniqueness guarantees --- components/salsa-2022/src/alloc.rs | 42 +++++++++++++++++++ components/salsa-2022/src/interned.rs | 5 ++- components/salsa-2022/src/lib.rs | 1 + .../src/tracked_struct/struct_map.rs | 13 +++--- 4 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 components/salsa-2022/src/alloc.rs diff --git a/components/salsa-2022/src/alloc.rs b/components/salsa-2022/src/alloc.rs new file mode 100644 index 000000000..633b52a7f --- /dev/null +++ b/components/salsa-2022/src/alloc.rs @@ -0,0 +1,42 @@ +use std::ptr::NonNull; + +/// A box but without the uniqueness guarantees. +pub struct Alloc { + data: NonNull, +} + +impl Alloc { + pub fn new(data: T) -> Self { + let data = Box::new(data); + let data = Box::into_raw(data); + Alloc { + data: unsafe { NonNull::new_unchecked(data) }, + } + } +} + +impl Drop for Alloc { + fn drop(&mut self) { + let data: *mut T = self.data.as_ptr(); + let data: Box = unsafe { Box::from_raw(data) }; + drop(data); + } +} + +impl std::ops::Deref for Alloc { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { self.data.as_ref() } + } +} + +impl std::ops::DerefMut for Alloc { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.data.as_mut() } + } +} + +unsafe impl Send for Alloc where T: Send {} + +unsafe impl Sync for Alloc where T: Sync {} diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index 3739c6841..987d31805 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -3,6 +3,7 @@ use std::fmt; use std::hash::Hash; use std::marker::PhantomData; +use crate::alloc::Alloc; use crate::durability::Durability; use crate::id::{AsId, LookupId}; use crate::ingredient::{fmt_index, IngredientRequiresReset}; @@ -39,7 +40,7 @@ pub struct InternedIngredient { /// Maps from an interned id to its data. /// /// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa. - value_map: FxDashMap>>, + value_map: FxDashMap>>, /// counter for the next id. counter: AtomicCell, @@ -121,7 +122,7 @@ where let value = self .value_map .entry(next_id) - .or_insert(Box::new(ValueStruct { + .or_insert(Alloc::new(ValueStruct { id: next_id, fields: internal_data, })); diff --git a/components/salsa-2022/src/lib.rs b/components/salsa-2022/src/lib.rs index c2c811d6d..f0099b532 100644 --- a/components/salsa-2022/src/lib.rs +++ b/components/salsa-2022/src/lib.rs @@ -1,4 +1,5 @@ pub mod accumulator; +mod alloc; pub mod cancelled; pub mod cycle; pub mod database; diff --git a/components/salsa-2022/src/tracked_struct/struct_map.rs b/components/salsa-2022/src/tracked_struct/struct_map.rs index 399eec27a..55b6ed207 100644 --- a/components/salsa-2022/src/tracked_struct/struct_map.rs +++ b/components/salsa-2022/src/tracked_struct/struct_map.rs @@ -7,6 +7,7 @@ use crossbeam::queue::SegQueue; use dashmap::mapref::one::RefMut; use crate::{ + alloc::Alloc, hash::{FxDashMap, FxHasher}, plumbing::transmute_lifetime, Id, Runtime, @@ -18,21 +19,21 @@ pub(crate) struct StructMap where C: Configuration, { - map: Arc>>>, + map: Arc>>>, /// 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>>, + deleted_entries: SegQueue>>, } pub(crate) struct StructMapView where C: Configuration, { - map: Arc>>>, + map: Arc>>>, } /// Return value for [`StructMap`][]'s `update` method. @@ -79,7 +80,7 @@ where pub fn insert<'db>(&'db self, runtime: &'db Runtime, value: ValueStruct) -> &ValueStruct { assert_eq!(value.created_at, runtime.current_revision()); - let boxed_value = Box::new(value); + let boxed_value = Alloc::new(value); let pointer = std::ptr::addr_of!(*boxed_value); let old_value = self.map.insert(boxed_value.id, boxed_value); @@ -165,7 +166,7 @@ where /// * If the value is not present in the map. /// * If the value has not been updated in this revision. fn get_from_map<'db>( - map: &'db FxDashMap>>, + map: &'db FxDashMap>>, runtime: &'db Runtime, id: Id, ) -> &'db ValueStruct { @@ -230,7 +231,7 @@ pub(crate) struct UpdateRef<'db, C> where C: Configuration, { - guard: RefMut<'db, Id, Box>, FxHasher>, + guard: RefMut<'db, Id, Alloc>, FxHasher>, } impl<'db, C> UpdateRef<'db, C> From 8c51f37292f3cbf5867429ad6b0812ab09963da3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 27 May 2024 07:40:40 -0400 Subject: [PATCH 49/61] Revert "WIP: temporarily add expanded version of test" This reverts commit a7b2805b06d4693422e08bcb6d6c4f0369852724. --- .../tests/tracked_with_struct_db_expanded.rs | 997 ------------------ 1 file changed, 997 deletions(-) delete mode 100644 salsa-2022-tests/tests/tracked_with_struct_db_expanded.rs diff --git a/salsa-2022-tests/tests/tracked_with_struct_db_expanded.rs b/salsa-2022-tests/tests/tracked_with_struct_db_expanded.rs deleted file mode 100644 index b719f4eae..000000000 --- a/salsa-2022-tests/tests/tracked_with_struct_db_expanded.rs +++ /dev/null @@ -1,997 +0,0 @@ -#![feature(prelude_import)] -#![feature(panic_internals)] -#![feature(fmt_helpers_for_derive)] -#![allow(warnings)] -#![feature(test)] -#![feature(derive_eq)] -#![feature(derive_clone_copy)] -#![feature(core_intrinsics)] -#![feature(structural_match)] -#![feature(coverage_attribute)] -#![feature(rustc_attrs)] -#![feature(raw_ref_op)] - -//! Test that a setting a field on a `#[salsa::input]` -//! overwrites and returns the old value. -#[prelude_import] -use std::prelude::rust_2021::*; -#[macro_use] -extern crate std; -use salsa::DebugWithDb; -use test_log::test; -struct Jar( - ::Ingredients, - as salsa::storage::IngredientsFor>::Ingredients, - ::Ingredients, -); -impl salsa::storage::HasIngredientsFor for Jar { - fn ingredient(&self) -> &::Ingredients { - &self.0 - } - fn ingredient_mut(&mut self) -> &mut ::Ingredients { - &mut self.0 - } -} -impl salsa::storage::HasIngredientsFor> for Jar { - fn ingredient(&self) -> & as salsa::storage::IngredientsFor>::Ingredients { - &self.1 - } - fn ingredient_mut( - &mut self, - ) -> &mut as salsa::storage::IngredientsFor>::Ingredients { - &mut self.1 - } -} -impl salsa::storage::HasIngredientsFor for Jar { - fn ingredient(&self) -> &::Ingredients { - &self.2 - } - fn ingredient_mut( - &mut self, - ) -> &mut ::Ingredients { - &mut self.2 - } -} -unsafe impl<'salsa_db> salsa::jar::Jar<'salsa_db> for Jar { - type DynDb = dyn Db + 'salsa_db; - unsafe fn init_jar(place: *mut Self, routes: &mut salsa::routes::Routes) - where - DB: salsa::storage::JarFromJars + salsa::storage::DbWithJar, - { - unsafe { - (&raw mut (*place).0) - .write(::create_ingredients(routes)); - } - unsafe { - (&raw mut (*place).1).write( - as salsa::storage::IngredientsFor>::create_ingredients(routes), - ); - } - unsafe { - (&raw mut (*place).2).write( - ::create_ingredients(routes), - ); - } - } -} -trait Db: salsa::DbWithJar {} -struct Database { - storage: salsa::Storage, -} -#[automatically_derived] -impl ::core::default::Default for Database { - #[inline] - fn default() -> Database { - Database { - storage: ::core::default::Default::default(), - } - } -} -impl salsa::database::AsSalsaDatabase for Database { - fn as_salsa_database(&self) -> &dyn salsa::Database { - self - } -} -impl salsa::storage::HasJars for Database { - type Jars = (Jar,); - fn jars(&self) -> (&Self::Jars, &salsa::Runtime) { - self.storage.jars() - } - fn jars_mut(&mut self) -> (&mut Self::Jars, &mut salsa::Runtime) { - self.storage.jars_mut() - } - fn create_jars(routes: &mut salsa::routes::Routes) -> Box { - unsafe { - salsa::plumbing::create_jars_inplace::(|jars| unsafe { - let place = &raw mut (*jars).0; - ::init_jar(place, routes); - }) - } - } -} -impl salsa::storage::HasJarsDyn for Database { - fn runtime(&self) -> &salsa::Runtime { - self.storage.runtime() - } - fn runtime_mut(&mut self) -> &mut salsa::Runtime { - self.storage.runtime_mut() - } - fn maybe_changed_after( - &self, - input: salsa::key::DependencyIndex, - revision: salsa::Revision, - ) -> bool { - let ingredient = self.storage.ingredient(input.ingredient_index()); - ingredient.maybe_changed_after(self, input, revision) - } - fn cycle_recovery_strategy( - &self, - ingredient_index: salsa::IngredientIndex, - ) -> salsa::cycle::CycleRecoveryStrategy { - let ingredient = self.storage.ingredient(ingredient_index); - ingredient.cycle_recovery_strategy() - } - fn origin( - &self, - index: salsa::DatabaseKeyIndex, - ) -> Option { - let ingredient = self.storage.ingredient(index.ingredient_index()); - ingredient.origin(index.key_index()) - } - fn mark_validated_output( - &self, - executor: salsa::DatabaseKeyIndex, - output: salsa::key::DependencyIndex, - ) { - let ingredient = self.storage.ingredient(output.ingredient_index()); - ingredient.mark_validated_output(self, executor, output.key_index()); - } - fn remove_stale_output( - &self, - executor: salsa::DatabaseKeyIndex, - stale_output: salsa::key::DependencyIndex, - ) { - let ingredient = self.storage.ingredient(stale_output.ingredient_index()); - ingredient.remove_stale_output(self, executor, stale_output.key_index()); - } - fn salsa_struct_deleted(&self, ingredient: salsa::IngredientIndex, id: salsa::Id) { - let ingredient = self.storage.ingredient(ingredient); - ingredient.salsa_struct_deleted(self, id); - } - fn fmt_index( - &self, - index: salsa::key::DependencyIndex, - fmt: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - let ingredient = self.storage.ingredient(index.ingredient_index()); - ingredient.fmt_index(index.key_index(), fmt) - } -} -impl salsa::storage::DbWithJar for Database { - fn as_jar_db<'db>(&'db self) -> &'db >::DynDb - where - 'db: 'db, - { - self as &'db >::DynDb - } -} -impl salsa::storage::HasJar for Database { - fn jar(&self) -> (&Jar, &salsa::Runtime) { - let (__jars, __runtime) = self.storage.jars(); - (&__jars.0, __runtime) - } - fn jar_mut(&mut self) -> (&mut Jar, &mut salsa::Runtime) { - let (__jars, __runtime) = self.storage.jars_mut(); - (&mut __jars.0, __runtime) - } -} -impl salsa::storage::JarFromJars for Database { - fn jar_from_jars<'db>(jars: &Self::Jars) -> &Jar { - &jars.0 - } - fn jar_from_jars_mut<'db>(jars: &mut Self::Jars) -> &mut Jar { - &mut jars.0 - } -} -impl salsa::Database for Database {} -impl Db for Database {} -struct MyInput(salsa::Id); -#[automatically_derived] -impl ::core::marker::Copy for MyInput {} -#[automatically_derived] -impl ::core::clone::Clone for MyInput { - #[inline] - fn clone(&self) -> MyInput { - let _: ::core::clone::AssertParamIsClone; - *self - } -} -#[automatically_derived] -impl ::core::marker::StructuralPartialEq for MyInput {} -#[automatically_derived] -impl ::core::cmp::PartialEq for MyInput { - #[inline] - fn eq(&self, other: &MyInput) -> bool { - self.0 == other.0 - } -} -#[automatically_derived] -impl ::core::cmp::PartialOrd for MyInput { - #[inline] - fn partial_cmp(&self, other: &MyInput) -> ::core::option::Option<::core::cmp::Ordering> { - ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0) - } -} -#[automatically_derived] -impl ::core::cmp::Eq for MyInput { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_receiver_is_total_eq(&self) -> () { - let _: ::core::cmp::AssertParamIsEq; - } -} -#[automatically_derived] -impl ::core::cmp::Ord for MyInput { - #[inline] - fn cmp(&self, other: &MyInput) -> ::core::cmp::Ordering { - ::core::cmp::Ord::cmp(&self.0, &other.0) - } -} -#[automatically_derived] -impl ::core::hash::Hash for MyInput { - #[inline] - fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () { - ::core::hash::Hash::hash(&self.0, state) - } -} -#[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] -impl MyInput { - pub fn new(__db: &>::DynDb, field: String) -> Self { - let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); - let __ingredients = - >::ingredient(__jar); - let __id = __ingredients.1.new_input(__runtime); - __ingredients - .0 - .store_new(__runtime, __id, field, salsa::Durability::LOW); - __id - } - fn field<'db>(self, __db: &'db >::DynDb) -> String { - let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); - let __ingredients = - >::ingredient(__jar); - __ingredients.0.fetch(__runtime, self).clone() - } - fn set_field<'db>( - self, - __db: &'db mut >::DynDb, - ) -> salsa::setter::Setter<'db, MyInput, String> { - let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar_mut(__db); - let __ingredients = - >::ingredient_mut(__jar); - salsa::setter::Setter::new(__runtime, self, &mut __ingredients.0) - } - pub fn salsa_id(&self) -> salsa::Id { - self.0 - } -} -impl salsa::storage::IngredientsFor for MyInput { - type Jar = crate::Jar; - type Ingredients = ( - salsa::input_field::InputFieldIngredient, - salsa::input::InputIngredient, - ); - fn create_ingredients(routes: &mut salsa::routes::Routes) -> Self::Ingredients - where - DB: salsa::DbWithJar + salsa::storage::JarFromJars, - { - ( - { - let index = routes.push( - |jars| { - let jar = - >::jar_from_jars(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); - &ingredients.0 - }, - |jars| { - let jar = - >::jar_from_jars_mut(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(jar); - &mut ingredients.0 - }, - ); - salsa::input_field::InputFieldIngredient::new(index, "field") - }, - { - let index = routes.push( - |jars| { - let jar = - >::jar_from_jars(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); - &ingredients.1 - }, - |jars| { - let jar = - >::jar_from_jars_mut(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(jar); - &mut ingredients.1 - }, - ); - salsa::input::InputIngredient::new(index, "MyInput") - }, - ) - } -} -impl salsa::id::AsId for MyInput { - fn as_id(&self) -> salsa::Id { - self.0 - } -} -impl salsa::id::FromId for MyInput { - fn from_id(id: salsa::Id) -> Self { - MyInput(id) - } -} -impl ::salsa::DebugWithDb<>::DynDb> for MyInput { - fn fmt( - &self, - f: &mut ::std::fmt::Formatter<'_>, - _db: &>::DynDb, - ) -> ::std::fmt::Result { - #[allow(unused_imports)] - use ::salsa::debug::helper::Fallback; - #[allow(unused_mut)] - let mut debug_struct = &mut f.debug_struct("MyInput"); - debug_struct = debug_struct.field("[salsa id]", &self.salsa_id().as_u32()); - debug_struct = - debug_struct.field( - "field", - &::salsa::debug::helper::SalsaDebug::< - String, - >::DynDb, - >::salsa_debug( - #[allow(clippy::needless_borrow)] - &self.field(_db), - _db, - ), - ); - debug_struct.finish() - } -} -impl salsa::salsa_struct::SalsaStructInDb for MyInput -where - DB: ?Sized + salsa::DbWithJar, -{ - fn register_dependent_fn(_db: &DB, _index: salsa::routes::IngredientIndex) {} -} -impl ::std::fmt::Debug for MyInput { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - f.debug_struct("MyInput") - .field("[salsa id]", &self.salsa_id().as_u32()) - .finish() - } -} -struct __MyTrackedConfig { - _uninhabited: std::convert::Infallible, -} -impl salsa::tracked_struct::Configuration for __MyTrackedConfig { - type Fields<'db> = (MyInput, MyList<'db>); - type Revisions = [salsa::Revision; 2]; - #[allow(clippy::unused_unit)] - fn id_fields(fields: &Self::Fields<'_>) -> impl std::hash::Hash { - () - } - 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; 2] - } - unsafe fn update_fields<'db>( - current_revision_: salsa::Revision, - revisions_: &mut Self::Revisions, - old_fields_: *mut Self::Fields<'db>, - new_fields_: Self::Fields<'db>, - ) { - use salsa::update::helper::Fallback as _; - if salsa::update::helper::Dispatch::::maybe_update( - &raw mut (*old_fields_).0, - new_fields_.0, - ) { - revisions_[0] = current_revision_; - } - if salsa::update::helper::Dispatch::>::maybe_update( - &raw mut (*old_fields_).1, - new_fields_.1, - ) { - revisions_[1] = current_revision_; - } - } -} -struct MyTracked<'db>( - *const salsa::tracked_struct::ValueStruct<__MyTrackedConfig>, - std::marker::PhantomData<&'db salsa::tracked_struct::ValueStruct<__MyTrackedConfig>>, -); -#[automatically_derived] -impl<'db> ::core::marker::Copy for MyTracked<'db> {} -#[automatically_derived] -impl<'db> ::core::clone::Clone for MyTracked<'db> { - #[inline] - fn clone(&self) -> MyTracked<'db> { - let _: ::core::clone::AssertParamIsClone< - *const salsa::tracked_struct::ValueStruct<__MyTrackedConfig>, - >; - let _: ::core::clone::AssertParamIsClone< - std::marker::PhantomData<&'db salsa::tracked_struct::ValueStruct<__MyTrackedConfig>>, - >; - *self - } -} -#[automatically_derived] -impl<'db> ::core::marker::StructuralPartialEq for MyTracked<'db> {} -#[automatically_derived] -impl<'db> ::core::cmp::PartialEq for MyTracked<'db> { - #[inline] - fn eq(&self, other: &MyTracked<'db>) -> bool { - self.0 == other.0 && self.1 == other.1 - } -} -#[automatically_derived] -impl<'db> ::core::cmp::PartialOrd for MyTracked<'db> { - #[inline] - fn partial_cmp(&self, other: &MyTracked<'db>) -> ::core::option::Option<::core::cmp::Ordering> { - match ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0) { - ::core::option::Option::Some(::core::cmp::Ordering::Equal) => { - ::core::cmp::PartialOrd::partial_cmp(&self.1, &other.1) - } - cmp => cmp, - } - } -} -#[automatically_derived] -impl<'db> ::core::cmp::Eq for MyTracked<'db> { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_receiver_is_total_eq(&self) -> () { - let _: ::core::cmp::AssertParamIsEq< - *const salsa::tracked_struct::ValueStruct<__MyTrackedConfig>, - >; - let _: ::core::cmp::AssertParamIsEq< - std::marker::PhantomData<&'db salsa::tracked_struct::ValueStruct<__MyTrackedConfig>>, - >; - } -} -#[automatically_derived] -impl<'db> ::core::cmp::Ord for MyTracked<'db> { - #[inline] - fn cmp(&self, other: &MyTracked<'db>) -> ::core::cmp::Ordering { - match ::core::cmp::Ord::cmp(&self.0, &other.0) { - ::core::cmp::Ordering::Equal => ::core::cmp::Ord::cmp(&self.1, &other.1), - cmp => cmp, - } - } -} -#[automatically_derived] -impl<'db> ::core::hash::Hash for MyTracked<'db> { - #[inline] - fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () { - ::core::hash::Hash::hash(&self.0, state); - ::core::hash::Hash::hash(&self.1, state) - } -} -#[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] -impl<'db> MyTracked<'db> { - pub fn new( - __db: &'db >::DynDb, - data: MyInput, - next: MyList<'db>, - ) -> Self { - let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); - let __ingredients = - >::ingredient(__jar); - let __data = __ingredients.0.new_struct(__runtime, (data, next)); - Self(__data, std::marker::PhantomData) - } - pub fn salsa_id(&self) -> salsa::Id { - salsa::id::AsId::as_id(unsafe { &*self.0 }) - } - fn data(self, __db: &'db >::DynDb) -> MyInput { - let (_, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); - let fields = unsafe { &*self.0 }.field(__runtime, 0); - fields.0.clone() - } - fn next(self, __db: &'db >::DynDb) -> MyList<'db> { - let (_, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); - let fields = unsafe { &*self.0 }.field(__runtime, 1); - fields.1.clone() - } -} -impl<'db> salsa::storage::IngredientsFor for MyTracked<'db> { - type Jar = crate::Jar; - type Ingredients = ( - salsa::tracked_struct::TrackedStructIngredient<__MyTrackedConfig>, - [salsa::tracked_struct::TrackedFieldIngredient<__MyTrackedConfig>; 2], - ); - fn create_ingredients(routes: &mut salsa::routes::Routes) -> Self::Ingredients - where - DB: salsa::DbWithJar + salsa::storage::JarFromJars, - { - let struct_ingredient = { - let index = routes.push( - |jars| { - let jar = >::jar_from_jars(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); - &ingredients.0 - }, - |jars| { - let jar = - >::jar_from_jars_mut(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(jar); - &mut ingredients.0 - }, - ); - salsa::tracked_struct::TrackedStructIngredient::new(index, "MyTracked") - }; - let field_ingredients = [ - { - let index = routes.push( - |jars| { - let jar = - >::jar_from_jars(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); - &ingredients.1[0] - }, - |jars| { - let jar = - >::jar_from_jars_mut(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(jar); - &mut ingredients.1[0] - }, - ); - struct_ingredient.new_field_ingredient(index, 0, "data") - }, - { - let index = routes.push( - |jars| { - let jar = - >::jar_from_jars(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient(jar); - &ingredients.1[1] - }, - |jars| { - let jar = - >::jar_from_jars_mut(jars); - let ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(jar); - &mut ingredients.1[1] - }, - ); - struct_ingredient.new_field_ingredient(index, 1, "next") - }, - ]; - (struct_ingredient, field_ingredients) - } -} -impl<'db, DB> salsa::salsa_struct::SalsaStructInDb for MyTracked<'db> -where - DB: ?Sized + salsa::DbWithJar, -{ - fn register_dependent_fn(db: &DB, index: salsa::routes::IngredientIndex) { - let (jar, _) = <_ as salsa::storage::HasJar>::jar(db); - let ingredients = - >>::ingredient(jar); - ingredients.0.register_dependent_fn(index) - } -} -impl<'db, DB> salsa::tracked_struct::TrackedStructInDb for MyTracked<'db> -where - DB: ?Sized + salsa::DbWithJar, -{ - fn database_key_index(db: &DB, id: salsa::Id) -> salsa::DatabaseKeyIndex { - let (jar, _) = <_ as salsa::storage::HasJar>::jar(db); - let ingredients = - >>::ingredient(jar); - ingredients.0.database_key_index(id) - } -} -unsafe impl<'db> salsa::update::Update for MyTracked<'db> { - unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { - if unsafe { *old_pointer } != new_value { - unsafe { *old_pointer = new_value }; - true - } else { - false - } - } -} -impl<'db> salsa::id::AsId for MyTracked<'db> { - fn as_id(&self) -> salsa::Id { - salsa::id::AsId::as_id(unsafe { &*self.0 }) - } -} -unsafe impl<'db> std::marker::Send for MyTracked<'db> {} -unsafe impl<'db> std::marker::Sync for MyTracked<'db> {} -impl<'db, DB> salsa::id::LookupId<&'db DB> for MyTracked<'db> -where - DB: ?Sized + salsa::DbWithJar, -{ - fn lookup_id(id: salsa::Id, db: &'db DB) -> Self { - let (jar, runtime) = <_ as salsa::storage::HasJar>::jar(db); - let ingredients = - >>::ingredient(jar); - Self( - ingredients.0.lookup_struct(runtime, id), - std::marker::PhantomData, - ) - } -} -impl<'db> ::salsa::DebugWithDb<>::DynDb> for MyTracked<'db> { - fn fmt( - &self, - f: &mut ::std::fmt::Formatter<'_>, - _db: &>::DynDb, - ) -> ::std::fmt::Result { - #[allow(unused_imports)] - use ::salsa::debug::helper::Fallback; - #[allow(unused_mut)] - let mut debug_struct = &mut f.debug_struct("MyTracked"); - debug_struct = debug_struct.field("[salsa id]", &self.salsa_id().as_u32()); - debug_struct = debug_struct.field( - "data", - &::salsa::debug::helper::SalsaDebug::< - MyInput, - >::DynDb, - >::salsa_debug( - #[allow(clippy::needless_borrow)] - &self.data(_db), - _db, - ), - ); - debug_struct = debug_struct.field( - "next", - &::salsa::debug::helper::SalsaDebug::< - MyList<'_>, - >::DynDb, - >::salsa_debug( - #[allow(clippy::needless_borrow)] - &self.next(_db), - _db, - ), - ); - debug_struct.finish() - } -} -impl<'db> ::std::fmt::Debug for MyTracked<'db> { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - f.debug_struct("MyTracked") - .field("[salsa id]", &self.salsa_id().as_u32()) - .finish() - } -} -enum MyList<'db> { - None, - Next(MyTracked<'db>), -} -#[automatically_derived] -impl<'db> ::core::marker::StructuralPartialEq for MyList<'db> {} -#[automatically_derived] -impl<'db> ::core::cmp::PartialEq for MyList<'db> { - #[inline] - fn eq(&self, other: &MyList<'db>) -> bool { - let __self_discr = ::core::intrinsics::discriminant_value(self); - let __arg1_discr = ::core::intrinsics::discriminant_value(other); - __self_discr == __arg1_discr - && match (self, other) { - (MyList::Next(__self_0), MyList::Next(__arg1_0)) => __self_0 == __arg1_0, - _ => true, - } - } -} -#[automatically_derived] -impl<'db> ::core::cmp::Eq for MyList<'db> { - #[inline] - #[doc(hidden)] - #[coverage(off)] - fn assert_receiver_is_total_eq(&self) -> () { - let _: ::core::cmp::AssertParamIsEq>; - } -} -#[automatically_derived] -impl<'db> ::core::clone::Clone for MyList<'db> { - #[inline] - fn clone(&self) -> MyList<'db> { - match self { - MyList::None => MyList::None, - MyList::Next(__self_0) => MyList::Next(::core::clone::Clone::clone(__self_0)), - } - } -} -#[automatically_derived] -impl<'db> ::core::fmt::Debug for MyList<'db> { - #[inline] - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - match self { - MyList::None => ::core::fmt::Formatter::write_str(f, "None"), - MyList::Next(__self_0) => { - ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Next", &__self_0) - } - } - } -} -unsafe impl<'db> salsa::update::Update for MyList<'db> { - unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { - use ::salsa::update::helper::Fallback as _; - let old_pointer = unsafe { &mut *old_pointer }; - match old_pointer { - MyList::None => { - let new_value = if let MyList::None = new_value { - () - } else { - *old_pointer = new_value; - return true; - }; - false - } - MyList::Next(__binding_0) => { - let new_value = if let MyList::Next(__binding_0) = new_value { - (__binding_0,) - } else { - *old_pointer = new_value; - return true; - }; - false - | unsafe { - salsa::update::helper::Dispatch::>::maybe_update( - __binding_0, - new_value.0, - ) - } - } - } - } -} -const _: () = { - impl<'db> ::salsa::debug::DebugWithDb<>::DynDb> for MyList<'db> { - fn fmt( - &self, - fmt: &mut std::fmt::Formatter<'_>, - db: &>::DynDb, - ) -> std::fmt::Result { - use ::salsa::debug::helper::Fallback as _; - match self { - MyList::None => { fmt.debug_tuple("None") }.finish(), - MyList::Next(ref __binding_0) => { - fmt.debug_tuple("Next") - .field(&::salsa::debug::helper::SalsaDebug::< - MyTracked<'db>, - >::DynDb, - >::salsa_debug(__binding_0, db)) - } - .finish(), - } - } - } -}; -#[allow(non_camel_case_types)] -struct create_tracked_list { - intern_map: salsa::interned::IdentityInterner, - function: salsa::function::FunctionIngredient, -} -impl salsa::function::Configuration for create_tracked_list { - type Jar = crate::Jar; - type SalsaStruct<'db> = MyInput; - type Input<'db> = MyInput; - type Value<'db> = MyTracked<'db>; - const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = - salsa::cycle::CycleRecoveryStrategy::Panic; - fn should_backdate_value(v1: &Self::Value<'_>, v2: &Self::Value<'_>) -> bool { - salsa::function::should_backdate_value(v1, v2) - } - fn execute<'db>( - __db: &'db salsa::function::DynDb<'db, Self>, - __id: salsa::Id, - ) -> Self::Value<'db> { - fn __fn<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { - let t0 = MyTracked::new(db, input, MyList::None); - let t1 = MyTracked::new(db, input, MyList::Next(t0)); - t1 - } - let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(__db); - let __ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient(__jar); - let __key = __ingredients.intern_map.data_with_db(__id, __db).clone(); - __fn(__db, __key) - } - fn recover_from_cycle<'db>( - _db: &'db salsa::function::DynDb<'db, Self>, - _cycle: &salsa::Cycle, - _key: salsa::Id, - ) -> Self::Value<'db> { - { - #[cold] - #[track_caller] - #[inline(never)] - const fn panic_cold_explicit() -> ! { - ::core::panicking::panic_explicit() - } - panic_cold_explicit(); - } - } -} -impl salsa::interned::Configuration for create_tracked_list { - type Data<'db> = (MyInput); -} -impl salsa::storage::IngredientsFor for create_tracked_list { - type Ingredients = Self; - type Jar = crate::Jar; - fn create_ingredients(routes: &mut salsa::routes::Routes) -> Self::Ingredients - where - DB: salsa::DbWithJar + salsa::storage::JarFromJars, - { - Self { - intern_map: salsa::interned::IdentityInterner::new(), - function: { - let index = routes.push( - |jars| { - let jar = - >::jar_from_jars(jars); - let ingredients = <_ as salsa::storage::HasIngredientsFor< - Self::Ingredients, - >>::ingredient(jar); - &ingredients.function - }, - |jars| { - let jar = - >::jar_from_jars_mut(jars); - let ingredients = <_ as salsa::storage::HasIngredientsFor< - Self::Ingredients, - >>::ingredient_mut(jar); - &mut ingredients.function - }, - ); - let ingredient = - salsa::function::FunctionIngredient::new(index, "create_tracked_list"); - ingredient.set_capacity(0usize); - ingredient - }, - } - } -} -impl create_tracked_list { - #[allow(dead_code, clippy::needless_lifetimes)] - fn get<'db>(db: &'db dyn Db, input: MyInput) -> &'db MyTracked<'db> { - let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(db); - let __ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient(__jar); - let __key = __ingredients.intern_map.intern_id(__runtime, (input)); - __ingredients.function.fetch(db, __key) - } - #[allow(dead_code, clippy::needless_lifetimes)] - fn set<'db>(db: &'db mut dyn Db, input: MyInput, __value: MyTracked<'db>) { - let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar_mut(db); - let __ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient_mut(__jar); - let __key = __ingredients.intern_map.intern_id(__runtime, (input)); - __ingredients - .function - .store(__runtime, __key, __value, salsa::Durability::LOW) - } - #[allow(dead_code, clippy::needless_lifetimes)] - fn accumulated<'db, __A: salsa::accumulator::Accumulator>( - db: &'db dyn Db, - input: MyInput, - ) -> Vec<<__A as salsa::accumulator::Accumulator>::Data> - where - >::DynDb: - salsa::storage::HasJar<<__A as salsa::accumulator::Accumulator>::Jar>, - { - let (__jar, __runtime) = <_ as salsa::storage::HasJar>::jar(db); - let __ingredients = - <_ as salsa::storage::HasIngredientsFor>::ingredient(__jar); - let __key = __ingredients.intern_map.intern_id(__runtime, (input)); - __ingredients.function.accumulated::<__A>(db, __key) - } -} -#[allow(clippy::needless_lifetimes)] -fn create_tracked_list<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> { - Clone::clone(create_tracked_list::get(db, input)) -} -extern crate test; -#[cfg(test)] -#[rustc_test_marker = "execute"] -pub const execute: test::TestDescAndFn = test::TestDescAndFn { - desc: test::TestDesc { - name: test::StaticTestName("execute"), - ignore: false, - ignore_message: ::core::option::Option::None, - source_file: "salsa-2022-tests/tests/tracked_with_struct_db.rs", - start_line: 47usize, - start_col: 4usize, - end_line: 47usize, - end_col: 11usize, - compile_fail: false, - no_run: false, - should_panic: test::ShouldPanic::No, - test_type: test::TestType::IntegrationTest, - }, - testfn: test::StaticTestFn( - #[coverage(off)] - || test::assert_test_result(execute1()), - ), -}; -fn execute1() { - mod init { - pub fn init() { - { - let mut env_logger_builder = ::test_log::env_logger::builder(); - let _ = env_logger_builder.is_test(true).try_init(); - } - } - } - init::init(); - { - let mut db = Database::default(); - let input = MyInput::new(&mut db, "foo".to_string()); - let t0: MyTracked = create_tracked_list(&db, input); - let t1 = create_tracked_list(&db, input); - ::expect_test::Expect { - position: ::expect_test::Position { - file: "salsa-2022-tests/tests/tracked_with_struct_db.rs", - line: 52u32, - column: 5u32, - }, - data: r#" - MyTracked { - [salsa id]: 1, - data: MyInput { - [salsa id]: 0, - field: "foo", - }, - next: Next( - MyTracked { - [salsa id]: 0, - data: MyInput { - [salsa id]: 0, - field: "foo", - }, - next: None, - }, - ), - } - "#, - indent: true, - } - .assert_debug_eq(&t0.debug(&db)); - match (&t0, &t1) { - (left_val, right_val) => { - if !(*left_val == *right_val) { - let kind = ::core::panicking::AssertKind::Eq; - ::core::panicking::assert_failed( - kind, - &*left_val, - &*right_val, - ::core::option::Option::None, - ); - } - } - }; - } -} -#[rustc_main] -#[coverage(off)] -pub fn main() -> () { - extern crate test; - test::test_main_static(&[&execute]) -} From 07d0ead9f40d42c9d6c737c9ad9276a5388baa0f Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 30 May 2024 01:55:13 -0400 Subject: [PATCH 50/61] return a NonNull instead of a `&'db` In old code, we converted to a `&'db` when creating a new tracked struct or interning, but this value in fact persisted beyond the end of `'db` (i.e., into the new revision). We now refactor so that we create the `Foo<'db>` from a `NonNull` instead of a `&'db T`, and then only create safe references when users access fields. This makes miri happy. --- components/salsa-2022-macros/src/interned.rs | 35 +++++++----- .../salsa-2022-macros/src/salsa_struct.rs | 8 +-- .../salsa-2022-macros/src/tracked_fn.rs | 10 ++++ .../salsa-2022-macros/src/tracked_struct.rs | 28 ++++++---- components/salsa-2022/src/alloc.rs | 26 +++++---- components/salsa-2022/src/interned.rs | 43 ++++++++++----- components/salsa-2022/src/tracked_struct.rs | 37 +++++++++---- .../src/tracked_struct/struct_map.rs | 54 +++++++++++-------- .../src/tracked_struct/tracked_field.rs | 2 + 9 files changed, 158 insertions(+), 85 deletions(-) diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index f9b749ce8..39c2d4d08 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -160,6 +160,7 @@ impl InternedStruct { data_ident: &syn::Ident, config_ident: &syn::Ident, ) -> syn::ItemImpl { + let the_ident = self.the_ident(); let lt_db = &self.named_db_lifetime(); let (_, _, _, type_generics, _) = self.the_ident_and_generics(); parse_quote_spanned!( @@ -167,6 +168,16 @@ impl InternedStruct { impl salsa::interned::Configuration for #config_ident { type Data<#lt_db> = #data_ident #type_generics; + + type Struct<#lt_db> = #the_ident < #lt_db >; + + unsafe fn struct_from_raw<'db>(ptr: std::ptr::NonNull>) -> Self::Struct<'db> { + #the_ident(ptr, std::marker::PhantomData) + } + + fn deref_struct<'db>(s: Self::Struct<'db>) -> &'db salsa::interned::ValueStruct { + unsafe { s.0.as_ref() } + } } ) } @@ -191,7 +202,7 @@ impl InternedStruct { let field_getters: Vec = self .all_fields() - .map(|field| { + .map(|field: &crate::salsa_struct::SalsaField| { let field_name = field.name(); let field_ty = field.ty(); let field_vis = field.vis(); @@ -199,13 +210,13 @@ impl InternedStruct { if field.is_clone_field() { parse_quote_spanned! { field_get_name.span() => #field_vis fn #field_get_name(self, _db: & #db_lt #db_dyn_ty) -> #field_ty { - std::clone::Clone::clone(&unsafe { &*self.0 }.data().#field_name) + std::clone::Clone::clone(&unsafe { self.0.as_ref() }.data().#field_name) } } } else { parse_quote_spanned! { field_get_name.span() => #field_vis fn #field_get_name(self, _db: & #db_lt #db_dyn_ty) -> & #db_lt #field_ty { - &unsafe { &*self.0 }.data().#field_name + &unsafe { self.0.as_ref() }.data().#field_name } } } @@ -218,18 +229,15 @@ impl InternedStruct { let constructor_name = self.constructor_name(); let new_method: syn::ImplItemFn = parse_quote_spanned! { constructor_name.span() => #vis fn #constructor_name( - db: &#db_dyn_ty, + db: &#db_lt #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< #the_ident #type_generics >>::ingredient(jar); - Self( - ingredients.intern(runtime, #data_ident { - #(#field_names,)* - __phantom: std::marker::PhantomData, - }), - std::marker::PhantomData, - ) + ingredients.intern(runtime, #data_ident { + #(#field_names,)* + __phantom: std::marker::PhantomData, + }) } }; @@ -262,6 +270,7 @@ impl InternedStruct { self.the_ident_and_generics(); let db_dyn_ty = self.db_dyn_ty(); let jar_ty = self.jar_ty(); + let db_lt = self.named_db_lifetime(); let field_getters: Vec = self .all_fields() @@ -296,7 +305,7 @@ impl InternedStruct { let constructor_name = self.constructor_name(); let new_method: syn::ImplItemFn = parse_quote_spanned! { constructor_name.span() => #vis fn #constructor_name( - db: &#db_dyn_ty, + db: & #db_lt #db_lt #db_dyn_ty, #(#field_names: #field_tys,)* ) -> Self { let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); @@ -384,7 +393,7 @@ impl InternedStruct { fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self { let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); - Self(ingredients.interned_value(id), std::marker::PhantomData) + ingredients.interned_value(id) } } }) diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index 4baccb3ed..3e2f1003a 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -349,7 +349,7 @@ impl SalsaStruct { #(#attrs)* #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #visibility struct #ident #generics ( - *const salsa::#module::ValueStruct < #config_ident >, + std::ptr::NonNull>, std::marker::PhantomData < & #lifetime salsa::#module::ValueStruct < #config_ident > > ); }) @@ -360,7 +360,9 @@ impl SalsaStruct { pub(crate) fn access_salsa_id_from_self(&self) -> syn::Expr { match self.the_struct_kind() { TheStructKind::Id => parse_quote!(self.0), - TheStructKind::Pointer(_) => parse_quote!(salsa::id::AsId::as_id(unsafe { &*self.0 })), + TheStructKind::Pointer(_) => { + parse_quote!(salsa::id::AsId::as_id(unsafe { self.0.as_ref() })) + } } } @@ -434,7 +436,7 @@ impl SalsaStruct { #where_clause { fn as_id(&self) -> salsa::Id { - salsa::id::AsId::as_id(unsafe { &*self.0 }) + salsa::id::AsId::as_id(unsafe { self.0.as_ref() }) } } diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index c1733b7f8..907ee3779 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -343,6 +343,16 @@ fn interned_configuration_impl( parse_quote!( impl salsa::interned::Configuration for #config_ty { type Data<#db_lt> = #intern_data_ty; + + type Struct<#db_lt> = & #db_lt salsa::interned::ValueStruct; + + unsafe fn struct_from_raw<'db>(ptr: std::ptr::NonNull>) -> Self::Struct<'db> { + unsafe { ptr.as_ref() } + } + + fn deref_struct<'db>(s: Self::Struct<'db>) -> &'db salsa::interned::ValueStruct { + s + } } ) } diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 86007b9cd..aa049dae3 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -92,6 +92,7 @@ impl TrackedStruct { 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(); + let the_ident = self.the_ident(); let lt_db = &self.named_db_lifetime(); // Create the function body that will update the revisions for each field. @@ -132,8 +133,19 @@ impl TrackedStruct { parse_quote! { impl salsa::tracked_struct::Configuration for #config_ident { type Fields<#lt_db> = ( #(#field_tys,)* ); + + type Struct<#lt_db> = #the_ident<#lt_db>; + type Revisions = [salsa::Revision; #arity]; + unsafe fn struct_from_raw<'db>(ptr: std::ptr::NonNull>) -> Self::Struct<'db> { + #the_ident(ptr, std::marker::PhantomData) + } + + fn deref_struct<'db>(s: Self::Struct<'db>) -> &'db salsa::tracked_struct::ValueStruct { + unsafe { s.0.as_ref() } + } + #[allow(clippy::unused_unit)] fn id_fields(fields: &Self::Fields<'_>) -> impl std::hash::Hash { ( #( &fields.#id_field_indices ),* ) @@ -205,7 +217,7 @@ impl TrackedStruct { #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> & #lt_db #field_ty { let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); - let fields = unsafe { &*self.0 }.field(__runtime, #field_index); + let fields = unsafe { self.0.as_ref() }.field(__runtime, #field_index); &fields.#field_index } } @@ -214,7 +226,7 @@ impl TrackedStruct { #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> #field_ty { let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); - let fields = unsafe { &*self.0 }.field(__runtime, #field_index); + let fields = unsafe { self.0.as_ref() }.field(__runtime, #field_index); fields.#field_index.clone() } } @@ -232,11 +244,6 @@ impl TrackedStruct { let salsa_id = self.access_salsa_id_from_self(); - let ctor = match the_kind { - TheStructKind::Id => quote!(salsa::id::FromId::from_as_id(#data)), - TheStructKind::Pointer(_) => quote!(Self(#data, std::marker::PhantomData)), - }; - let lt_db = self.maybe_elided_db_lifetime(); parse_quote! { #[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] @@ -246,11 +253,10 @@ impl TrackedStruct { { let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< Self >>::ingredient(__jar); - let #data = __ingredients.0.new_struct( + __ingredients.0.new_struct( __runtime, (#(#field_names,)*), - ); - #ctor + ) } pub fn salsa_id(&self) -> salsa::Id { @@ -354,7 +360,7 @@ impl TrackedStruct { fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self { let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); - Self(ingredients.#tracked_struct_ingredient.lookup_struct(runtime, id), std::marker::PhantomData) + ingredients.#tracked_struct_ingredient.lookup_struct(runtime, id) } } }) diff --git a/components/salsa-2022/src/alloc.rs b/components/salsa-2022/src/alloc.rs index 633b52a7f..557f429ad 100644 --- a/components/salsa-2022/src/alloc.rs +++ b/components/salsa-2022/src/alloc.rs @@ -13,30 +13,28 @@ impl Alloc { data: unsafe { NonNull::new_unchecked(data) }, } } -} -impl Drop for Alloc { - fn drop(&mut self) { - let data: *mut T = self.data.as_ptr(); - let data: Box = unsafe { Box::from_raw(data) }; - drop(data); + pub fn as_raw(&self) -> NonNull { + self.data } -} -impl std::ops::Deref for Alloc { - type Target = T; - - fn deref(&self) -> &Self::Target { + pub unsafe fn as_ref(&self) -> &T { unsafe { self.data.as_ref() } } -} -impl std::ops::DerefMut for Alloc { - fn deref_mut(&mut self) -> &mut Self::Target { + pub unsafe fn as_mut(&mut self) -> &mut T { unsafe { self.data.as_mut() } } } +impl Drop for Alloc { + fn drop(&mut self) { + let data: *mut T = self.data.as_ptr(); + let data: Box = unsafe { Box::from_raw(data) }; + drop(data); + } +} + unsafe impl Send for Alloc where T: Send {} unsafe impl Sync for Alloc where T: Sync {} diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index 987d31805..7839b64fe 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -2,6 +2,7 @@ use crossbeam::atomic::AtomicCell; use std::fmt; use std::hash::Hash; use std::marker::PhantomData; +use std::ptr::NonNull; use crate::alloc::Alloc; use crate::durability::Durability; @@ -18,8 +19,28 @@ use super::ingredient::Ingredient; use super::routes::IngredientIndex; use super::Revision; -pub trait Configuration { +pub trait Configuration: Sized { type Data<'db>: InternedData; + type Struct<'db>: Copy; + + /// Create an end-user struct from the underlying raw pointer. + /// + /// This call is an "end-step" to the tracked struct lookup/creation + /// process in a given revision: it occurs only when the struct is newly + /// created or, if a struct is being reused, after we have updated its + /// fields (or confirmed it is green and no updates are required). + /// + /// # Unsafety + /// + /// Requires that `ptr` represents a "confirmed" value in this revision, + /// which means that it will remain valid and immutable for the remainder of this + /// revision, represented by the lifetime `'db`. + unsafe fn struct_from_raw<'db>(ptr: NonNull>) -> Self::Struct<'db>; + + /// Deref the struct to yield the underlying value struct. + /// Since we are still part of the `'db` lifetime in which the struct was created, + /// this deref is safe, and the value-struct fields are immutable and verified. + fn deref_struct<'db>(s: Self::Struct<'db>) -> &'db ValueStruct; } pub trait InternedData: Sized + Eq + Hash + Clone {} @@ -83,15 +104,11 @@ where } pub fn intern_id<'db>(&'db self, runtime: &'db Runtime, data: C::Data<'db>) -> crate::Id { - self.intern(runtime, data).as_id() + C::deref_struct(self.intern(runtime, data)).as_id() } /// Intern data to a unique reference. - pub fn intern<'db>( - &'db self, - runtime: &'db Runtime, - data: C::Data<'db>, - ) -> &'db ValueStruct { + pub fn intern<'db>(&'db self, runtime: &'db Runtime, data: C::Data<'db>) -> C::Struct<'db> { runtime.report_tracked_read( DependencyIndex::for_table(self.ingredient_index), Durability::MAX, @@ -126,27 +143,27 @@ where id: next_id, fields: internal_data, })); - // SAFETY: Items are only removed from the `value_map` with an `&mut self` reference. - let value_ref = unsafe { transmute_lifetime(self, &**value) }; + let value_raw = value.as_raw(); drop(value); entry.insert(next_id); - value_ref + // SAFETY: Items are only removed from the `value_map` with an `&mut self` reference. + unsafe { C::struct_from_raw(value_raw) } } } } - pub fn interned_value<'db>(&'db self, id: Id) -> &'db ValueStruct { + pub fn interned_value<'db>(&'db self, id: Id) -> C::Struct<'db> { let r = self.value_map.get(&id).unwrap(); // SAFETY: Items are only removed from the `value_map` with an `&mut self` reference. - unsafe { transmute_lifetime(self, &**r) } + unsafe { C::struct_from_raw(r.as_raw()) } } /// Lookup the data for an interned value based on its id. /// Rarely used since end-users generally carry a struct with a pointer directly /// to the interned item. pub fn data<'db>(&'db self, id: Id) -> &'db C::Data<'db> { - self.interned_value(id).data() + C::deref_struct(self.interned_value(id)).data() } /// Variant of `data` that takes a (unnecessary) database argument. diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index b5640e413..59197899c 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -1,4 +1,4 @@ -use std::{fmt, hash::Hash}; +use std::{fmt, hash::Hash, ptr::NonNull}; use crossbeam::atomic::AtomicCell; use dashmap::mapref::entry::Entry; @@ -25,7 +25,7 @@ mod tracked_field; /// Trait that defines the key properties of a tracked struct. /// Implemented by the `#[salsa::tracked]` macro when applied /// to a struct. -pub trait Configuration { +pub trait Configuration: Sized { /// A (possibly empty) tuple of the fields for this struct. type Fields<'db>; @@ -35,6 +35,27 @@ pub trait Configuration { /// values have changed (or if the field is marked as `#[no_eq]`). type Revisions; + type Struct<'db>: Copy; + + /// Create an end-user struct from the underlying raw pointer. + /// + /// This call is an "end-step" to the tracked struct lookup/creation + /// process in a given revision: it occurs only when the struct is newly + /// created or, if a struct is being reused, after we have updated its + /// fields (or confirmed it is green and no updates are required). + /// + /// # Unsafety + /// + /// Requires that `ptr` represents a "confirmed" value in this revision, + /// which means that it will remain valid and immutable for the remainder of this + /// revision, represented by the lifetime `'db`. + unsafe fn struct_from_raw<'db>(ptr: NonNull>) -> Self::Struct<'db>; + + /// Deref the struct to yield the underlying value struct. + /// Since we are still part of the `'db` lifetime in which the struct was created, + /// this deref is safe, and the value-struct fields are immutable and verified. + fn deref_struct<'db>(s: Self::Struct<'db>) -> &'db ValueStruct; + fn id_fields(fields: &Self::Fields<'_>) -> impl Hash; /// Access the revision of a given value field. @@ -132,10 +153,6 @@ struct KeyStruct { disambiguator: Disambiguator, } -impl crate::interned::Configuration for KeyStruct { - type Data<'db> = KeyStruct; -} - // ANCHOR: ValueStruct #[derive(Debug)] pub struct ValueStruct @@ -262,7 +279,7 @@ where &'db self, runtime: &'db Runtime, fields: C::Fields<'db>, - ) -> &'db ValueStruct { + ) -> C::Struct<'db> { let data_hash = crate::hash::hash(&C::id_fields(&fields)); let (query_key, current_deps, disambiguator) = @@ -310,7 +327,7 @@ where // verified. Therefore, the durability ought not to have // changed (nor the field values, but the user could've // done something stupid, so we can't *assert* this is true). - assert!(r.durability == current_deps.durability); + assert!(C::deref_struct(r).durability == current_deps.durability); r } @@ -333,8 +350,8 @@ where if current_deps.durability < data.durability { data.revisions = C::new_revisions(current_revision); } - data.created_at = current_revision; data.durability = current_deps.durability; + data.created_at = current_revision; data_ref.freeze() } } @@ -347,7 +364,7 @@ where /// # Panics /// /// If the struct has not been created in this revision. - pub fn lookup_struct<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db ValueStruct { + pub fn lookup_struct<'db>(&'db self, runtime: &'db Runtime, id: Id) -> C::Struct<'db> { self.struct_map.get(runtime, id) } diff --git a/components/salsa-2022/src/tracked_struct/struct_map.rs b/components/salsa-2022/src/tracked_struct/struct_map.rs index 55b6ed207..57c90c747 100644 --- a/components/salsa-2022/src/tracked_struct/struct_map.rs +++ b/components/salsa-2022/src/tracked_struct/struct_map.rs @@ -50,7 +50,7 @@ where /// to this struct creation were up-to-date, and therefore the field contents /// ought not to have changed (barring user error). Returns a shared reference /// because caller cannot safely modify fields at this point. - Current(&'db ValueStruct), + Current(C::Struct<'db>), } impl StructMap @@ -77,13 +77,14 @@ where /// /// * If value with same `value.id` is already present in the map. /// * If value not created in current revision. - pub fn insert<'db>(&'db self, runtime: &'db Runtime, value: ValueStruct) -> &ValueStruct { + pub fn insert<'db>(&'db self, runtime: &'db Runtime, value: ValueStruct) -> C::Struct<'db> { assert_eq!(value.created_at, runtime.current_revision()); + let id = value.id; let boxed_value = Alloc::new(value); - let pointer = std::ptr::addr_of!(*boxed_value); + let pointer = boxed_value.as_raw(); - let old_value = self.map.insert(boxed_value.id, boxed_value); + let old_value = self.map.insert(id, boxed_value); assert!(old_value.is_none()); // ...strictly speaking we probably need to abort here // Unsafety clause: @@ -92,12 +93,16 @@ where // the pointer is to the contents of the box, which have a stable // address. // * Values are only removed or altered when we have `&mut self`. - unsafe { transmute_lifetime(self, &*pointer) } + unsafe { C::struct_from_raw(pointer) } } pub fn validate<'db>(&'db self, runtime: &'db Runtime, id: Id) { let mut data = self.map.get_mut(&id).unwrap(); + // UNSAFE: We never permit `&`-access in the current revision until data.created_at + // has been updated to the current revision (which we check below). + let data = unsafe { data.as_mut() }; + // Never update a struct twice in the same revision. let current_revision = runtime.current_revision(); assert!(data.created_at < current_revision); @@ -117,6 +122,10 @@ where // Never update a struct twice in the same revision. let current_revision = runtime.current_revision(); + // UNSAFE: We never permit `&`-access in the current revision until data.created_at + // has been updated to the current revision (which we check below). + let data_ref = unsafe { data.as_mut() }; + // Subtle: it's possible that this struct was already validated // in this revision. What can happen (e.g., in the test // `test_run_5_then_20` in `specify_tracked_fn_in_rev_1_but_not_2.rs`) @@ -140,12 +149,12 @@ where // // For this reason, we just return `None` in this case, ensuring that the calling // code cannot violate that `&`-reference. - if data.created_at == current_revision { + if data_ref.created_at == current_revision { drop(data); - return Update::Current(&Self::get_from_map(&self.map, runtime, id)); + return Update::Current(Self::get_from_map(&self.map, runtime, id)); } - data.created_at = current_revision; + data_ref.created_at = current_revision; Update::Outdated(UpdateRef { guard: data }) } @@ -155,7 +164,7 @@ where /// /// * If the value is not present in the map. /// * If the value has not been updated in this revision. - pub fn get<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db ValueStruct { + pub fn get<'db>(&'db self, runtime: &'db Runtime, id: Id) -> C::Struct<'db> { Self::get_from_map(&self.map, runtime, id) } @@ -169,14 +178,17 @@ where map: &'db FxDashMap>>, runtime: &'db Runtime, id: Id, - ) -> &'db ValueStruct { + ) -> C::Struct<'db> { let data = map.get(&id).unwrap(); - let data: &ValueStruct = &**data; + + // UNSAFE: We permit `&`-access in the current revision once data.created_at + // has been updated to the current revision (which we check below). + let data_ref: &ValueStruct = unsafe { data.as_ref() }; // Before we drop the lock, check that the value has // been updated in this revision. This is what allows us to return a `` let current_revision = runtime.current_revision(); - let created_at = data.created_at; + let created_at = data_ref.created_at; assert!( created_at == current_revision, "access to tracked struct from previous revision" @@ -187,7 +199,7 @@ where // * Value will not be updated again in this revision, // and revision will not change so long as runtime is shared // * We only remove values from the map when we have `&mut self` - unsafe { transmute_lifetime(map, data) } + unsafe { C::struct_from_raw(data.as_raw()) } } /// Remove the entry for `id` from the map. @@ -195,7 +207,8 @@ where /// NB. the data won't actually be freed until `drop_deleted_entries` is called. pub fn delete(&self, id: Id) -> Option { if let Some((_, data)) = self.map.remove(&id) { - let key = data.key; + // UNSAFE: The `key` field is immutable once `ValueStruct` is created. + let key = unsafe { data.as_ref() }.key; self.deleted_entries.push(data); Some(key) } else { @@ -219,7 +232,7 @@ where /// /// * If the value is not present in the map. /// * If the value has not been updated in this revision. - pub fn get<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db ValueStruct { + pub fn get<'db>(&'db self, runtime: &'db Runtime, id: Id) -> C::Struct<'db> { StructMap::get_from_map(&self.map, runtime, id) } } @@ -239,13 +252,12 @@ where C: Configuration, { /// Finalize this update, freezing the value for the rest of the revision. - pub fn freeze(self) -> &'db ValueStruct { + pub fn freeze(self) -> C::Struct<'db> { // Unsafety clause: // // see `get` above - let data: &ValueStruct = &*self.guard; - let dummy: &'db () = &(); - unsafe { transmute_lifetime(dummy, data) } + let data = self.guard.as_raw(); + unsafe { C::struct_from_raw(data) } } } @@ -256,7 +268,7 @@ where type Target = ValueStruct; fn deref(&self) -> &Self::Target { - &self.guard + unsafe { self.guard.as_ref() } } } @@ -265,6 +277,6 @@ where C: Configuration, { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.guard + unsafe { self.guard.as_mut() } } } diff --git a/components/salsa-2022/src/tracked_struct/tracked_field.rs b/components/salsa-2022/src/tracked_struct/tracked_field.rs index 03451fe4b..5e7bc1b43 100644 --- a/components/salsa-2022/src/tracked_struct/tracked_field.rs +++ b/components/salsa-2022/src/tracked_struct/tracked_field.rs @@ -40,6 +40,7 @@ where /// The caller is responible for selecting the appropriate element. pub fn field<'db>(&'db self, runtime: &'db Runtime, id: Id) -> &'db C::Fields<'db> { let data = self.struct_map.get(runtime, id); + let data = C::deref_struct(data); let changed_at = C::revision(&data.revisions, self.field_index); @@ -78,6 +79,7 @@ where let runtime = db.runtime(); let id = input.key_index.unwrap(); let data = self.struct_map.get(runtime, id); + let data = C::deref_struct(data); let field_changed_at = C::revision(&data.revisions, self.field_index); field_changed_at > revision } From 88b964d18dc6b05c51b0c0f5b226d96c3f5539bc Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 30 May 2024 08:33:57 -0400 Subject: [PATCH 51/61] use `const _: ()` to disable clippy lints --- components/salsa-2022-macros/src/interned.rs | 26 +++++++------ .../salsa-2022-macros/src/tracked_struct.rs | 39 ++++++++++++------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 39c2d4d08..f7f9b6836 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -76,18 +76,22 @@ impl InternedStruct { quote! { #the_struct #config_struct - #configuration_impl #data_struct - #ingredients_for_impl - #as_id_impl - #from_id_impl - #lookup_id_impl - #(#send_sync_impls)* - #named_fields_impl - #salsa_struct_in_db_impl - #as_debug_with_db_impl - #update_impl - #debug_impl + + #[allow(warnings, clippy::all)] + const _: () = { + #configuration_impl + #ingredients_for_impl + #as_id_impl + #from_id_impl + #lookup_id_impl + #(#send_sync_impls)* + #named_fields_impl + #salsa_struct_in_db_impl + #as_debug_with_db_impl + #update_impl + #debug_impl + }; }, )) } diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index aa049dae3..1349c440e 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -70,20 +70,31 @@ impl TrackedStruct { let debug_impl = self.debug_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); Ok(quote! { - #config_struct - #config_impl #the_struct - #inherent_impl - #ingredients_for_impl - #salsa_struct_in_db_impl - #tracked_struct_in_db_impl - #update_impl - #as_id_impl - #from_id_impl - #(#send_sync_impls)* - #lookup_id_impl - #as_debug_with_db_impl - #debug_impl + + // It'd be nice if this struct definition + // could be moved into the `const _: () = {}` so that it doesn't + // pollute the user's namespace. The reason it cannot is that it appears + // in the field types within `#the_struct`. This seems solveable but it + // would take another trait or indirection (e.g., replacing + // with `::Config`). + #config_struct + + #[allow(clippy::all, dead_code, warnings)] + const _: () = { + #config_impl + #inherent_impl + #ingredients_for_impl + #salsa_struct_in_db_impl + #tracked_struct_in_db_impl + #update_impl + #as_id_impl + #from_id_impl + #(#send_sync_impls)* + #lookup_id_impl + #as_debug_with_db_impl + #debug_impl + }; }) } @@ -146,7 +157,6 @@ impl TrackedStruct { unsafe { s.0.as_ref() } } - #[allow(clippy::unused_unit)] fn id_fields(fields: &Self::Fields<'_>) -> impl std::hash::Hash { ( #( &fields.#id_field_indices ),* ) } @@ -246,7 +256,6 @@ impl TrackedStruct { let lt_db = self.maybe_elided_db_lifetime(); parse_quote! { - #[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] impl #impl_generics #ident #type_generics #where_clause { pub fn #constructor_name(__db: &#lt_db #db_dyn_ty, #(#field_names: #field_tys,)*) -> Self From 0ad0be8095e9055db643bc6ceef6ba6793db01dd Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 30 May 2024 08:23:42 -0400 Subject: [PATCH 52/61] pacify the merciless clippy --- components/salsa-2022-macros/src/db_lifetime.rs | 4 ++-- components/salsa-2022-macros/src/debug.rs | 3 +-- .../salsa-2022-macros/src/debug_with_db.rs | 4 ++-- components/salsa-2022-macros/src/input.rs | 2 +- components/salsa-2022-macros/src/interned.rs | 2 +- components/salsa-2022-macros/src/salsa_struct.rs | 4 ++-- components/salsa-2022-macros/src/tracked_fn.rs | 2 +- .../salsa-2022-macros/src/tracked_struct.rs | 6 ++---- components/salsa-2022/src/function/memo.rs | 2 +- components/salsa-2022/src/interned.rs | 11 +++++------ .../salsa-2022/src/runtime/active_query.rs | 2 +- components/salsa-2022/src/tracked_struct.rs | 6 +++--- .../salsa-2022/src/tracked_struct/struct_map.rs | 1 - components/salsa-2022/src/update.rs | 16 +++++++++++++++- .../tests/interned-struct-with-lifetime.rs | 4 ++-- .../tests/preverify-struct-with-leaked-data.rs | 2 +- src/lib.rs | 12 ++++++------ src/runtime.rs | 2 +- 18 files changed, 47 insertions(+), 38 deletions(-) diff --git a/components/salsa-2022-macros/src/db_lifetime.rs b/components/salsa-2022-macros/src/db_lifetime.rs index d6bafaa08..2074d4085 100644 --- a/components/salsa-2022-macros/src/db_lifetime.rs +++ b/components/salsa-2022-macros/src/db_lifetime.rs @@ -16,7 +16,7 @@ pub(crate) fn default_db_lifetime(span: Span) -> syn::Lifetime { /// Require that either there are no generics or exactly one lifetime parameter. pub(crate) fn require_optional_db_lifetime(generics: &syn::Generics) -> syn::Result<()> { - if generics.params.len() == 0 { + if generics.params.is_empty() { return Ok(()); } @@ -27,7 +27,7 @@ pub(crate) fn require_optional_db_lifetime(generics: &syn::Generics) -> syn::Res /// Require that either there is exactly one lifetime parameter. pub(crate) fn require_db_lifetime(generics: &syn::Generics) -> syn::Result<()> { - if generics.params.len() == 0 { + if generics.params.is_empty() { return Err(syn::Error::new_spanned( generics, "this definition must have a `'db` lifetime", diff --git a/components/salsa-2022-macros/src/debug.rs b/components/salsa-2022-macros/src/debug.rs index 22ba85618..e7ec53ada 100644 --- a/components/salsa-2022-macros/src/debug.rs +++ b/components/salsa-2022-macros/src/debug.rs @@ -31,8 +31,7 @@ pub(crate) fn dump_tokens(input_name: impl ToString, tokens: TokenStream) -> Tok .unwrap() .write_all(token_string.as_bytes())?; rustfmt.wait_with_output() - }) - .and_then(|output| Ok(eprintln!("{}", String::from_utf8_lossy(&output.stdout)))) + }).map(|output| eprintln!("{}", String::from_utf8_lossy(&output.stdout))) .or_else(|_| Ok(eprintln!("{token_string}"))); } diff --git a/components/salsa-2022-macros/src/debug_with_db.rs b/components/salsa-2022-macros/src/debug_with_db.rs index 17c5c535f..c840527f8 100644 --- a/components/salsa-2022-macros/src/debug_with_db.rs +++ b/components/salsa-2022-macros/src/debug_with_db.rs @@ -62,7 +62,7 @@ pub(crate) fn debug_with_db(input: syn::DeriveInput) -> syn::Result syn::Result variant.fold( quote!(#fmt.debug_tuple(#variant_name)), |tokens, binding| { - let binding_data = binding_tokens(&binding); + let binding_data = binding_tokens(binding); quote!(#tokens . field(#binding_data)) }, ), diff --git a/components/salsa-2022-macros/src/input.rs b/components/salsa-2022-macros/src/input.rs index de273d480..e31b36821 100644 --- a/components/salsa-2022-macros/src/input.rs +++ b/components/salsa-2022-macros/src/input.rs @@ -55,7 +55,7 @@ impl InputStruct { let inherent_impl = self.input_inherent_impl(); let ingredients_for_impl = self.input_ingredients(); let as_id_impl = self.as_id_impl(); - let from_id_impl = self.from_id_impl(); + let from_id_impl = self.impl_of_from_id(); let salsa_struct_in_db_impl = self.salsa_struct_in_db_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); let debug_impl = self.debug_impl(); diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index f7f9b6836..7c0bde7d7 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -62,7 +62,7 @@ impl InternedStruct { 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 from_id_impl = self.from_id_impl(); + let from_id_impl = self.impl_of_from_id(); let lookup_id_impl = self.lookup_id_impl(); let send_sync_impls = self.send_sync_impls(); let named_fields_impl = self.inherent_impl_for_named_fields(); diff --git a/components/salsa-2022-macros/src/salsa_struct.rs b/components/salsa-2022-macros/src/salsa_struct.rs index 3e2f1003a..329813f5b 100644 --- a/components/salsa-2022-macros/src/salsa_struct.rs +++ b/components/salsa-2022-macros/src/salsa_struct.rs @@ -177,7 +177,7 @@ impl SalsaStruct { // FIXME: this should be a comma separated list but I couldn't // be bothered to remember how syn does this. let args: syn::Ident = attr.parse_args()?; - if args.to_string() == "DebugWithDb" { + if args == "DebugWithDb" { Ok(vec![Customization::DebugWithDb]) } else { Err(syn::Error::new_spanned(args, "unrecognized customization")) @@ -446,7 +446,7 @@ impl SalsaStruct { } /// Generate `impl salsa::id::AsId for Foo` - pub(crate) fn from_id_impl(&self) -> Option { + pub(crate) fn impl_of_from_id(&self) -> Option { match self.the_struct_kind() { TheStructKind::Id => { let ident = self.the_ident(); diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index 907ee3779..29744200e 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -50,7 +50,7 @@ pub(crate) fn tracked_fn( *item_fn.block = getter_fn(&args, &mut item_fn.sig, item_fn.block.span(), &config_ty)?; Ok(crate::debug::dump_tokens( - &fn_ident, + fn_ident, quote! { #fn_struct diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index 1349c440e..bdd87048d 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -16,7 +16,7 @@ pub(crate) fn tracked( let tokens = SalsaStruct::with_struct(args, struct_item, "tracked_struct") .and_then(|el| TrackedStruct(el).generate_tracked())?; - Ok(crate::debug::dump_tokens(&struct_name, tokens)) + Ok(crate::debug::dump_tokens(struct_name, tokens)) } struct TrackedStruct(SalsaStruct); @@ -65,7 +65,7 @@ impl TrackedStruct { let update_impl = self.update_impl(); let as_id_impl = self.as_id_impl(); let send_sync_impls = self.send_sync_impls(); - let from_id_impl = self.from_id_impl(); + let from_id_impl = self.impl_of_from_id(); let lookup_id_impl = self.lookup_id_impl(); let debug_impl = self.debug_impl(); let as_debug_with_db_impl = self.as_debug_with_db_impl(); @@ -250,8 +250,6 @@ impl TrackedStruct { let field_tys = self.all_field_tys(); let constructor_name = self.constructor_name(); - let data = syn::Ident::new("__data", Span::call_site()); - let salsa_id = self.access_salsa_id_from_self(); let lt_db = self.maybe_elided_db_lifetime(); diff --git a/components/salsa-2022/src/function/memo.rs b/components/salsa-2022/src/function/memo.rs index 45182b6ed..a40c21e5a 100644 --- a/components/salsa-2022/src/function/memo.rs +++ b/components/salsa-2022/src/function/memo.rs @@ -58,7 +58,7 @@ impl MemoMap { /// Removes any existing memo for the given key. #[must_use] - pub(super) fn remove<'db>(&'db self, key: Id) -> Option>>> { + pub(super) fn remove(&self, key: Id) -> Option>>> { unsafe { self.map.remove(&key).map(|o| self.to_self(o.1)) } } diff --git a/components/salsa-2022/src/interned.rs b/components/salsa-2022/src/interned.rs index 7839b64fe..2fc7c79ad 100644 --- a/components/salsa-2022/src/interned.rs +++ b/components/salsa-2022/src/interned.rs @@ -9,7 +9,6 @@ use crate::durability::Durability; use crate::id::{AsId, LookupId}; 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, Id}; @@ -30,7 +29,7 @@ pub trait Configuration: Sized { /// created or, if a struct is being reused, after we have updated its /// fields (or confirmed it is green and no updates are required). /// - /// # Unsafety + /// # Safety /// /// Requires that `ptr` represents a "confirmed" value in this revision, /// which means that it will remain valid and immutable for the remainder of this @@ -40,7 +39,7 @@ pub trait Configuration: Sized { /// Deref the struct to yield the underlying value struct. /// Since we are still part of the `'db` lifetime in which the struct was created, /// this deref is safe, and the value-struct fields are immutable and verified. - fn deref_struct<'db>(s: Self::Struct<'db>) -> &'db ValueStruct; + fn deref_struct(s: Self::Struct<'_>) -> &ValueStruct; } pub trait InternedData: Sized + Eq + Hash + Clone {} @@ -152,7 +151,7 @@ where } } - pub fn interned_value<'db>(&'db self, id: Id) -> C::Struct<'db> { + pub fn interned_value(&self, id: Id) -> C::Struct<'_> { let r = self.value_map.get(&id).unwrap(); // SAFETY: Items are only removed from the `value_map` with an `&mut self` reference. @@ -162,7 +161,7 @@ where /// Lookup the data for an interned value based on its id. /// Rarely used since end-users generally carry a struct with a pointer directly /// to the interned item. - pub fn data<'db>(&'db self, id: Id) -> &'db C::Data<'db> { + pub fn data(&self, id: Id) -> &C::Data<'_> { C::deref_struct(self.interned_value(id)).data() } @@ -284,7 +283,7 @@ impl ValueStruct where C: Configuration, { - pub fn data<'db>(&'db self) -> &'db C::Data<'db> { + pub fn data(&self) -> &C::Data<'_> { // SAFETY: The lifetime of `self` is tied to the interning ingredient; // we never remove data without an `&mut self` access to the interning ingredient. unsafe { self.to_self_ref(&self.fields) } diff --git a/components/salsa-2022/src/runtime/active_query.rs b/components/salsa-2022/src/runtime/active_query.rs index 1af86f823..4e966ca9c 100644 --- a/components/salsa-2022/src/runtime/active_query.rs +++ b/components/salsa-2022/src/runtime/active_query.rs @@ -172,7 +172,7 @@ impl ActiveQuery { pub(crate) fn take_inputs_from(&mut self, cycle_query: &ActiveQuery) { self.changed_at = cycle_query.changed_at; self.durability = cycle_query.durability; - self.input_outputs = cycle_query.input_outputs.clone(); + self.input_outputs.clone_from(&cycle_query.input_outputs); } pub(super) fn disambiguate(&mut self, hash: u64) -> Disambiguator { diff --git a/components/salsa-2022/src/tracked_struct.rs b/components/salsa-2022/src/tracked_struct.rs index 59197899c..32287eb3f 100644 --- a/components/salsa-2022/src/tracked_struct.rs +++ b/components/salsa-2022/src/tracked_struct.rs @@ -44,7 +44,7 @@ pub trait Configuration: Sized { /// created or, if a struct is being reused, after we have updated its /// fields (or confirmed it is green and no updates are required). /// - /// # Unsafety + /// # Safety /// /// Requires that `ptr` represents a "confirmed" value in this revision, /// which means that it will remain valid and immutable for the remainder of this @@ -54,7 +54,7 @@ pub trait Configuration: Sized { /// Deref the struct to yield the underlying value struct. /// Since we are still part of the `'db` lifetime in which the struct was created, /// this deref is safe, and the value-struct fields are immutable and verified. - fn deref_struct<'db>(s: Self::Struct<'db>) -> &'db ValueStruct; + fn deref_struct(s: Self::Struct<'_>) -> &ValueStruct; fn id_fields(fields: &Self::Fields<'_>) -> impl Hash; @@ -68,7 +68,7 @@ pub trait Configuration: Sized { /// Update the field data and, if the value has changed, /// the appropriate entry in the `revisions` array. /// - /// # Safety requirements and conditions + /// # Safety /// /// Requires the same conditions as the `maybe_update` /// method on [the `Update` trait](`crate::update::Update`). diff --git a/components/salsa-2022/src/tracked_struct/struct_map.rs b/components/salsa-2022/src/tracked_struct/struct_map.rs index 57c90c747..4bf982062 100644 --- a/components/salsa-2022/src/tracked_struct/struct_map.rs +++ b/components/salsa-2022/src/tracked_struct/struct_map.rs @@ -9,7 +9,6 @@ use dashmap::mapref::one::RefMut; use crate::{ alloc::Alloc, hash::{FxDashMap, FxHasher}, - plumbing::transmute_lifetime, Id, Runtime, }; diff --git a/components/salsa-2022/src/update.rs b/components/salsa-2022/src/update.rs index 5a013a86e..53b75d46b 100644 --- a/components/salsa-2022/src/update.rs +++ b/components/salsa-2022/src/update.rs @@ -23,6 +23,12 @@ pub mod helper { pub struct Dispatch(PhantomData); + impl Default for Dispatch { + fn default() -> Self { + Self::new() + } + } + impl Dispatch { pub fn new() -> Self { Dispatch(PhantomData) @@ -33,11 +39,17 @@ pub mod helper { where D: Update, { + /// # Safety + /// + /// See the `maybe_update` method in the [`Update`][] trait. pub unsafe fn maybe_update(old_pointer: *mut D, new_value: D) -> bool { unsafe { D::maybe_update(old_pointer, new_value) } } } + /// # Safety + /// + /// Impl will fulfill the postconditions of `maybe_update` pub unsafe trait Fallback { /// Same safety conditions as `Update::maybe_update` unsafe fn maybe_update(old_pointer: *mut T, new_value: T) -> bool; @@ -92,6 +104,8 @@ pub fn always_update( *old_pointer = new_value; } +/// # Safety +/// /// The `unsafe` on the trait is to assert that `maybe_update` ensures /// the properties it is intended to ensure. pub unsafe trait Update { @@ -99,7 +113,7 @@ pub unsafe trait Update { /// /// True if the value should be considered to have changed in the new revision. /// - /// # Unsafe contract + /// # Safety /// /// ## Requires /// diff --git a/salsa-2022-tests/tests/interned-struct-with-lifetime.rs b/salsa-2022-tests/tests/interned-struct-with-lifetime.rs index a8fa3e4c3..e44f498e4 100644 --- a/salsa-2022-tests/tests/interned-struct-with-lifetime.rs +++ b/salsa-2022-tests/tests/interned-struct-with-lifetime.rs @@ -23,8 +23,8 @@ struct InternedPair<'db> { #[salsa::tracked] fn intern_stuff(db: &dyn Db) -> String { - let s1 = InternedString::new(db, format!("Hello, ")); - let s2 = InternedString::new(db, format!("World, ")); + let s1 = InternedString::new(db, "Hello, ".to_string()); + let s2 = InternedString::new(db, "World, ".to_string()); let s3 = InternedPair::new(db, (s1, s2)); format!("{:?}", s3.debug(db)) } diff --git a/salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs b/salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs index 9365a0011..7d542a65f 100644 --- a/salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs +++ b/salsa-2022-tests/tests/preverify-struct-with-leaked-data.rs @@ -9,7 +9,7 @@ use salsa_2022_tests::{HasLogger, Logger}; use test_log::test; thread_local! { - static COUNTER: Cell = Cell::new(0); + static COUNTER: Cell = const { Cell::new(0) }; } #[salsa::jar(db = Db)] diff --git a/src/lib.rs b/src/lib.rs index f5d14ea18..71d2b9461 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,9 +119,9 @@ pub struct Event { impl Event { /// Returns a type that gives a user-readable debug output. /// Use like `println!("{:?}", index.debug(db))`. - pub fn debug<'me, D: ?Sized>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me + pub fn debug<'me, D>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me where - D: plumbing::DatabaseOps, + D: ?Sized + plumbing::DatabaseOps, { EventDebug { event: self, db } } @@ -201,9 +201,9 @@ pub enum EventKind { impl EventKind { /// Returns a type that gives a user-readable debug output. /// Use like `println!("{:?}", index.debug(db))`. - pub fn debug<'me, D: ?Sized>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me + pub fn debug<'me, D>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me where - D: plumbing::DatabaseOps, + D: ?Sized + plumbing::DatabaseOps, { EventKindDebug { kind: self, db } } @@ -402,9 +402,9 @@ impl DatabaseKeyIndex { /// Returns a type that gives a user-readable debug output. /// Use like `println!("{:?}", index.debug(db))`. - pub fn debug(self, db: &D) -> impl std::fmt::Debug + '_ + pub fn debug(self, db: &D) -> impl std::fmt::Debug + '_ where - D: plumbing::DatabaseOps, + D: ?Sized + plumbing::DatabaseOps, { DatabaseKeyIndexDebug { index: self, db } } diff --git a/src/runtime.rs b/src/runtime.rs index d0b616a43..73cc14884 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -628,7 +628,7 @@ impl ActiveQuery { pub(crate) fn take_inputs_from(&mut self, cycle_query: &ActiveQuery) { self.changed_at = cycle_query.changed_at; self.durability = cycle_query.durability; - self.dependencies = cycle_query.dependencies.clone(); + self.dependencies.clone_from(&cycle_query.dependencies); } } From b9ab8fcebddae4f6e1dbef239068b8cfd0ed170e Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 30 May 2024 08:46:15 -0400 Subject: [PATCH 53/61] rustfmt has opinions --- components/salsa-2022-macros/src/debug.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/salsa-2022-macros/src/debug.rs b/components/salsa-2022-macros/src/debug.rs index e7ec53ada..0563a91ee 100644 --- a/components/salsa-2022-macros/src/debug.rs +++ b/components/salsa-2022-macros/src/debug.rs @@ -31,7 +31,8 @@ pub(crate) fn dump_tokens(input_name: impl ToString, tokens: TokenStream) -> Tok .unwrap() .write_all(token_string.as_bytes())?; rustfmt.wait_with_output() - }).map(|output| eprintln!("{}", String::from_utf8_lossy(&output.stdout))) + }) + .map(|output| eprintln!("{}", String::from_utf8_lossy(&output.stdout))) .or_else(|_| Ok(eprintln!("{token_string}"))); } From ce750dadf501aa6e86cba76beac9fc5a38cd4d7f Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 30 May 2024 09:40:21 -0400 Subject: [PATCH 54/61] allow elided lifetimes in tracked fn return values --- .../salsa-2022-macros/src/configuration.rs | 6 +- .../salsa-2022-macros/src/tracked_fn.rs | 33 ++++++-- components/salsa-2022-macros/src/xform.rs | 15 +++- .../tests/elided-lifetime-in-tracked-fn.rs | 81 +++++++++++++++++++ 4 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 salsa-2022-tests/tests/elided-lifetime-in-tracked-fn.rs diff --git a/components/salsa-2022-macros/src/configuration.rs b/components/salsa-2022-macros/src/configuration.rs index 65ad7f37d..f4942f2d7 100644 --- a/components/salsa-2022-macros/src/configuration.rs +++ b/components/salsa-2022-macros/src/configuration.rs @@ -1,3 +1,5 @@ +use crate::xform::ChangeLt; + pub(crate) struct Configuration { pub(crate) db_lt: syn::Lifetime, pub(crate) jar_ty: syn::Type, @@ -88,9 +90,9 @@ pub(crate) fn panic_cycle_recovery_fn() -> syn::ImplItemFn { } } -pub(crate) fn value_ty(sig: &syn::Signature) -> syn::Type { +pub(crate) fn value_ty(db_lt: &syn::Lifetime, sig: &syn::Signature) -> syn::Type { match &sig.output { syn::ReturnType::Default => parse_quote!(()), - syn::ReturnType::Type(_, ty) => syn::Type::clone(ty), + syn::ReturnType::Type(_, ty) => ChangeLt::elided_to(db_lt).in_type(ty), } } diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index 29744200e..2af3482fb 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -6,6 +6,7 @@ use syn::{ReturnType, Token}; use crate::configuration::{self, Configuration, CycleRecoveryStrategy}; use crate::db_lifetime::{self, db_lifetime, require_optional_db_lifetime}; use crate::options::Options; +use crate::xform::ChangeLt; pub(crate) fn tracked_fn( args: proc_macro::TokenStream, @@ -340,6 +341,8 @@ fn interned_configuration_impl( (#(#arg_tys),*) ); + let intern_data_ty = ChangeLt::elided_to(db_lt).in_type(&intern_data_ty); + parse_quote!( impl salsa::interned::Configuration for #config_ty { type Data<#db_lt> = #intern_data_ty; @@ -421,7 +424,8 @@ fn fn_configuration(args: &FnArgs, item_fn: &syn::ItemFn) -> Configuration { FunctionType::SalsaStruct => salsa_struct_ty.clone(), FunctionType::RequiresInterning => parse_quote!(salsa::id::Id), }; - let value_ty = configuration::value_ty(&item_fn.sig); + let key_ty = ChangeLt::elided_to(&db_lt).in_type(&key_ty); + let value_ty = configuration::value_ty(&db_lt, &item_fn.sig); let fn_ty = item_fn.sig.ident.clone(); @@ -693,12 +697,21 @@ fn setter_fn( item_fn: &syn::ItemFn, config_ty: &syn::Type, ) -> syn::Result { + let mut setter_sig = item_fn.sig.clone(); + + require_optional_db_lifetime(&item_fn.sig.generics)?; + let db_lt = &db_lifetime(&item_fn.sig.generics); + match setter_sig.generics.lifetimes().count() { + 0 => setter_sig.generics.params.push(parse_quote!(#db_lt)), + 1 => (), + _ => panic!("unreachable -- would have generated an error earlier"), + }; + // The setter has *always* the same signature as the original: // but it takes a value arg and has no return type. let jar_ty = args.jar_ty(); let (db_var, arg_names) = fn_args(item_fn)?; - let mut setter_sig = item_fn.sig.clone(); - let value_ty = configuration::value_ty(&item_fn.sig); + let value_ty = configuration::value_ty(db_lt, &item_fn.sig); setter_sig.ident = syn::Ident::new("set", item_fn.sig.ident.span()); match &mut setter_sig.inputs[0] { // change from `&dyn ...` to `&mut dyn...` @@ -706,6 +719,7 @@ fn setter_fn( syn::FnArg::Typed(pat_ty) => match &mut *pat_ty.ty { syn::Type::Reference(ty) => { ty.mutability = Some(Token![mut](ty.and_token.span())); + ty.lifetime = Some(db_lt.clone()); } _ => unreachable!(), // early fns should have detected }, @@ -771,12 +785,21 @@ fn specify_fn( return Ok(None); } + let mut setter_sig = item_fn.sig.clone(); + + require_optional_db_lifetime(&item_fn.sig.generics)?; + let db_lt = &db_lifetime(&item_fn.sig.generics); + match setter_sig.generics.lifetimes().count() { + 0 => setter_sig.generics.params.push(parse_quote!(#db_lt)), + 1 => (), + _ => panic!("unreachable -- would have generated an error earlier"), + }; + // `specify` has the same signature as the original, // but it takes a value arg and has no return type. let jar_ty = args.jar_ty(); let (db_var, arg_names) = fn_args(item_fn)?; - let mut setter_sig = item_fn.sig.clone(); - let value_ty = configuration::value_ty(&item_fn.sig); + let value_ty = configuration::value_ty(db_lt, &item_fn.sig); setter_sig.ident = syn::Ident::new("specify", item_fn.sig.ident.span()); let value_arg = syn::Ident::new("__value", item_fn.sig.output.span()); setter_sig.inputs.push(parse_quote!(#value_arg: #value_ty)); diff --git a/components/salsa-2022-macros/src/xform.rs b/components/salsa-2022-macros/src/xform.rs index ec00d5c5f..9097d6174 100644 --- a/components/salsa-2022-macros/src/xform.rs +++ b/components/salsa-2022-macros/src/xform.rs @@ -2,21 +2,28 @@ use syn::visit_mut::VisitMut; pub(crate) struct ChangeLt<'a> { from: Option<&'a str>, - to: &'a str, + to: String, } impl<'a> ChangeLt<'a> { pub fn elided_to_static() -> Self { ChangeLt { from: Some("_"), - to: "static", + to: "static".to_string(), + } + } + + pub fn elided_to(db_lt: &syn::Lifetime) -> Self { + ChangeLt { + from: Some("_"), + to: db_lt.ident.to_string(), } } pub fn to_elided() -> Self { ChangeLt { from: None, - to: "_", + to: "_".to_string(), } } @@ -30,7 +37,7 @@ impl<'a> ChangeLt<'a> { impl syn::visit_mut::VisitMut for ChangeLt<'_> { fn visit_lifetime_mut(&mut self, i: &mut syn::Lifetime) { if self.from.map(|f| i.ident == f).unwrap_or(true) { - i.ident = syn::Ident::new(self.to, i.ident.span()); + i.ident = syn::Ident::new(&self.to, i.ident.span()); } } } diff --git a/salsa-2022-tests/tests/elided-lifetime-in-tracked-fn.rs b/salsa-2022-tests/tests/elided-lifetime-in-tracked-fn.rs new file mode 100644 index 000000000..3bae26671 --- /dev/null +++ b/salsa-2022-tests/tests/elided-lifetime-in-tracked-fn.rs @@ -0,0 +1,81 @@ +//! Test that a `tracked` fn on a `salsa::input` +//! compiles and executes successfully. + +use salsa_2022_tests::{HasLogger, Logger}; + +use expect_test::expect; +use test_log::test; + +#[salsa::jar(db = Db)] +struct Jar(MyInput, MyTracked<'_>, final_result, intermediate_result); + +trait Db: salsa::DbWithJar + HasLogger {} + +#[salsa::input(jar = Jar)] +struct MyInput { + field: u32, +} + +#[salsa::tracked(jar = Jar)] +fn final_result(db: &dyn Db, input: MyInput) -> u32 { + db.push_log(format!("final_result({:?})", input)); + intermediate_result(db, input).field(db) * 2 +} + +#[salsa::tracked(jar = Jar)] +struct MyTracked<'db> { + field: u32, +} + +#[salsa::tracked] +fn intermediate_result(db: &dyn Db, input: MyInput) -> MyTracked<'_> { + db.push_log(format!("intermediate_result({:?})", input)); + MyTracked::new(db, input.field(db) / 2) +} + +#[salsa::db(Jar)] +#[derive(Default)] +struct Database { + storage: salsa::Storage, + logger: Logger, +} + +impl salsa::Database for Database {} + +impl Db for Database {} + +impl HasLogger for Database { + fn logger(&self) -> &Logger { + &self.logger + } +} + +#[test] +fn execute() { + let mut db = Database::default(); + + let input = MyInput::new(&db, 22); + assert_eq!(final_result(&db, input), 22); + db.assert_logs(expect![[r#" + [ + "final_result(MyInput { [salsa id]: 0 })", + "intermediate_result(MyInput { [salsa id]: 0 })", + ]"#]]); + + // Intermediate result is the same, so final result does + // not need to be recomputed: + input.set_field(&mut db).to(23); + assert_eq!(final_result(&db, input), 22); + db.assert_logs(expect![[r#" + [ + "intermediate_result(MyInput { [salsa id]: 0 })", + ]"#]]); + + input.set_field(&mut db).to(24); + assert_eq!(final_result(&db, input), 24); + db.assert_logs(expect![[r#" + [ + "intermediate_result(MyInput { [salsa id]: 0 })", + "final_result(MyInput { [salsa id]: 0 })", + ]"#]]); +} From 53266837af2ee9be05bb1f913f01879e84c6e5a6 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 30 May 2024 09:46:20 -0400 Subject: [PATCH 55/61] remove "setter" function altogether This...seems dated. We have `specify` which is a more correct and principled version. Not sure what `set` was meant to be but I don't see any tests for it so...kill it. --- .../salsa-2022-macros/src/tracked_fn.rs | 59 +------------------ 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/components/salsa-2022-macros/src/tracked_fn.rs b/components/salsa-2022-macros/src/tracked_fn.rs index 2af3482fb..e74be5b52 100644 --- a/components/salsa-2022-macros/src/tracked_fn.rs +++ b/components/salsa-2022-macros/src/tracked_fn.rs @@ -1,7 +1,7 @@ use proc_macro2::{Literal, Span, TokenStream}; use syn::spanned::Spanned; use syn::visit_mut::VisitMut; -use syn::{ReturnType, Token}; +use syn::ReturnType; use crate::configuration::{self, Configuration, CycleRecoveryStrategy}; use crate::db_lifetime::{self, db_lifetime, require_optional_db_lifetime}; @@ -593,7 +593,6 @@ fn setter_impl( ) -> syn::Result { let ref_getter_fn = ref_getter_fn(args, item_fn, config_ty)?; let accumulated_fn = accumulated_fn(args, item_fn, config_ty)?; - let setter_fn = setter_fn(args, item_fn, config_ty)?; let specify_fn = specify_fn(args, item_fn, config_ty)?.map(|f| quote! { #f }); let set_lru_fn = set_lru_capacity_fn(args, config_ty)?.map(|f| quote! { #f }); @@ -602,9 +601,6 @@ fn setter_impl( #[allow(dead_code, clippy::needless_lifetimes)] #ref_getter_fn - #[allow(dead_code, clippy::needless_lifetimes)] - #setter_fn - #[allow(dead_code, clippy::needless_lifetimes)] #accumulated_fn @@ -690,59 +686,6 @@ fn ref_getter_fn( Ok(ref_getter_fn) } -/// Creates a `set` associated function that can be used to set (given an `&mut db`) -/// the value for this function for some inputs. -fn setter_fn( - args: &FnArgs, - item_fn: &syn::ItemFn, - config_ty: &syn::Type, -) -> syn::Result { - let mut setter_sig = item_fn.sig.clone(); - - require_optional_db_lifetime(&item_fn.sig.generics)?; - let db_lt = &db_lifetime(&item_fn.sig.generics); - match setter_sig.generics.lifetimes().count() { - 0 => setter_sig.generics.params.push(parse_quote!(#db_lt)), - 1 => (), - _ => panic!("unreachable -- would have generated an error earlier"), - }; - - // The setter has *always* the same signature as the original: - // but it takes a value arg and has no return type. - let jar_ty = args.jar_ty(); - let (db_var, arg_names) = fn_args(item_fn)?; - let value_ty = configuration::value_ty(db_lt, &item_fn.sig); - setter_sig.ident = syn::Ident::new("set", item_fn.sig.ident.span()); - match &mut setter_sig.inputs[0] { - // change from `&dyn ...` to `&mut dyn...` - syn::FnArg::Receiver(_) => unreachable!(), // early fns should have detected - syn::FnArg::Typed(pat_ty) => match &mut *pat_ty.ty { - syn::Type::Reference(ty) => { - ty.mutability = Some(Token![mut](ty.and_token.span())); - ty.lifetime = Some(db_lt.clone()); - } - _ => unreachable!(), // early fns should have detected - }, - } - let value_arg = syn::Ident::new("__value", item_fn.sig.output.span()); - setter_sig.inputs.push(parse_quote!(#value_arg: #value_ty)); - setter_sig.output = ReturnType::Default; - Ok(syn::ImplItemFn { - attrs: vec![], - vis: item_fn.vis.clone(), - defaultness: None, - sig: setter_sig, - block: parse_quote! { - { - let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar_mut(#db_var); - let __ingredients = <_ as salsa::storage::HasIngredientsFor<#config_ty>>::ingredient_mut(__jar); - let __key = __ingredients.intern_map.intern_id(__runtime, (#(#arg_names),*)); - __ingredients.function.store(__runtime, __key, #value_arg, salsa::Durability::LOW) - } - }, - }) -} - /// Create a `set_lru_capacity` associated function that can be used to change LRU /// capacity at runtime. /// Note that this function is only generated if the tracked function has the lru option set. From f91eeb931d93f1118aeb1e9e85fe28677a05319e Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 11 Jun 2024 05:20:04 -0400 Subject: [PATCH 56/61] remove dead code tracked structs only support `'db` lifetimes --- .../salsa-2022-macros/src/tracked_struct.rs | 101 +++++++----------- 1 file changed, 37 insertions(+), 64 deletions(-) diff --git a/components/salsa-2022-macros/src/tracked_struct.rs b/components/salsa-2022-macros/src/tracked_struct.rs index bdd87048d..5cd549dd5 100644 --- a/components/salsa-2022-macros/src/tracked_struct.rs +++ b/components/salsa-2022-macros/src/tracked_struct.rs @@ -186,11 +186,12 @@ impl TrackedStruct { fn tracked_inherent_impl(&self) -> syn::ItemImpl { let (ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); - let the_kind = &self.the_struct_kind(); + let TheStructKind::Pointer(lt_db) = self.the_struct_kind() else { + panic!("expected pointer struct"); + }; let jar_ty = self.jar_ty(); let db_dyn_ty = self.db_dyn_ty(); - let tracked_field_ingredients: Literal = self.tracked_field_ingredients_index(); let field_indices = self.all_field_indices(); let field_vises: Vec<_> = self.all_fields().map(SalsaField::vis).collect(); @@ -198,48 +199,22 @@ impl TrackedStruct { 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 = 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)| - match the_kind { - TheStructKind::Id => { - if !*is_clone_field { - parse_quote_spanned! { field_get_name.span() => - #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 #type_generics >>::ingredient(__jar); - &__ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self.0).#field_index - } - } - } else { - parse_quote_spanned! { field_get_name.span() => - #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 #type_generics >>::ingredient(__jar); - __ingredients.#tracked_field_ingredients[#field_index].field(__runtime, self.0).#field_index.clone() - } - } + if !*is_clone_field { + parse_quote_spanned! { field_get_name.span() => + #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> & #lt_db #field_ty + { + let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); + let fields = unsafe { self.0.as_ref() }.field(__runtime, #field_index); + &fields.#field_index } } - - TheStructKind::Pointer(lt_db) => { - if !*is_clone_field { - parse_quote_spanned! { field_get_name.span() => - #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> & #lt_db #field_ty - { - let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); - let fields = unsafe { self.0.as_ref() }.field(__runtime, #field_index); - &fields.#field_index - } - } - } else { - parse_quote_spanned! { field_get_name.span() => - #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> #field_ty - { - let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); - let fields = unsafe { self.0.as_ref() }.field(__runtime, #field_index); - fields.#field_index.clone() - } - } + } else { + parse_quote_spanned! { field_get_name.span() => + #field_vis fn #field_get_name(self, __db: & #lt_db #db_dyn_ty) -> #field_ty + { + let (_, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db); + let fields = unsafe { self.0.as_ref() }.field(__runtime, #field_index); + fields.#field_index.clone() } } } @@ -349,28 +324,26 @@ impl TrackedStruct { } /// Implementation of `LookupId`. - fn lookup_id_impl(&self) -> Option { - match self.the_struct_kind() { - TheStructKind::Id => None, - TheStructKind::Pointer(db_lt) => { - let (ident, parameters, _, type_generics, where_clause) = - self.the_ident_and_generics(); - let db = syn::Ident::new("DB", ident.span()); - let jar_ty = self.jar_ty(); - let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); - Some(parse_quote_spanned! { ident.span() => - impl<#db, #parameters> salsa::id::LookupId<& #db_lt #db> for #ident #type_generics - where - #db: ?Sized + salsa::DbWithJar<#jar_ty>, - #where_clause - { - fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self { - let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); - let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); - ingredients.#tracked_struct_ingredient.lookup_struct(runtime, id) - } - } - }) + fn lookup_id_impl(&self) -> syn::ItemImpl { + let TheStructKind::Pointer(db_lt) = self.the_struct_kind() else { + panic!("expected a Pointer impl") + }; + + let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics(); + let db = syn::Ident::new("DB", ident.span()); + let jar_ty = self.jar_ty(); + let tracked_struct_ingredient = self.tracked_struct_ingredient_index(); + parse_quote_spanned! { ident.span() => + impl<#db, #parameters> salsa::id::LookupId<& #db_lt #db> for #ident #type_generics + where + #db: ?Sized + salsa::DbWithJar<#jar_ty>, + #where_clause + { + fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self { + let (jar, runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); + ingredients.#tracked_struct_ingredient.lookup_struct(runtime, id) + } } } } From c02f30a4d6251a739fd58eff223136a14771731a Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 11 Jun 2024 05:24:20 -0400 Subject: [PATCH 57/61] remove dead code from interned structs --- components/salsa-2022-macros/src/interned.rs | 171 +++++-------------- 1 file changed, 39 insertions(+), 132 deletions(-) diff --git a/components/salsa-2022-macros/src/interned.rs b/components/salsa-2022-macros/src/interned.rs index 7c0bde7d7..96e764bfa 100644 --- a/components/salsa-2022-macros/src/interned.rs +++ b/components/salsa-2022-macros/src/interned.rs @@ -127,34 +127,18 @@ impl InternedStruct { let visibility = self.visibility(); let all_field_names = self.all_field_names(); let all_field_tys = self.all_field_tys(); + let db_lt = self.db_lt(); - match self.the_struct_kind() { - TheStructKind::Id => { - parse_quote_spanned! { data_ident.span() => - #[derive(Eq, PartialEq, Hash, Clone)] - #visibility struct #data_ident #impl_generics - where - #where_clause - { - #( - #all_field_names: #all_field_tys, - )* - } - } - } - TheStructKind::Pointer(db_lt) => { - parse_quote_spanned! { data_ident.span() => - #[derive(Eq, PartialEq, Hash, Clone)] - #visibility struct #data_ident #impl_generics - where - #where_clause - { - #( - #all_field_names: #all_field_tys, - )* - __phantom: std::marker::PhantomData<& #db_lt ()>, - } - } + parse_quote_spanned! { data_ident.span() => + #[derive(Eq, PartialEq, Hash, Clone)] + #visibility struct #data_ident #impl_generics + where + #where_clause + { + #( + #all_field_names: #all_field_tys, + )* + __phantom: std::marker::PhantomData<& #db_lt ()>, } } } @@ -189,15 +173,7 @@ impl InternedStruct { /// If this is an interned struct, then generate methods to access each field, /// as well as a `new` method. fn inherent_impl_for_named_fields(&self) -> syn::ItemImpl { - match self.the_struct_kind() { - TheStructKind::Id => self.inherent_impl_for_named_fields_id(), - TheStructKind::Pointer(db_lt) => self.inherent_impl_for_named_fields_lt(&db_lt), - } - } - - /// If this is an interned struct, then generate methods to access each field, - /// as well as a `new` method. - fn inherent_impl_for_named_fields_lt(&self, db_lt: &syn::Lifetime) -> syn::ItemImpl { + let db_lt = self.db_lt(); let vis: &syn::Visibility = self.visibility(); let (the_ident, _, impl_generics, type_generics, where_clause) = self.the_ident_and_generics(); @@ -266,81 +242,6 @@ impl InternedStruct { } } - /// If this is an interned struct, then generate methods to access each field, - /// as well as a `new` method. - fn inherent_impl_for_named_fields_id(&self) -> syn::ItemImpl { - let vis: &syn::Visibility = self.visibility(); - let (the_ident, _, impl_generics, type_generics, where_clause) = - self.the_ident_and_generics(); - let db_dyn_ty = self.db_dyn_ty(); - let jar_ty = self.jar_ty(); - let db_lt = self.named_db_lifetime(); - - let field_getters: Vec = self - .all_fields() - .map(|field| { - let field_name = field.name(); - let field_ty = field.ty(); - let field_vis = field.vis(); - let field_get_name = field.get_name(); - if field.is_clone_field() { - parse_quote_spanned! { field_get_name.span() => - #field_vis fn #field_get_name(self, 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< #the_ident #type_generics >>::ingredient(jar); - std::clone::Clone::clone(&ingredients.data(self.0).#field_name) - } - } - } else { - parse_quote_spanned! { field_get_name.span() => - #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< #the_ident #type_generics >>::ingredient(jar); - &ingredients.data(self.0).#field_name - } - } - } - }) - .collect(); - - let field_names = self.all_field_names(); - let field_tys = self.all_field_tys(); - let data_ident = self.data_ident(); - let constructor_name = self.constructor_name(); - let new_method: syn::ImplItemFn = parse_quote_spanned! { constructor_name.span() => - #vis fn #constructor_name( - db: & #db_lt #db_lt #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< #the_ident #type_generics >>::ingredient(jar); - salsa::id::FromId::from_as_id(ingredients.intern(runtime, #data_ident { - #(#field_names,)* - })) - } - }; - - let salsa_id = quote!( - pub fn salsa_id(&self) -> salsa::Id { - self.0 - } - ); - - parse_quote! { - #[allow(dead_code, clippy::pedantic, clippy::complexity, clippy::style)] - impl #impl_generics #the_ident #type_generics - where - #where_clause - { - #(#field_getters)* - - #new_method - - #salsa_id - } - } - } - /// Generates an impl of `salsa::storage::IngredientsFor`. /// /// For a memoized type, the only ingredient is an `InternedIngredient`. @@ -379,28 +280,34 @@ impl InternedStruct { } } - /// Implementation of `LookupId`. - fn lookup_id_impl(&self) -> Option { + fn db_lt(&self) -> syn::Lifetime { match self.the_struct_kind() { - TheStructKind::Id => None, - TheStructKind::Pointer(db_lt) => { - let (ident, parameters, _, type_generics, where_clause) = - self.the_ident_and_generics(); - let db = syn::Ident::new("DB", ident.span()); - let jar_ty = self.jar_ty(); - Some(parse_quote_spanned! { ident.span() => - impl<#db, #parameters> salsa::id::LookupId<& #db_lt #db> for #ident #type_generics - where - #db: ?Sized + salsa::DbWithJar<#jar_ty>, - #where_clause - { - fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self { - let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); - let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); - ingredients.interned_value(id) - } - } - }) + TheStructKind::Pointer(db_lt) => db_lt, + TheStructKind::Id => { + // This should be impossible due to the conditions enforced + // in `validate_interned()`. + panic!("interned structs must have `'db` lifetimes"); + } + } + } + + /// Implementation of `LookupId`. + fn lookup_id_impl(&self) -> syn::ItemImpl { + let db_lt = self.db_lt(); + let (ident, parameters, _, type_generics, where_clause) = self.the_ident_and_generics(); + let db = syn::Ident::new("DB", ident.span()); + let jar_ty = self.jar_ty(); + parse_quote_spanned! { ident.span() => + impl<#db, #parameters> salsa::id::LookupId<& #db_lt #db> for #ident #type_generics + where + #db: ?Sized + salsa::DbWithJar<#jar_ty>, + #where_clause + { + fn lookup_id(id: salsa::Id, db: & #db_lt DB) -> Self { + let (jar, _) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(db); + let ingredients = <#jar_ty as salsa::storage::HasIngredientsFor<#ident #type_generics>>::ingredient(jar); + ingredients.interned_value(id) + } } } } From af2c9737a934cdb8bd2c49326596745b9971fad5 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 11 Jun 2024 05:36:45 -0400 Subject: [PATCH 58/61] rework tutorial a bit to be more up to date --- book/src/tutorial/ir.md | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/book/src/tutorial/ir.md b/book/src/tutorial/ir.md index 6f54c3675..f9fb9839b 100644 --- a/book/src/tutorial/ir.md +++ b/book/src/tutorial/ir.md @@ -39,11 +39,12 @@ In our compiler, we have just one simple input, the `SourceProgram`, which has a ### The data lives in the database Although they are declared like other Rust structs, Salsa structs are implemented quite differently. -The values of their fields are stored in the Salsa database, and the struct itself just contains a numeric identifier. +The values of their fields are stored in the Salsa database and the struct themselves just reference it. This means that the struct instances are copy (no matter what fields they contain). Creating instances of the struct and accessing fields is done by invoking methods like `new` as well as getters and setters. -More concretely, the `#[salsa::input]` annotation will generate a struct for `SourceProgram` like this: +In the case of `#[salsa::input]`, the struct contains a `salsa::Id`, which is a non-zero integrated. +So the generated `SourceProgram` struct looks something like this: ```rust #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -82,7 +83,8 @@ In this case, the parser is going to take in the `SourceProgram` struct that we Like with an input, the fields of a tracked struct are also stored in the database. Unlike an input, those fields are immutable (they cannot be "set"), and Salsa compares them across revisions to know when they have changed. -In this case, if parsing the input produced the same `Program` result (e.g., because the only change to the input was some trailing whitespace, perhaps), +In this case, if parsing the input produced the same `Program` result +(e.g., because the only change to the input was some trailing whitespace, perhaps), then subsequent parts of the computation won't need to re-execute. (We'll revisit the role of tracked structs in reuse more in future parts of the IR.) @@ -92,6 +94,19 @@ Apart from the fields being immutable, the API for working with a tracked struct * You use a getter to read the value of a field, just like with an input (e.g., `my_func.statements(db)` to read the `statements` field). * In this case, the field is tagged as `#[return_ref]`, which means that the getter will return a `&Vec`, instead of cloning the vector. +### The `'db` lifetime + +Unlike inputs, tracked structs carry a `'db` lifetime. +This lifetime is tied to the `&db` used to create them and +ensures that, so long as you are using the struct, +the database remains immutable: +in other words, you cannot change the values of a `salsa::Input`. + +The `'db` lifetime also allows tracked structs to be implemented +using a pointer (versus the numeric id found in `salsa::input` structs). +This doesn't really effect you as a user except that it allows accessing fields from tracked structs, +which is very common, to be optimized. + ## Representing functions We will also use a tracked struct to represent each function: @@ -122,7 +137,7 @@ For more details, see the [algorithm](../reference/algorithm.md) page of the ref ## Interned structs The final kind of Salsa struct are *interned structs*. -As with input and tracked structs, the data for an interned struct is stored in the database, and you just pass around a single integer. +As with input and tracked structs, the data for an interned struct is stored in the database. Unlike those structs, if you intern the same data twice, you get back the **same integer**. A classic use of interning is for small strings like function names and variables. @@ -143,15 +158,14 @@ let f2 = FunctionId::new(&db, "my_string".to_string()); assert_eq!(f1, f2); ``` -### Interned ids are guaranteed to be consistent within a revision, but not across revisions (but you don't have to care) - -Interned ids are guaranteed not to change within a single revision, so you can intern things from all over your program and get back consistent results. -When you change the inputs, however, Salsa may opt to clear some of the interned values and choose different integers. -However, if this happens, it will also be sure to re-execute every function that interned that value, so all of them still see a consistent value, -just a different one than they saw in a previous revision. +### Interned values carry a `'db` lifetime -In other words, within a Salsa computation, you can assume that interning produces a single consistent integer, and you don't have to think about it. -If, however, you export interned identifiers outside the computation, and then change the inputs, they may no longer be valid or may refer to different values. +Like tracked structs, interned values carry a `'db` lifetime that prevents them from being used across salsa revisions. +It also permits them to be implemented using a pointer "under the hood", permitting efficient field access. +Interned values are guaranteed to be consistent within a single revision. +Across revisions, they may be cleared, reallocated, or reassigned -- but you cannot generally observe this, +since the `'db` lifetime prevents you from changing inputs (and hence creating a new revision) +while an interned value is in active use. ### Expressions and statements From bcad24c7f14da235cf31dffb03fe244f71f6e0dd Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 13 Jun 2024 08:05:00 -0400 Subject: [PATCH 59/61] add a safety comment on `Update` This was not obvious to me initially. --- components/salsa-2022/src/update.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/salsa-2022/src/update.rs b/components/salsa-2022/src/update.rs index 53b75d46b..ab7a5a18a 100644 --- a/components/salsa-2022/src/update.rs +++ b/components/salsa-2022/src/update.rs @@ -108,6 +108,13 @@ pub fn always_update( /// /// The `unsafe` on the trait is to assert that `maybe_update` ensures /// the properties it is intended to ensure. +/// +/// `Update` must NEVER be implemented for any `&'db T` value +/// (i.e., any Rust reference tied to the database). +/// This is because update methods are invoked across revisions. +/// The `'db` lifetimes are only valid within a single revision. +/// Therefore any use of that reference in a new revision will violate +/// stacked borrows. pub unsafe trait Update { /// # Returns /// From ab9aa3ac0850cf8e4fd32c393d78de99e3e9a50c Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 13 Jun 2024 08:05:16 -0400 Subject: [PATCH 60/61] WIP: start writing a safety chapter Still debating the best structure, so the contents are rather scattershot. I may have found a hole, but it's...obscure and I'm comfortable with it for the time being, though I think we want to close it eventually. --- book/src/SUMMARY.md | 1 + book/src/plumbing/db_lifetime.md | 238 +++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 book/src/plumbing/db_lifetime.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 2818279a7..c239bc4fe 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -32,6 +32,7 @@ - [Plumbing](./plumbing.md) - [Jars and ingredients](./plumbing/jars_and_ingredients.md) - [Databases and runtime](./plumbing/database_and_runtime.md) + - [The db lifetime on tracked/interned structs](./plumbing/db_lifetime.md) - [Tracked structures](./plumbing/tracked_structs.md) - [Query operations](./plumbing/query_ops.md) - [maybe changed after](./plumbing/maybe_changed_after.md) diff --git a/book/src/plumbing/db_lifetime.md b/book/src/plumbing/db_lifetime.md new file mode 100644 index 000000000..07b1d14af --- /dev/null +++ b/book/src/plumbing/db_lifetime.md @@ -0,0 +1,238 @@ +# The `'db` lifetime + +[Tracked](./tracked_structs.md) and interned structs are both declared with a `'db` lifetime. +This lifetime is linked to the `db: &DB` reference used to create them. +The `'db` lifetime has several implications: + +* It ensures that the user does not create a new salsa revision while a tracked/interned struct is in active use. Creating a new salsa revision requires modifying an input which requires an `&mut DB` reference, therefore it cannot occur during `'db`. + * The struct may not even exist in the new salsa revision so allowing access would be confusing. +* It permits the structs to be implemented using a pointer rather than a `salsa::Id`, which in turn means more efficient field access (no read locks required). + +This section discusses the unsafe code used for pointer-based access along with the reasoning behind it. To be concrete, we'll focus on tracked structs -- interned structs are very similar. + +## A note on UB + +When we say in this page "users cannot do X", we mean without Undefined Behavior (e.g., by transmuting integers around etc). + +## Proof obligations + +Here is a typical sequence of operations for a tracked struct along with the user operations that will require us to prove unsafe assertions: + +* A tracked function `f` executes in revision R0 and creates a tracked struct with `#[id]` fields `K` for the first time. + * `K` will be stored in the interning hashmap and mapped to a fresh identifier `id`. + * The identifier `id` will be used as the key in the `StructMap` and point to a freshly created allocation `alloc : Alloc`. + * A `ts: TS<'db>` is created from the raw pointer `alloc` and returned to the user. +* The value of the field `field` is accessed on the tracked struct instance `ts` by invoking the method `ts.field(db)` + * *Unsafe:* This accesses the raw pointer to `alloc`.* A new revision R1 begins. +* The tracked function `f` does not re-execute in R1. +* The value of the field `field` is accessed on the tracked struct instance `ts` by invoking the method `ts.field(db)` + * *Unsafe:* This accesses the raw pointer to `alloc`.* A new revision R2 begins. +* The tracked function `f` does reexecute in R1 and it again creates a tracked struct with key `K` and with (Some) distinct field values. + * The fields for `ts` are updated. +* The value of the field `field` is accessed on the tracked struct instance `ts` by invoking the method `ts.field(db)` + * *Unsafe:* This accesses the raw pointer to `alloc`. +* A new revision R3 begins. +* When `f` executes this time it does NOT create a tracked struct with key `K`. The tracked struct `ts` is placed in the "to be deleted" list. +* A new revision R4 begins: + * The allocation `alloc` is freed. + +As noted in the list, the core "unsafe" operation that users can perform is to access the fields of a tracked struct. +Tracked structs store a raw pointer to the `alloc`, owned by the ingredient, that contains their field data. +Accessing the fields of a tracked struct returns a `&`-reference to fields stored in that `alloc`, which means we must ensure Rust's two core constraints are satisfied for the lifetime of that reference: + +* The allocation `alloc` will not be freed (i.e., not be dropped) +* The contents of the fields will not be mutated + +As the sequence above illustrates, we have to show that those two constraints are true in a variety of circumstances: + +* newly created tracked structs +* tracked structs that were created in prior revisions and re-validated in this revision +* tracked structs whose fields were updated in this revision +* tracked structs that were *not* created in this revision + +## Definitions + +For every tracked struct `ts` we say that it has a **defining query** `f(..)`. +This refers to a particular invocation of the tracked function `f` with a particular set of arguments `..`. +This defining query is unique within a revision, meaning that `f` executes at most once with that same set of arguments. + +We say that a query has *executed in a revision R* if its function body was executed. When this occurs, all tracked structs defined (created) by that query will be recorded along with the query's result. + +We say that a query has been *validated in a revision R* if the salsa system determined that its inputs did not change and so skipped executing it. This also triggers the tracked structs defined by that query to be considered validated (in particular, we execute a function on them which updates some internal fields, as described below). + +When we talk about `ts`, we mean + +## Theorem: At the start of a new revision, all references to `ts` are within salsa's database + +After `ts` is deleted, there may be other memoized values still reference `ts`, but they must have a red input query. +**Is this true even if there are user bugs like non-deterministic functions?** +Argument: yes, because of non-forgery, those memoized values could not be accessed. +How did those memoized values obtain the `TS<'db>` value in the first place? +It must have come from a function argument (XX: what about thread-local state). +Therefore, to access the value, they would have to provide those function arguments again. +But how did they get them? + +Potential holes: + +* Thread-local APIs that let you thread `'db` values down in an "invisible" way, so that you can return them without them showing up in your arguments -- e.g. a tracked function `() -> S<'db>` that obtains its value from thread-local state. + * We might be able to sanity check against this with enough effort by defining some traits that guarantee that every lifetime tagged thing in your result *could have* come from one of your arguments, but I don't think we can prove it altogether. We either have to tell users "don't do that" or we need to have some kind of dynamic check, e.g. with a kind of versioned pointer. Note that it does require unsafe code at present but only because of the limits of our existing APIs. + * Alternatively we can do a better job cleaning up deleted stuff. This we could do. +* what about weird `Eq` implementations and the like? Do we have to make those unsafe? + +## Theorem: To access a tracked struct `ts` in revision R, the defining query `f(..)` must have either *executed* or been *validated* in the revision R. + +This is the core bit of reasoning underlying most of what follows. +The idea is that users cannot "forge" a tracked struct instance `ts`. +They must have gotten it through salsa's internal mechanisms. +This is important because salsa will provide `&`-references to fields within that remain valid during a revision. +But at the start of a new revision salsa may opt to modify those fields or even free the allocation. +This is safe because users cannot have references to `ts` at the start of a new revision. + + +### Lemma + + +We will prove it by proceeding through the revisions in the life cycle above (this can be considered a proof by induction). + +### Before `ts` is first created in R0 + +Users must have originally obtained `ts: TS<'db>` by invoking `TS::new(&db, ...)`. +This is because creating an instance of `TS` requires providing a `NonNull` pointer +to an unsafe function whose contract requires the pointer's validity. + +**FIXME:** This is not strictly true, I think the constructor is just a private tuple ctor, we should fix that. + +### During R0 + + +### + + +### Inductive case: Consider some revision R + +We start by showing some circumstances that cannot occur: + +* accessing the field of a tracked struct `ts` that was never created +* accessing the field of a tracked struct `ts` after it is freed + +### Lemma (no forgery): Users cannot forge a tracked struct + +The first observation is that users cannot "forge" an instance of a tracked struct `ts`. +They are required to produce a pointer to an `Alloc`. +This implies that every tracked struct `ts` originated in the ingredient. +The same is not true for input structs, for example, because they are created from integer identifiers and users could just make those up. + +### Lemma (within one rev): Users cannot hold a tracked struct `ts` across revisions + +The lifetime `'db` of the tracked struct `ts: TS<'db>` is created from a `db: &'db dyn Db` handle. +Beginning a new revision requires an `&mut` reference. +Therefore so long as users are actively using the value `ts` the database cannot start a new revision. + +*Check:* What if users had two databases and invoked internal methods? Maybe they could then. We may have to add some assertions. + +### Theorem: In order to get a tracked struct `ts` in revision R0, the tracked fn `f` that creates it must either *execute* or *be validated* first + +The two points above combine to + + +## Creating new values + +Each new value is stored in a `salsa::alloc::Alloc` created by `StructMap::insert`. +`Alloc` is a variant of the standard Rust `Box` that carries no uniqueness implications. +This means that every tracked struct has its own allocation. +This allocation is owned by the tracked struct ingredient +and thus stays live until the tracked struct ingredient is dropped +or until it is removed (see later for safety conditions around removal). + +## The user type uses a raw pointer + +The `#[salsa::tracked]` macro creates a user-exposed struct that looks roughly like this: + +```rust +// This struct is a wrapper around the actual fields that adds +// some revision metadata. You can think of it as a newtype'd +// version of the fields of the tracked struct. +use salsa::tracked_struct::ValueStruct; + +struct MyTrackedStruct<'db> { + value: *const ValueStruct<..>, + phantom: PhantomData<&'db ValueStruct<...>> +} +``` + +Key observations: + +* The actual pointer to the `ValueStruct` used at runtime is not a Rust reference but a raw pointer. This is needed for stacked borrows. +* A `PhantomData` is used to keep the `'db` lifetime alive. + +The reason we use a raw pointer in the struct is because instances of this struct will outlive the `'db` lifetime. Consider this example: + +```rust +let mut db = MyDatabase::default(); +let input = MyInput::new(&mut db, ...); + +// Revision 1: +let result1 = tracked_fn(&db, input); + +// Revision 2: +input.set_field(&mut db).to(...); +let result2 = tracked_fn(&db, input); +``` + +Tracked structs created by `tracked_fn` during Revision 1 +may be reused during Revision 2, but the original `&db` reference +used to create them has expired. +If we stored a true Rust reference, that would be a violation of +the stacked borrows rules. + +Instead, we store a raw pointer and, +whenever users invoke the accessor methods for particular fields, +we create a new reference to the contents: + +```rust +impl<'db> MyTrackedStruct<'db> { + fn field(self, db: &'db dyn DB) -> &'db FieldType { + ... + } +} +``` + +This reference is linked to `db` and remains valid so long as the + +## The `'db` lifetime at rest + +## Updating tracked struct fields across revisions + +### The `XX` + +## Safety lemmas + +These lemmas are used to justify the safety of the system. + +### Using `MyTracked<'db>` within some revision R always "happens after' a call to `MyTracked::new` + +Whenever a tracked struct instance `TS<'db>` is created for the first time in revision R1, +the result is a fresh allocation and hence there cannot be any +pre-existing aliases of that struct. + +`TS<'db>` will at that time be stored into the salsa database. +In later revisions, we assert that + +### `&'db T` references are never stored in the database + + +We maintain the invariant that, in any later revision R2, + +However in some later revision R2, how + +## Ways this could go wrong and how we prevent them + +### + +### Storing an `&'db T` into a field + + +### Freeing the memory while a tracked struct remains live + + +### Aliases of a tracked struct \ No newline at end of file From 1544ee97ce402e8ed93265215acfeac6079ea2f2 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sat, 15 Jun 2024 06:09:10 -0400 Subject: [PATCH 61/61] Apply suggestions from code review Co-authored-by: Micha Reiser Co-authored-by: Ryan Cumming Co-authored-by: David Barsky --- book/src/plumbing/db_lifetime.md | 2 +- book/src/reference/durability.md | 2 +- book/src/tutorial/ir.md | 8 ++++---- components/salsa-2022-macros/src/db_lifetime.rs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/book/src/plumbing/db_lifetime.md b/book/src/plumbing/db_lifetime.md index 07b1d14af..da2327bda 100644 --- a/book/src/plumbing/db_lifetime.md +++ b/book/src/plumbing/db_lifetime.md @@ -27,7 +27,7 @@ Here is a typical sequence of operations for a tracked struct along with the use * The tracked function `f` does not re-execute in R1. * The value of the field `field` is accessed on the tracked struct instance `ts` by invoking the method `ts.field(db)` * *Unsafe:* This accesses the raw pointer to `alloc`.* A new revision R2 begins. -* The tracked function `f` does reexecute in R1 and it again creates a tracked struct with key `K` and with (Some) distinct field values. +* The tracked function `f` does reexecute in R2 and it again creates a tracked struct with key `K` and with (Some) distinct field values. * The fields for `ts` are updated. * The value of the field `field` is accessed on the tracked struct instance `ts` by invoking the method `ts.field(db)` * *Unsafe:* This accesses the raw pointer to `alloc`. diff --git a/book/src/reference/durability.md b/book/src/reference/durability.md index 80c34e3b4..7ca511adf 100644 --- a/book/src/reference/durability.md +++ b/book/src/reference/durability.md @@ -1,7 +1,7 @@ # Durability "Durability" is an optimization that can greatly improve the performance of your salsa programs. -Durability specifies the probably that an input's value will change. +Durability specifies the probability that an input's value will change. The default is "low durability". But when you set the value of an input, you can manually specify a higher durability, typically `Durability::HIGH`. diff --git a/book/src/tutorial/ir.md b/book/src/tutorial/ir.md index f9fb9839b..0105535af 100644 --- a/book/src/tutorial/ir.md +++ b/book/src/tutorial/ir.md @@ -43,8 +43,8 @@ The values of their fields are stored in the Salsa database and the struct thems This means that the struct instances are copy (no matter what fields they contain). Creating instances of the struct and accessing fields is done by invoking methods like `new` as well as getters and setters. -In the case of `#[salsa::input]`, the struct contains a `salsa::Id`, which is a non-zero integrated. -So the generated `SourceProgram` struct looks something like this: +In the case of `#[salsa::input]`, the struct contains a `salsa::Id`, which is a non-zero integer. +Therefore, the generated `SourceProgram` struct looks something like this: ```rust #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -104,8 +104,8 @@ in other words, you cannot change the values of a `salsa::Input`. The `'db` lifetime also allows tracked structs to be implemented using a pointer (versus the numeric id found in `salsa::input` structs). -This doesn't really effect you as a user except that it allows accessing fields from tracked structs, -which is very common, to be optimized. +This doesn't really effect you as a user except that it allows accessing fields from tracked structs— +a very common operation—to be optimized. ## Representing functions diff --git a/components/salsa-2022-macros/src/db_lifetime.rs b/components/salsa-2022-macros/src/db_lifetime.rs index 2074d4085..ebb75120c 100644 --- a/components/salsa-2022-macros/src/db_lifetime.rs +++ b/components/salsa-2022-macros/src/db_lifetime.rs @@ -4,7 +4,7 @@ use proc_macro2::Span; use syn::spanned::Spanned; -/// Normally we try to use whatever lifetime parameter the use gave us +/// Normally we try to use whatever lifetime parameter the user gave us /// to represent `'db`; but if they didn't give us one, we need to use a default /// name. We choose `'db`. pub(crate) fn default_db_lifetime(span: Span) -> syn::Lifetime { @@ -51,7 +51,7 @@ pub(crate) fn require_db_lifetime(generics: &syn::Generics) -> syn::Result<()> { Ok(()) } -/// Return the `'db` lifetime given be the user, or a default. +/// Return the `'db` lifetime given by the user, or a default. /// The generics ought to have been checked with `require_db_lifetime` already. pub(crate) fn db_lifetime(generics: &syn::Generics) -> syn::Lifetime { if let Some(lt) = generics.lifetimes().next() {