From dba66f1a37acca014c2402f231ed5b361bd7d8fe Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 18 Jul 2025 00:55:50 -0400 Subject: [PATCH] Use `inventory` for static ingredient registration (#934) * use `inventory` for static ingredient registration * remove unnecessary synchronization from memo tables * use global ingredient caches for database-independent ingredients * add manual ingredient registration API * remove static ingredient index optimization when manual registration is in use * fix atomic imports * simplify ingredient caches --- .github/workflows/test.yml | 2 + Cargo.toml | 7 +- .../src/setup_accumulator_impl.rs | 13 +- .../src/setup_input_struct.rs | 17 +- .../src/setup_interned_struct.rs | 15 +- .../salsa-macro-rules/src/setup_tracked_fn.rs | 68 ++-- .../src/setup_tracked_struct.rs | 17 +- components/salsa-macros/src/db.rs | 9 +- components/salsa-macros/src/fn_util.rs | 2 +- components/salsa-macros/src/hygiene.rs | 12 +- components/salsa-macros/src/supertype.rs | 4 +- components/salsa-macros/src/tracked_fn.rs | 8 +- components/salsa-macros/src/tracked_impl.rs | 2 +- src/accumulator.rs | 13 +- src/database.rs | 7 +- src/function.rs | 35 +- src/function/memo.rs | 2 +- src/ingredient.rs | 30 +- src/ingredient_cache.rs | 202 +++++++++++ src/input.rs | 13 +- src/input/input_field.rs | 6 +- src/interned.rs | 39 +-- src/lib.rs | 15 +- src/memo_ingredient_indices.rs | 43 ++- src/salsa_struct.rs | 2 +- src/storage.rs | 64 +++- src/sync.rs | 96 +----- src/table/memo.rs | 169 +++------- src/tracked_struct.rs | 29 +- src/tracked_struct/tracked_field.rs | 6 +- src/views.rs | 40 ++- src/zalsa.rs | 315 +++++++----------- src/zalsa_local.rs | 2 +- tests/accumulate-chain.rs | 2 + tests/accumulate-custom-debug.rs | 2 + tests/accumulate-dag.rs | 2 + tests/accumulate-execution-order.rs | 2 + tests/accumulate-from-tracked-fn.rs | 2 + tests/accumulate-no-duplicates.rs | 2 + tests/accumulate-reuse-workaround.rs | 2 + tests/accumulate-reuse.rs | 2 + tests/accumulate.rs | 2 + tests/accumulated_backdate.rs | 2 + tests/backtrace.rs | 34 +- tests/check_auto_traits.rs | 2 + tests/compile_fail.rs | 2 + tests/cycle.rs | 2 + tests/cycle_accumulate.rs | 2 + tests/cycle_fallback_immediate.rs | 2 + tests/cycle_initial_call_back_into_cycle.rs | 2 + tests/cycle_initial_call_query.rs | 2 + tests/cycle_maybe_changed_after.rs | 2 + tests/cycle_output.rs | 2 + tests/cycle_recovery_call_back_into_cycle.rs | 2 + tests/cycle_recovery_call_query.rs | 2 + tests/cycle_regression_455.rs | 2 + tests/cycle_result_dependencies.rs | 2 + tests/cycle_tracked.rs | 2 + tests/cycle_tracked_own_input.rs | 2 + tests/dataflow.rs | 2 + tests/debug.rs | 2 + tests/debug_db_contents.rs | 2 + tests/deletion-cascade.rs | 2 + tests/deletion-drops.rs | 2 + tests/deletion.rs | 2 + tests/derive_update.rs | 2 + tests/durability.rs | 2 + tests/elided-lifetime-in-tracked-fn.rs | 2 + ...truct_changes_but_fn_depends_on_field_y.rs | 2 + ...input_changes_but_fn_depends_on_field_y.rs | 2 + tests/hash_collision.rs | 2 + tests/hello_world.rs | 2 + tests/input_default.rs | 2 + tests/input_field_durability.rs | 2 + tests/input_setter_preserves_durability.rs | 2 + tests/intern_access_in_different_revision.rs | 2 + tests/interned-revisions.rs | 2 + tests/interned-structs.rs | 2 + tests/interned-structs_self_ref.rs | 20 +- tests/lru.rs | 2 + tests/manual_registration.rs | 92 +++++ tests/memory-usage.rs | 2 + tests/mutate_in_place.rs | 2 + tests/override_new_get_set.rs | 2 + ...ng-tracked-struct-outside-of-tracked-fn.rs | 2 + tests/parallel/main.rs | 2 + tests/preverify-struct-with-leaked-data-2.rs | 2 + tests/preverify-struct-with-leaked-data.rs | 2 + tests/return_mode.rs | 2 + tests/singleton.rs | 2 + ...the-key-is-created-in-the-current-query.rs | 2 + tests/synthetic_write.rs | 2 + tests/tracked-struct-id-field-bad-eq.rs | 2 + tests/tracked-struct-id-field-bad-hash.rs | 2 + tests/tracked-struct-unchanged-in-new-rev.rs | 2 + tests/tracked-struct-value-field-bad-eq.rs | 2 + tests/tracked-struct-value-field-not-eq.rs | 2 + tests/tracked_assoc_fn.rs | 2 + tests/tracked_fn_constant.rs | 2 + .../tracked_fn_high_durability_dependency.rs | 1 + tests/tracked_fn_interned_lifetime.rs | 2 + tests/tracked_fn_multiple_args.rs | 2 + tests/tracked_fn_no_eq.rs | 2 + tests/tracked_fn_on_input.rs | 2 + ...racked_fn_on_input_with_high_durability.rs | 1 + tests/tracked_fn_on_interned.rs | 2 + tests/tracked_fn_on_interned_enum.rs | 2 + tests/tracked_fn_on_tracked.rs | 2 + tests/tracked_fn_on_tracked_specify.rs | 2 + tests/tracked_fn_orphan_escape_hatch.rs | 2 + tests/tracked_fn_read_own_entity.rs | 2 + tests/tracked_fn_read_own_specify.rs | 2 + tests/tracked_fn_return_ref.rs | 2 + tests/tracked_method.rs | 2 + tests/tracked_method_inherent_return_deref.rs | 2 + tests/tracked_method_inherent_return_ref.rs | 2 + tests/tracked_method_on_tracked_struct.rs | 2 + tests/tracked_method_trait_return_ref.rs | 2 + tests/tracked_method_with_self_ty.rs | 2 + tests/tracked_struct.rs | 2 + tests/tracked_struct_db1_lt.rs | 2 + tests/tracked_struct_disambiguates.rs | 2 + tests/tracked_struct_durability.rs | 2 + tests/tracked_struct_manual_update.rs | 2 + tests/tracked_struct_mixed_tracked_fields.rs | 2 + tests/tracked_struct_recreate_new_revision.rs | 2 + tests/tracked_struct_with_interned_query.rs | 2 + tests/tracked_with_intern.rs | 2 + tests/tracked_with_struct_db.rs | 2 + tests/tracked_with_struct_ord.rs | 2 + 130 files changed, 1023 insertions(+), 613 deletions(-) create mode 100644 src/ingredient_cache.rs create mode 100644 tests/manual_registration.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 803d4105..acb69257 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,6 +55,8 @@ jobs: run: cargo clippy --workspace --all-targets -- -D warnings - name: Test run: cargo nextest run --workspace --all-targets --no-fail-fast + - name: Test Manual Registration + run: cargo nextest run --workspace --tests --no-fail-fast --no-default-features --features macros - name: Test docs run: cargo test --workspace --doc - name: Check (without default features) diff --git a/Cargo.toml b/Cargo.toml index 4a579ac7..54eac853 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,13 +19,15 @@ hashbrown = "0.15" hashlink = "0.10" indexmap = "2" intrusive-collections = "0.9.7" -papaya = "0.2.3" parking_lot = "0.12" portable-atomic = "1" rustc-hash = "2" smallvec = "1" tracing = { version = "0.1", default-features = false, features = ["std"] } +# Automatic ingredient registration. +inventory = { version = "0.3.20", optional = true } + # parallel map rayon = { version = "1.10.0", optional = true } @@ -36,7 +38,8 @@ thin-vec = "0.2.13" shuttle = { version = "0.8.0", optional = true } [features] -default = ["salsa_unstable", "rayon", "macros"] +default = ["salsa_unstable", "rayon", "macros", "inventory"] +inventory = ["dep:inventory"] shuttle = ["dep:shuttle"] # FIXME: remove `salsa_unstable` before 1.0. salsa_unstable = [] diff --git a/components/salsa-macro-rules/src/setup_accumulator_impl.rs b/components/salsa-macro-rules/src/setup_accumulator_impl.rs index 788d5cf7..7842067e 100644 --- a/components/salsa-macro-rules/src/setup_accumulator_impl.rs +++ b/components/salsa-macro-rules/src/setup_accumulator_impl.rs @@ -21,14 +21,21 @@ macro_rules! setup_accumulator_impl { use salsa::plumbing as $zalsa; use salsa::plumbing::accumulator as $zalsa_struct; + impl $zalsa::HasJar for $Struct { + type Jar = $zalsa_struct::JarImpl<$Struct>; + const KIND: $zalsa::JarKind = $zalsa::JarKind::Struct; + } + + $zalsa::register_jar! { + $zalsa::ErasedJar::erase::<$Struct>() + } + fn $ingredient(zalsa: &$zalsa::Zalsa) -> &$zalsa_struct::IngredientImpl<$Struct> { static $CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Struct>> = $zalsa::IngredientCache::new(); $CACHE.get_or_create(zalsa, || { - zalsa - .lookup_jar_by_type::<$zalsa_struct::JarImpl<$Struct>>() - .get_or_create() + zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Struct>>() }) } diff --git a/components/salsa-macro-rules/src/setup_input_struct.rs b/components/salsa-macro-rules/src/setup_input_struct.rs index a2a402ba..38988c54 100644 --- a/components/salsa-macro-rules/src/setup_input_struct.rs +++ b/components/salsa-macro-rules/src/setup_input_struct.rs @@ -74,6 +74,15 @@ macro_rules! setup_input_struct { type $Configuration = $Struct; + impl $zalsa::HasJar for $Struct { + type Jar = $zalsa_struct::JarImpl<$Configuration>; + const KIND: $zalsa::JarKind = $zalsa::JarKind::Struct; + } + + $zalsa::register_jar! { + $zalsa::ErasedJar::erase::<$Struct>() + } + impl $zalsa_struct::Configuration for $Configuration { const LOCATION: $zalsa::Location = $zalsa::Location { file: file!(), @@ -101,14 +110,14 @@ macro_rules! setup_input_struct { $zalsa::IngredientCache::new(); CACHE.get_or_create(zalsa, || { - zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().get_or_create() + zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>() }) } pub fn ingredient_mut(db: &mut dyn $zalsa::Database) -> (&mut $zalsa_struct::IngredientImpl, &mut $zalsa::Runtime) { let zalsa_mut = db.zalsa_mut(); zalsa_mut.new_revision(); - let index = zalsa_mut.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().get_or_create(); + let index = zalsa_mut.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>(); let (ingredient, runtime) = zalsa_mut.lookup_ingredient_mut(index); let ingredient = ingredient.assert_type_mut::<$zalsa_struct::IngredientImpl>(); (ingredient, runtime) @@ -149,8 +158,8 @@ macro_rules! setup_input_struct { impl $zalsa::SalsaStructInDb for $Struct { type MemoIngredientMap = $zalsa::MemoIngredientSingletonIndex; - fn lookup_or_create_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices { - aux.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().get_or_create().into() + fn lookup_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices { + aux.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().into() } #[inline] diff --git a/components/salsa-macro-rules/src/setup_interned_struct.rs b/components/salsa-macro-rules/src/setup_interned_struct.rs index 103f4d21..3a62355c 100644 --- a/components/salsa-macro-rules/src/setup_interned_struct.rs +++ b/components/salsa-macro-rules/src/setup_interned_struct.rs @@ -92,6 +92,15 @@ macro_rules! setup_interned_struct { type $Configuration = $StructWithStatic; + impl<$($db_lt_arg)?> $zalsa::HasJar for $Struct<$($db_lt_arg)?> { + type Jar = $zalsa_struct::JarImpl<$Configuration>; + const KIND: $zalsa::JarKind = $zalsa::JarKind::Struct; + } + + $zalsa::register_jar! { + $zalsa::ErasedJar::erase::<$StructWithStatic>() + } + type $StructDataIdent<$db_lt> = ($($field_ty,)*); /// Key to use during hash lookups. Each field is some type that implements `Lookup` @@ -149,7 +158,7 @@ macro_rules! setup_interned_struct { let zalsa = db.zalsa(); CACHE.get_or_create(zalsa, || { - zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().get_or_create() + zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>() }) } } @@ -181,8 +190,8 @@ macro_rules! setup_interned_struct { impl< $($db_lt_arg)? > $zalsa::SalsaStructInDb for $Struct< $($db_lt_arg)? > { type MemoIngredientMap = $zalsa::MemoIngredientSingletonIndex; - fn lookup_or_create_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices { - aux.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().get_or_create().into() + fn lookup_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices { + aux.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().into() } #[inline] diff --git a/components/salsa-macro-rules/src/setup_tracked_fn.rs b/components/salsa-macro-rules/src/setup_tracked_fn.rs index 850b3e58..47707071 100644 --- a/components/salsa-macro-rules/src/setup_tracked_fn.rs +++ b/components/salsa-macro-rules/src/setup_tracked_fn.rs @@ -91,6 +91,16 @@ macro_rules! setup_tracked_fn { struct $Configuration; + $zalsa::register_jar! { + $zalsa::ErasedJar::erase::<$fn_name>() + } + + #[allow(non_local_definitions)] + impl $zalsa::HasJar for $fn_name { + type Jar = $fn_name; + const KIND: $zalsa::JarKind = $zalsa::JarKind::TrackedFn; + } + static $FN_CACHE: $zalsa::IngredientCache<$zalsa::function::IngredientImpl<$Configuration>> = $zalsa::IngredientCache::new(); @@ -108,7 +118,7 @@ macro_rules! setup_tracked_fn { impl $zalsa::SalsaStructInDb for $InternedData<'_> { type MemoIngredientMap = $zalsa::MemoIngredientSingletonIndex; - fn lookup_or_create_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices { + fn lookup_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices { $zalsa::IngredientIndices::empty() } @@ -155,27 +165,19 @@ macro_rules! setup_tracked_fn { impl $Configuration { fn fn_ingredient(db: &dyn $Db) -> &$zalsa::function::IngredientImpl<$Configuration> { let zalsa = db.zalsa(); - $FN_CACHE.get_or_create(zalsa, || { - let jar_entry = zalsa.lookup_jar_by_type::<$Configuration>(); - - // If the ingredient has already been inserted, we know that the downcaster - // has also been registered. This is a fast-path for multi-database use cases - // that bypass the ingredient cache and will always execute this closure. - if let Some(index) = jar_entry.get() { - return index; - } - - ::zalsa_register_downcaster(db); - jar_entry.get_or_create() - }) + $FN_CACHE + .get_or_create(zalsa, || zalsa.lookup_jar_by_type::<$fn_name>()) + .get_or_init(|| ::zalsa_register_downcaster(db)) } pub fn fn_ingredient_mut(db: &mut dyn $Db) -> &mut $zalsa::function::IngredientImpl { - ::zalsa_register_downcaster(db); + let view = ::zalsa_register_downcaster(db); let zalsa_mut = db.zalsa_mut(); - let index = zalsa_mut.lookup_jar_by_type::<$Configuration>().get_or_create(); + let index = zalsa_mut.lookup_jar_by_type::<$fn_name>(); let (ingredient, _) = zalsa_mut.lookup_ingredient_mut(index); - ingredient.assert_type_mut::<$zalsa::function::IngredientImpl>() + let ingredient = ingredient.assert_type_mut::<$zalsa::function::IngredientImpl>(); + ingredient.get_or_init(|| view); + ingredient } $zalsa::macro_if! { $needs_interner => @@ -184,8 +186,7 @@ macro_rules! setup_tracked_fn { ) -> &$zalsa::interned::IngredientImpl<$Configuration> { let zalsa = db.zalsa(); $INTERN_CACHE.get_or_create(zalsa, || { - ::zalsa_register_downcaster(db); - zalsa.lookup_jar_by_type::<$Configuration>().get_or_create().successor(0) + zalsa.lookup_jar_by_type::<$fn_name>().successor(0) }) } } @@ -248,42 +249,31 @@ macro_rules! setup_tracked_fn { } } - impl $zalsa::Jar for $Configuration { - fn create_dependencies(zalsa: &$zalsa::Zalsa) -> $zalsa::IngredientIndices - where - Self: Sized - { - $zalsa::macro_if! { - if $needs_interner { - $zalsa::IngredientIndices::empty() - } else { - <$InternedData as $zalsa::SalsaStructInDb>::lookup_or_create_ingredient_index(zalsa) - } - } - } - + #[allow(non_local_definitions)] + impl $zalsa::Jar for $fn_name { fn create_ingredients( - zalsa: &$zalsa::Zalsa, + zalsa: &mut $zalsa::Zalsa, first_index: $zalsa::IngredientIndex, - struct_index: $zalsa::IngredientIndices, ) -> Vec> { let struct_index: $zalsa::IngredientIndices = $zalsa::macro_if! { if $needs_interner { first_index.successor(0).into() } else { - struct_index + // Note that struct ingredients are created before tracked functions, + // so this cannot panic. + <$InternedData as $zalsa::SalsaStructInDb>::lookup_ingredient_index(zalsa) } }; $zalsa::macro_if! { $needs_interner => - let intern_ingredient = <$zalsa::interned::IngredientImpl<$Configuration>>::new( + let mut intern_ingredient = <$zalsa::interned::IngredientImpl<$Configuration>>::new( first_index.successor(0) ); } let intern_ingredient_memo_types = $zalsa::macro_if! { if $needs_interner { - Some($zalsa::Ingredient::memo_table_types(&intern_ingredient)) + Some($zalsa::Ingredient::memo_table_types_mut(&mut intern_ingredient)) } else { None } @@ -303,7 +293,6 @@ macro_rules! setup_tracked_fn { first_index, memo_ingredient_indices, $lru, - zalsa.views().downcaster_for::(), ); $zalsa::macro_if! { if $needs_interner { @@ -386,6 +375,7 @@ macro_rules! setup_tracked_fn { $zalsa::return_mode_expression!(($return_mode, __, __), $output_ty, result,) }) } + // The struct needs be last in the macro expansion in order to make the tracked // function's ident be identified as a function, not a struct, during semantic highlighting. // for more details, see https://github.com/salsa-rs/salsa/pull/612. diff --git a/components/salsa-macro-rules/src/setup_tracked_struct.rs b/components/salsa-macro-rules/src/setup_tracked_struct.rs index 8b54fdd2..07131735 100644 --- a/components/salsa-macro-rules/src/setup_tracked_struct.rs +++ b/components/salsa-macro-rules/src/setup_tracked_struct.rs @@ -107,8 +107,8 @@ macro_rules! setup_tracked_struct { std::marker::PhantomData &$db_lt ()> ); - #[allow(clippy::all)] #[allow(dead_code)] + #[allow(clippy::all)] const _: () = { use salsa::plumbing as $zalsa; use $zalsa::tracked_struct as $zalsa_struct; @@ -116,6 +116,15 @@ macro_rules! setup_tracked_struct { type $Configuration = $Struct<'static>; + impl<$db_lt> $zalsa::HasJar for $Struct<$db_lt> { + type Jar = $zalsa_struct::JarImpl<$Configuration>; + const KIND: $zalsa::JarKind = $zalsa::JarKind::Struct; + } + + $zalsa::register_jar! { + $zalsa::ErasedJar::erase::<$Struct<'static>>() + } + impl $zalsa_struct::Configuration for $Configuration { const LOCATION: $zalsa::Location = $zalsa::Location { file: file!(), @@ -188,7 +197,7 @@ macro_rules! setup_tracked_struct { $zalsa::IngredientCache::new(); CACHE.get_or_create(zalsa, || { - zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().get_or_create() + zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>() }) } } @@ -210,8 +219,8 @@ macro_rules! setup_tracked_struct { impl $zalsa::SalsaStructInDb for $Struct<'_> { type MemoIngredientMap = $zalsa::MemoIngredientSingletonIndex; - fn lookup_or_create_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices { - aux.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().get_or_create().into() + fn lookup_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices { + aux.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().into() } #[inline] diff --git a/components/salsa-macros/src/db.rs b/components/salsa-macros/src/db.rs index 478ebea5..12ee4891 100644 --- a/components/salsa-macros/src/db.rs +++ b/components/salsa-macros/src/db.rs @@ -110,7 +110,7 @@ impl DbMacro { let trait_name = &input.ident; input.items.push(parse_quote! { #[doc(hidden)] - fn zalsa_register_downcaster(&self); + fn zalsa_register_downcaster(&self) -> salsa::plumbing::DatabaseDownCaster; }); let comment = format!(" Downcast a [`dyn Database`] to a [`dyn {trait_name}`]"); @@ -135,10 +135,11 @@ impl DbMacro { }; input.items.push(parse_quote! { + #[cold] + #[inline(never)] #[doc(hidden)] - #[inline(always)] - fn zalsa_register_downcaster(&self) { - salsa::plumbing::views(self).add(::downcast); + fn zalsa_register_downcaster(&self) -> salsa::plumbing::DatabaseDownCaster { + salsa::plumbing::views(self).add(::downcast) } }); input.items.push(parse_quote! { diff --git a/components/salsa-macros/src/fn_util.rs b/components/salsa-macros/src/fn_util.rs index 619d0fd9..d06d0a7d 100644 --- a/components/salsa-macros/src/fn_util.rs +++ b/components/salsa-macros/src/fn_util.rs @@ -15,7 +15,7 @@ pub fn input_ids(hygiene: &Hygiene, sig: &syn::Signature, skip: usize) -> Vec syn::Ident { + pub(crate) fn ident(&self, text: impl AsRef) -> syn::Ident { // Make the default be `foo_` rather than `foo` -- this helps detect // cases where people wrote `foo` instead of `#foo` or `$foo` in the generated code. - let mut buffer = format!("{text}_"); + let mut buffer = format!("{}_", text.as_ref()); while self.user_tokens.contains(&buffer) { buffer.push('_'); @@ -61,4 +61,12 @@ impl Hygiene { syn::Ident::new(&buffer, proc_macro2::Span::call_site()) } + + /// Generates an identifier similar to `text` but distinct from any identifiers + /// that appear in the user's code. + /// + /// The identifier must be unique relative to the `scope` identifier. + pub(crate) fn scoped_ident(&self, scope: &syn::Ident, text: &str) -> syn::Ident { + self.ident(format!("{scope}_{text}")) + } } diff --git a/components/salsa-macros/src/supertype.rs b/components/salsa-macros/src/supertype.rs index 5b433bd8..d1c6c70b 100644 --- a/components/salsa-macros/src/supertype.rs +++ b/components/salsa-macros/src/supertype.rs @@ -72,8 +72,8 @@ fn enum_impl(enum_item: syn::ItemEnum) -> syn::Result { type MemoIngredientMap = zalsa::MemoIngredientIndices; #[inline] - fn lookup_or_create_ingredient_index(__zalsa: &zalsa::Zalsa) -> zalsa::IngredientIndices { - zalsa::IngredientIndices::merge([ #( <#variant_types as zalsa::SalsaStructInDb>::lookup_or_create_ingredient_index(__zalsa) ),* ]) + fn lookup_ingredient_index(__zalsa: &zalsa::Zalsa) -> zalsa::IngredientIndices { + zalsa::IngredientIndices::merge([ #( <#variant_types as zalsa::SalsaStructInDb>::lookup_ingredient_index(__zalsa) ),* ]) } #[inline] diff --git a/components/salsa-macros/src/tracked_fn.rs b/components/salsa-macros/src/tracked_fn.rs index f2f07893..9f7ec087 100644 --- a/components/salsa-macros/src/tracked_fn.rs +++ b/components/salsa-macros/src/tracked_fn.rs @@ -132,10 +132,10 @@ impl Macro { inner_fn.sig.ident = self.hygiene.ident("inner"); let zalsa = self.hygiene.ident("zalsa"); - let Configuration = self.hygiene.ident("Configuration"); - let InternedData = self.hygiene.ident("InternedData"); - let FN_CACHE = self.hygiene.ident("FN_CACHE"); - let INTERN_CACHE = self.hygiene.ident("INTERN_CACHE"); + let Configuration = self.hygiene.scoped_ident(fn_name, "Configuration"); + let InternedData = self.hygiene.scoped_ident(fn_name, "InternedData"); + let FN_CACHE = self.hygiene.scoped_ident(fn_name, "FN_CACHE"); + let INTERN_CACHE = self.hygiene.scoped_ident(fn_name, "INTERN_CACHE"); let inner = &inner_fn.sig.ident; let function_type = function_type(&item); diff --git a/components/salsa-macros/src/tracked_impl.rs b/components/salsa-macros/src/tracked_impl.rs index 9ad9c2c4..2a07ae0c 100644 --- a/components/salsa-macros/src/tracked_impl.rs +++ b/components/salsa-macros/src/tracked_impl.rs @@ -99,7 +99,7 @@ impl Macro { }); let InnerTrait = self.hygiene.ident("InnerTrait"); - let inner_fn_name = self.hygiene.ident(&fn_item.sig.ident.to_string()); + let inner_fn_name = self.hygiene.ident(fn_item.sig.ident.to_string()); let AssociatedFunctionArguments { self_token, diff --git a/src/accumulator.rs b/src/accumulator.rs index b3103c31..3b1358c6 100644 --- a/src/accumulator.rs +++ b/src/accumulator.rs @@ -10,7 +10,7 @@ use accumulated::{Accumulated, AnyAccumulated}; use crate::cycle::CycleHeads; use crate::function::VerifyResult; use crate::ingredient::{Ingredient, Jar}; -use crate::plumbing::{IngredientIndices, ZalsaLocal}; +use crate::plumbing::ZalsaLocal; use crate::sync::Arc; use crate::table::memo::MemoTableTypes; use crate::zalsa::{IngredientIndex, Zalsa}; @@ -44,9 +44,8 @@ impl Default for JarImpl { impl Jar for JarImpl { fn create_ingredients( - _zalsa: &Zalsa, + _zalsa: &mut Zalsa, first_index: IngredientIndex, - _dependencies: IngredientIndices, ) -> Vec> { vec![Box::new(>::new(first_index))] } @@ -64,7 +63,7 @@ pub struct IngredientImpl { impl IngredientImpl { /// Find the accumulator ingredient for `A` in the database, if any. pub fn from_zalsa(zalsa: &Zalsa) -> Option<&Self> { - let index = zalsa.lookup_jar_by_type::>().get_or_create(); + let index = zalsa.lookup_jar_by_type::>(); let ingredient = zalsa.lookup_ingredient(index).assert_type::(); Some(ingredient) } @@ -115,7 +114,11 @@ impl Ingredient for IngredientImpl { A::DEBUG_NAME } - fn memo_table_types(&self) -> Arc { + fn memo_table_types(&self) -> &Arc { + unreachable!("accumulator does not allocate pages") + } + + fn memo_table_types_mut(&mut self) -> &mut Arc { unreachable!("accumulator does not allocate pages") } } diff --git a/src/database.rs b/src/database.rs index 594deb0a..b840398f 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,6 +1,7 @@ use std::any::Any; use std::borrow::Cow; +use crate::views::DatabaseDownCaster; use crate::zalsa::{IngredientIndex, ZalsaDatabase}; use crate::{Durability, Revision}; @@ -80,9 +81,11 @@ pub trait Database: Send + AsDynDatabase + Any + ZalsaDatabase { crate::attach::attach(self, || op(self)) } + #[cold] + #[inline(never)] #[doc(hidden)] - #[inline(always)] - fn zalsa_register_downcaster(&self) { + fn zalsa_register_downcaster(&self) -> DatabaseDownCaster { + self.zalsa().views().downcaster_for::() // The no-op downcaster is special cased in view caster construction. } diff --git a/src/function.rs b/src/function.rs index 76b2abf7..ceb006fe 100644 --- a/src/function.rs +++ b/src/function.rs @@ -3,6 +3,7 @@ use std::any::Any; use std::fmt; use std::ptr::NonNull; use std::sync::atomic::Ordering; +use std::sync::OnceLock; pub(crate) use sync::SyncGuard; use crate::accumulator::accumulated_map::{AccumulatedMap, InputAccumulatedValues}; @@ -129,7 +130,7 @@ pub struct IngredientImpl { /// /// The supplied database must be be the same as the database used to construct the [`Views`] /// instances that this downcaster was derived from. - view_caster: DatabaseDownCaster, + view_caster: OnceLock>, sync_table: SyncTable, @@ -156,18 +157,30 @@ where index: IngredientIndex, memo_ingredient_indices: as SalsaStructInDb>::MemoIngredientMap, lru: usize, - view_caster: DatabaseDownCaster, ) -> Self { Self { index, memo_ingredient_indices, lru: lru::Lru::new(lru), deleted_entries: Default::default(), - view_caster, + view_caster: OnceLock::new(), sync_table: SyncTable::new(index), } } + /// Set the view-caster for this tracked function ingredient, if it has + /// not already been initialized. + #[inline] + pub fn get_or_init( + &self, + view_caster: impl FnOnce() -> DatabaseDownCaster, + ) -> &Self { + // Note that we must set this lazily as we don't have access to the database + // type when ingredients are registered into the `Zalsa`. + self.view_caster.get_or_init(view_caster); + self + } + #[inline] pub fn database_key_index(&self, key: Id) -> DatabaseKeyIndex { DatabaseKeyIndex::new(self.index, key) @@ -226,6 +239,12 @@ where fn memo_ingredient_index(&self, zalsa: &Zalsa, id: Id) -> MemoIngredientIndex { self.memo_ingredient_indices.get_zalsa_id(zalsa, id) } + + fn view_caster(&self) -> &DatabaseDownCaster { + self.view_caster + .get() + .expect("tracked function ingredients cannot be accessed before calling `init`") + } } impl Ingredient for IngredientImpl @@ -248,7 +267,7 @@ where cycle_heads: &mut CycleHeads, ) -> VerifyResult { // SAFETY: The `db` belongs to the ingredient as per caller invariant - let db = unsafe { self.view_caster.downcast_unchecked(db) }; + let db = unsafe { self.view_caster().downcast_unchecked(db) }; self.maybe_changed_after(db, input, revision, cycle_heads) } @@ -339,7 +358,11 @@ where C::DEBUG_NAME } - fn memo_table_types(&self) -> Arc { + fn memo_table_types(&self) -> &Arc { + unreachable!("function does not allocate pages") + } + + fn memo_table_types_mut(&mut self) -> &mut Arc { unreachable!("function does not allocate pages") } @@ -352,7 +375,7 @@ where db: &'db dyn Database, key_index: Id, ) -> (Option<&'db AccumulatedMap>, InputAccumulatedValues) { - let db = self.view_caster.downcast(db); + let db = self.view_caster().downcast(db); self.accumulated_map(db, key_index) } } diff --git a/src/function/memo.rs b/src/function/memo.rs index 8f8952e5..8f8393fc 100644 --- a/src/function/memo.rs +++ b/src/function/memo.rs @@ -466,7 +466,7 @@ mod _memory_usage { impl SalsaStructInDb for DummyStruct { type MemoIngredientMap = MemoIngredientSingletonIndex; - fn lookup_or_create_ingredient_index(_: &Zalsa) -> IngredientIndices { + fn lookup_ingredient_index(_: &Zalsa) -> IngredientIndices { unimplemented!() } diff --git a/src/ingredient.rs b/src/ingredient.rs index ff483769..796a6e12 100644 --- a/src/ingredient.rs +++ b/src/ingredient.rs @@ -6,7 +6,6 @@ use crate::cycle::{ empty_cycle_heads, CycleHeads, CycleRecoveryStrategy, IterationCount, ProvisionalStatus, }; use crate::function::VerifyResult; -use crate::plumbing::IngredientIndices; use crate::runtime::Running; use crate::sync::Arc; use crate::table::memo::MemoTableTypes; @@ -16,35 +15,20 @@ use crate::zalsa_local::QueryOriginRef; use crate::{Database, DatabaseKeyIndex, Id, Revision}; /// A "jar" is a group of ingredients that are added atomically. +/// /// Each type implementing jar can be added to the database at most once. pub trait Jar: Any { - /// This creates the ingredient dependencies of this jar. We need to split this from `create_ingredients()` - /// because while `create_ingredients()` is called, a lock on the ingredient map is held (to guarantee - /// atomicity), so other ingredients could not be created. - /// - /// Only tracked fns use this. - fn create_dependencies(_zalsa: &Zalsa) -> IngredientIndices - where - Self: Sized, - { - IngredientIndices::empty() - } - /// Create the ingredients given the index of the first one. + /// /// All subsequent ingredients will be assigned contiguous indices. fn create_ingredients( - zalsa: &Zalsa, + zalsa: &mut Zalsa, first_index: IngredientIndex, - dependencies: IngredientIndices, - ) -> Vec> - where - Self: Sized; + ) -> Vec>; /// This returns the [`TypeId`] of the ID struct, that is, the struct that wraps `salsa::Id` /// and carry the name of the jar. - fn id_struct_type_id() -> TypeId - where - Self: Sized; + fn id_struct_type_id() -> TypeId; } pub struct Location { @@ -151,7 +135,9 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync { ); } - fn memo_table_types(&self) -> Arc; + fn memo_table_types(&self) -> &Arc; + + fn memo_table_types_mut(&mut self) -> &mut Arc; fn fmt_index(&self, index: crate::Id, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt_index(self.debug_name(), index, fmt) diff --git a/src/ingredient_cache.rs b/src/ingredient_cache.rs new file mode 100644 index 00000000..8b9ebe76 --- /dev/null +++ b/src/ingredient_cache.rs @@ -0,0 +1,202 @@ +pub use imp::IngredientCache; + +#[cfg(feature = "inventory")] +mod imp { + use crate::plumbing::Ingredient; + use crate::sync::atomic::{self, AtomicU32, Ordering}; + use crate::zalsa::Zalsa; + use crate::IngredientIndex; + + use std::marker::PhantomData; + + /// Caches an ingredient index. + /// + /// Note that all ingredients are statically registered with `inventory`, so their + /// indices should be stable across any databases. + pub struct IngredientCache + where + I: Ingredient, + { + ingredient_index: AtomicU32, + phantom: PhantomData I>, + } + + impl Default for IngredientCache + where + I: Ingredient, + { + fn default() -> Self { + Self::new() + } + } + + impl IngredientCache + where + I: Ingredient, + { + const UNINITIALIZED: u32 = u32::MAX; + + /// Create a new cache + pub const fn new() -> Self { + Self { + ingredient_index: atomic::AtomicU32::new(Self::UNINITIALIZED), + phantom: PhantomData, + } + } + + /// Get a reference to the ingredient in the database. + /// + /// If the ingredient index is not already in the cache, it will be loaded and cached. + pub fn get_or_create<'db>( + &self, + zalsa: &'db Zalsa, + load_index: impl Fn() -> IngredientIndex, + ) -> &'db I { + let mut ingredient_index = self.ingredient_index.load(Ordering::Acquire); + if ingredient_index == Self::UNINITIALIZED { + ingredient_index = self.get_or_create_index_slow(load_index).as_u32(); + }; + + zalsa + .lookup_ingredient(IngredientIndex::from_unchecked(ingredient_index)) + .assert_type() + } + + #[cold] + #[inline(never)] + fn get_or_create_index_slow( + &self, + load_index: impl Fn() -> IngredientIndex, + ) -> IngredientIndex { + let ingredient_index = load_index(); + + // It doesn't matter if we overwrite any stores, as `create_index` should + // always return the same index when the `inventory` feature is enabled. + self.ingredient_index + .store(ingredient_index.as_u32(), Ordering::Release); + + ingredient_index + } + } +} + +#[cfg(not(feature = "inventory"))] +mod imp { + use crate::nonce::Nonce; + use crate::plumbing::Ingredient; + use crate::sync::atomic::{AtomicU64, Ordering}; + use crate::zalsa::{StorageNonce, Zalsa}; + use crate::IngredientIndex; + + use std::marker::PhantomData; + use std::mem; + + /// Caches an ingredient index. + /// + /// With manual registration, ingredient indices can vary across databases, + /// but we can retain most of the benefit by optimizing for the the case of + /// a single database. + pub struct IngredientCache + where + I: Ingredient, + { + // A packed representation of `Option<(Nonce, IngredientIndex)>`. + // + // This allows us to replace a lock in favor of an atomic load. This works thanks to `Nonce` + // having a niche, which means the entire type can fit into an `AtomicU64`. + cached_data: AtomicU64, + phantom: PhantomData I>, + } + + impl Default for IngredientCache + where + I: Ingredient, + { + fn default() -> Self { + Self::new() + } + } + + impl IngredientCache + where + I: Ingredient, + { + const UNINITIALIZED: u64 = 0; + + /// Create a new cache + pub const fn new() -> Self { + Self { + cached_data: AtomicU64::new(Self::UNINITIALIZED), + phantom: PhantomData, + } + } + + /// Get a reference to the ingredient in the database. + /// + /// If the ingredient is not already in the cache, it will be created. + #[inline(always)] + pub fn get_or_create<'db>( + &self, + zalsa: &'db Zalsa, + create_index: impl Fn() -> IngredientIndex, + ) -> &'db I { + let index = self.get_or_create_index(zalsa, create_index); + zalsa.lookup_ingredient(index).assert_type::() + } + + pub fn get_or_create_index( + &self, + zalsa: &Zalsa, + create_index: impl Fn() -> IngredientIndex, + ) -> IngredientIndex { + const _: () = assert!( + mem::size_of::<(Nonce, IngredientIndex)>() == mem::size_of::() + ); + + let cached_data = self.cached_data.load(Ordering::Acquire); + if cached_data == Self::UNINITIALIZED { + return self.get_or_create_index_slow(zalsa, create_index); + }; + + // Unpack our `u64` into the nonce and index. + let index = IngredientIndex::from_unchecked(cached_data as u32); + + // SAFETY: We've checked against `UNINITIALIZED` (0) above and so the upper bits must be non-zero. + let nonce = crate::nonce::Nonce::::from_u32(unsafe { + std::num::NonZeroU32::new_unchecked((cached_data >> u32::BITS) as u32) + }); + + // The data was cached for a different database, we have to ensure the ingredient was + // created in ours. + if zalsa.nonce() != nonce { + return create_index(); + } + + index + } + + #[cold] + #[inline(never)] + fn get_or_create_index_slow( + &self, + zalsa: &Zalsa, + create_index: impl Fn() -> IngredientIndex, + ) -> IngredientIndex { + let index = create_index(); + let nonce = zalsa.nonce().into_u32().get() as u64; + let packed = (nonce << u32::BITS) | (index.as_u32() as u64); + debug_assert_ne!(packed, IngredientCache::::UNINITIALIZED); + + // Discard the result, whether we won over the cache or not doesn't matter. + _ = self.cached_data.compare_exchange( + IngredientCache::::UNINITIALIZED, + packed, + Ordering::Release, + Ordering::Relaxed, + ); + + // Use our locally computed index regardless of which one was cached. + index + } + } +} diff --git a/src/input.rs b/src/input.rs index fe72c7e1..c13d23e2 100644 --- a/src/input.rs +++ b/src/input.rs @@ -56,9 +56,8 @@ impl Default for JarImpl { impl Jar for JarImpl { fn create_ingredients( - _zalsa: &Zalsa, + _zalsa: &mut Zalsa, struct_index: crate::zalsa::IngredientIndex, - _dependencies: crate::memo_ingredient_indices::IngredientIndices, ) -> Vec> { let struct_ingredient: IngredientImpl = IngredientImpl::new(struct_index); @@ -117,7 +116,7 @@ impl IngredientImpl { fields, revisions, durabilities, - memos: Default::default(), + memos: MemoTable::new(self.memo_table_types()), }) }); @@ -238,8 +237,12 @@ impl Ingredient for IngredientImpl { C::DEBUG_NAME } - fn memo_table_types(&self) -> Arc { - self.memo_table_types.clone() + fn memo_table_types(&self) -> &Arc { + &self.memo_table_types + } + + fn memo_table_types_mut(&mut self) -> &mut Arc { + &mut self.memo_table_types } /// Returns memory usage information about any inputs. diff --git a/src/input/input_field.rs b/src/input/input_field.rs index 5e1df487..f0e4856c 100644 --- a/src/input/input_field.rs +++ b/src/input/input_field.rs @@ -76,7 +76,11 @@ where C::FIELD_DEBUG_NAMES[self.field_index] } - fn memo_table_types(&self) -> Arc { + fn memo_table_types(&self) -> &Arc { + unreachable!("input fields do not allocate pages") + } + + fn memo_table_types_mut(&mut self) -> &mut Arc { unreachable!("input fields do not allocate pages") } } diff --git a/src/interned.rs b/src/interned.rs index 2138494c..4350e77a 100644 --- a/src/interned.rs +++ b/src/interned.rs @@ -15,7 +15,7 @@ use crate::durability::Durability; use crate::function::VerifyResult; use crate::id::{AsId, FromId}; use crate::ingredient::Ingredient; -use crate::plumbing::{IngredientIndices, Jar, ZalsaLocal}; +use crate::plumbing::{Jar, ZalsaLocal}; use crate::revision::AtomicRevision; use crate::sync::{Arc, Mutex, OnceLock}; use crate::table::memo::{MemoTable, MemoTableTypes, MemoTableWithTypesMut}; @@ -224,9 +224,8 @@ impl Default for JarImpl { impl Jar for JarImpl { fn create_ingredients( - _zalsa: &Zalsa, + _zalsa: &mut Zalsa, first_index: IngredientIndex, - _dependencies: IngredientIndices, ) -> Vec> { vec![Box::new(IngredientImpl::::new(first_index)) as _] } @@ -416,7 +415,6 @@ where // Fill up the table for the first few revisions without attempting garbage collection. if !self.revision_queue.is_primed() { return self.intern_id_cold( - db, key, zalsa, zalsa_local, @@ -530,16 +528,16 @@ where // Insert the new value into the ID map. shard.key_map.insert_unique(hash, new_id, hasher); - // Free the memos associated with the previous interned value. - // // SAFETY: We hold the lock for the shard containing the value, and the // value has not been interned in the current revision, so no references to // it can exist. - let mut memo_table = unsafe { std::mem::take(&mut *value.memos.get()) }; + let memo_table = unsafe { &mut *value.memos.get() }; + // Free the memos associated with the previous interned value. + // // SAFETY: The memo table belongs to a value that we allocated, so it has the // correct type. - unsafe { self.clear_memos(zalsa, &mut memo_table, new_id) }; + unsafe { self.clear_memos(zalsa, memo_table, new_id) }; if value_shared.is_reusable::() { // Move the value to the front of the LRU list. @@ -553,16 +551,7 @@ where } // If we could not find any stale slots, we are forced to allocate a new one. - self.intern_id_cold( - db, - key, - zalsa, - zalsa_local, - assemble, - shard, - shard_index, - hash, - ) + self.intern_id_cold(key, zalsa, zalsa_local, assemble, shard, shard_index, hash) } /// The cold path for interning a value, allocating a new slot. @@ -571,7 +560,6 @@ where #[allow(clippy::too_many_arguments)] fn intern_id_cold<'db, Key>( &'db self, - _db: &'db dyn crate::Database, key: Key, zalsa: &Zalsa, zalsa_local: &ZalsaLocal, @@ -598,7 +586,7 @@ where let id = zalsa_local.allocate(zalsa, self.ingredient_index, |id| Value:: { shard: shard_index as u16, link: LinkedListLink::new(), - memos: UnsafeCell::new(MemoTable::default()), + memos: UnsafeCell::new(MemoTable::new(self.memo_table_types())), // SAFETY: We call `from_internal_data` to restore the correct lifetime before access. fields: UnsafeCell::new(unsafe { self.to_internal_data(assemble(id, key)) }), shared: UnsafeCell::new(ValueShared { @@ -696,6 +684,9 @@ where }; std::mem::forget(table_guard); + + // Reset the table after having dropped any memos. + memo_table.reset(); } // Hashes the value by its fields. @@ -849,8 +840,12 @@ where C::DEBUG_NAME } - fn memo_table_types(&self) -> Arc { - self.memo_table_types.clone() + fn memo_table_types(&self) -> &Arc { + &self.memo_table_types + } + + fn memo_table_types_mut(&mut self) -> &mut Arc { + &mut self.memo_table_types } /// Returns memory usage information about any interned values. diff --git a/src/lib.rs b/src/lib.rs index 83e60077..17145225 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,13 +14,11 @@ mod function; mod hash; mod id; mod ingredient; +mod ingredient_cache; mod input; mod interned; mod key; mod memo_ingredient_indices; -mod nonce; -#[cfg(feature = "rayon")] -mod parallel; mod return_mode; mod revision; mod runtime; @@ -34,6 +32,12 @@ mod views; mod zalsa; mod zalsa_local; +#[cfg(not(feature = "inventory"))] +mod nonce; + +#[cfg(feature = "rayon")] +mod parallel; + #[cfg(feature = "rayon")] pub use parallel::{join, par_map}; #[cfg(feature = "macros")] @@ -90,6 +94,7 @@ pub mod plumbing { pub use crate::durability::Durability; pub use crate::id::{AsId, FromId, FromIdWithDb, Id}; pub use crate::ingredient::{Ingredient, Jar, Location}; + pub use crate::ingredient_cache::IngredientCache; pub use crate::key::DatabaseKeyIndex; pub use crate::memo_ingredient_indices::{ IngredientIndices, MemoIngredientIndices, MemoIngredientMap, MemoIngredientSingletonIndex, @@ -102,8 +107,10 @@ pub mod plumbing { pub use crate::tracked_struct::TrackedStructInDb; pub use crate::update::helper::{Dispatch as UpdateDispatch, Fallback as UpdateFallback}; pub use crate::update::{always_update, Update}; + pub use crate::views::DatabaseDownCaster; pub use crate::zalsa::{ - transmute_data_ptr, views, IngredientCache, IngredientIndex, Zalsa, ZalsaDatabase, + register_jar, transmute_data_ptr, views, ErasedJar, HasJar, IngredientIndex, JarKind, + Zalsa, ZalsaDatabase, }; pub use crate::zalsa_local::ZalsaLocal; diff --git a/src/memo_ingredient_indices.rs b/src/memo_ingredient_indices.rs index 8d3e4322..ba1dcf45 100644 --- a/src/memo_ingredient_indices.rs +++ b/src/memo_ingredient_indices.rs @@ -49,11 +49,11 @@ pub trait NewMemoIngredientIndices { /// /// The memo types must be correct. unsafe fn create( - zalsa: &Zalsa, + zalsa: &mut Zalsa, struct_indices: IngredientIndices, ingredient: IngredientIndex, memo_type: MemoEntryType, - intern_ingredient_memo_types: Option>, + intern_ingredient_memo_types: Option<&mut Arc>, ) -> Self; } @@ -62,34 +62,39 @@ impl NewMemoIngredientIndices for MemoIngredientIndices { /// /// The memo types must be correct. unsafe fn create( - zalsa: &Zalsa, + zalsa: &mut Zalsa, struct_indices: IngredientIndices, ingredient: IngredientIndex, memo_type: MemoEntryType, - _intern_ingredient_memo_types: Option>, + _intern_ingredient_memo_types: Option<&mut Arc>, ) -> Self { debug_assert!( _intern_ingredient_memo_types.is_none(), "intern ingredient can only have a singleton memo ingredient" ); + let Some(&last) = struct_indices.indices.last() else { unreachable!("Attempting to construct struct memo mapping for non tracked function?") }; + let mut indices = Vec::new(); indices.resize( (last.as_u32() as usize) + 1, MemoIngredientIndex::from_usize((u32::MAX - 1) as usize), ); + for &struct_ingredient in &struct_indices.indices { - let memo_types = zalsa - .lookup_ingredient(struct_ingredient) - .memo_table_types(); + let memo_ingredient_index = + zalsa.next_memo_ingredient_index(struct_ingredient, ingredient); + indices[struct_ingredient.as_u32() as usize] = memo_ingredient_index; - let mi = zalsa.next_memo_ingredient_index(struct_ingredient, ingredient); - memo_types.set(mi, &memo_type); + let (struct_ingredient, _) = zalsa.lookup_ingredient_mut(struct_ingredient); + let memo_types = Arc::get_mut(struct_ingredient.memo_table_types_mut()) + .expect("memo tables are not shared until database initialization is complete"); - indices[struct_ingredient.as_u32() as usize] = mi; + memo_types.set(memo_ingredient_index, memo_type); } + MemoIngredientIndices { indices: indices.into_boxed_slice(), } @@ -146,25 +151,27 @@ impl MemoIngredientMap for MemoIngredientSingletonIndex { impl NewMemoIngredientIndices for MemoIngredientSingletonIndex { #[inline] unsafe fn create( - zalsa: &Zalsa, + zalsa: &mut Zalsa, indices: IngredientIndices, ingredient: IngredientIndex, memo_type: MemoEntryType, - intern_ingredient_memo_types: Option>, + intern_ingredient_memo_types: Option<&mut Arc>, ) -> Self { let &[struct_ingredient] = &*indices.indices else { unreachable!("Attempting to construct struct memo mapping from enum?") }; + let memo_ingredient_index = zalsa.next_memo_ingredient_index(struct_ingredient, ingredient); let memo_types = intern_ingredient_memo_types.unwrap_or_else(|| { - zalsa - .lookup_ingredient(struct_ingredient) - .memo_table_types() + let (struct_ingredient, _) = zalsa.lookup_ingredient_mut(struct_ingredient); + struct_ingredient.memo_table_types_mut() }); - let mi = zalsa.next_memo_ingredient_index(struct_ingredient, ingredient); - memo_types.set(mi, &memo_type); - Self(mi) + Arc::get_mut(memo_types) + .expect("memo tables are not shared until database initialization is complete") + .set(memo_ingredient_index, memo_type); + + Self(memo_ingredient_index) } } diff --git a/src/salsa_struct.rs b/src/salsa_struct.rs index 80ba4879..725b308a 100644 --- a/src/salsa_struct.rs +++ b/src/salsa_struct.rs @@ -16,7 +16,7 @@ pub trait SalsaStructInDb: Sized { /// While implementors of this trait may call [`crate::zalsa::JarEntry::get_or_create`] /// to create the ingredient, they aren't required to. For example, supertypes recursively /// call [`crate::zalsa::JarEntry::get_or_create`] for their variants and combine them. - fn lookup_or_create_ingredient_index(zalsa: &Zalsa) -> IngredientIndices; + fn lookup_ingredient_index(zalsa: &Zalsa) -> IngredientIndices; /// Plumbing to support nested salsa supertypes. /// diff --git a/src/storage.rs b/src/storage.rs index 19dd55a4..a8c2abec 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use std::panic::RefUnwindSafe; use crate::sync::{Arc, Condvar, Mutex}; -use crate::zalsa::{Zalsa, ZalsaDatabase}; +use crate::zalsa::{ErasedJar, HasJar, Zalsa, ZalsaDatabase}; use crate::zalsa_local::{self, ZalsaLocal}; use crate::{Database, Event, EventKind}; @@ -42,8 +42,15 @@ impl Default for StorageHandle { impl StorageHandle { pub fn new(event_callback: Option>) -> Self { + Self::with_jars(event_callback, Vec::new()) + } + + fn with_jars( + event_callback: Option>, + jars: Vec, + ) -> Self { Self { - zalsa_impl: Arc::new(Zalsa::new::(event_callback)), + zalsa_impl: Arc::new(Zalsa::new::(event_callback, jars)), coordinate: CoordinateDrop(Arc::new(Coordinate { clones: Mutex::new(1), cvar: Default::default(), @@ -115,6 +122,11 @@ impl Storage { } } + /// Returns a builder for database storage. + pub fn builder() -> StorageBuilder { + StorageBuilder::default() + } + /// Convert this instance of [`Storage`] into a [`StorageHandle`]. /// /// This will discard the local state of this [`Storage`], thereby returning a value that @@ -168,6 +180,54 @@ impl Storage { // ANCHOR_END: cancel_other_workers } +/// A builder for a [`Storage`] instance. +/// +/// This type can be created with the [`Storage::builder`] function. +pub struct StorageBuilder { + jars: Vec, + event_callback: Option>, + _db: PhantomData, +} + +impl Default for StorageBuilder { + fn default() -> Self { + Self { + jars: Vec::new(), + event_callback: None, + _db: PhantomData, + } + } +} + +impl StorageBuilder { + /// Set a callback for salsa events. + /// + /// The `event_callback` function will be invoked by the salsa runtime at various points during execution. + pub fn event_callback( + mut self, + callback: Box, + ) -> Self { + self.event_callback = Some(callback); + self + } + + /// Manually register an ingredient. + /// + /// Manual ingredient registration is necessary when the `inventory` feature is disabled. + pub fn ingredient(mut self) -> Self { + self.jars.push(ErasedJar::erase::()); + self + } + + /// Construct the [`Storage`] using the provided builder options. + pub fn build(self) -> Storage { + Storage { + handle: StorageHandle::with_jars(self.event_callback, self.jars), + zalsa_local: ZalsaLocal::new(), + } + } +} + #[allow(clippy::undocumented_unsafe_blocks)] // TODO(#697) document safety unsafe impl ZalsaDatabase for T { #[inline(always)] diff --git a/src/sync.rs b/src/sync.rs index b9e2d258..e3472d2d 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -5,40 +5,6 @@ pub mod shim { pub use shuttle::sync::*; pub use shuttle::{thread, thread_local}; - pub mod papaya { - use std::hash::{BuildHasher, Hash}; - use std::marker::PhantomData; - - pub struct HashMap(super::Mutex>); - - impl Default for HashMap { - fn default() -> Self { - Self(super::Mutex::default()) - } - } - - pub struct LocalGuard<'a>(PhantomData<&'a ()>); - - impl HashMap - where - K: Eq + Hash, - V: Clone, - S: BuildHasher, - { - pub fn guard(&self) -> LocalGuard<'_> { - LocalGuard(PhantomData) - } - - pub fn get(&self, key: &K, _guard: &LocalGuard<'_>) -> Option { - self.0.lock().get(key).cloned() - } - - pub fn insert(&self, key: K, value: V, _guard: &LocalGuard<'_>) { - self.0.lock().insert(key, value); - } - } - } - /// A wrapper around shuttle's `Mutex` to mirror parking-lot's API. #[derive(Default, Debug)] pub struct Mutex(shuttle::sync::Mutex); @@ -57,24 +23,6 @@ pub mod shim { } } - /// A wrapper around shuttle's `RwLock` to mirror parking-lot's API. - #[derive(Default, Debug)] - pub struct RwLock(shuttle::sync::RwLock); - - impl RwLock { - pub fn read(&self) -> RwLockReadGuard<'_, T> { - self.0.read().unwrap() - } - - pub fn write(&self) -> RwLockWriteGuard<'_, T> { - self.0.write().unwrap() - } - - pub fn get_mut(&mut self) -> &mut T { - self.0.get_mut().unwrap() - } - } - /// A wrapper around shuttle's `Condvar` to mirror parking-lot's API. #[derive(Default, Debug)] pub struct Condvar(shuttle::sync::Condvar); @@ -164,7 +112,7 @@ pub mod shim { #[cfg(not(feature = "shuttle"))] pub mod shim { - pub use parking_lot::{Mutex, MutexGuard, RwLock}; + pub use parking_lot::{Mutex, MutexGuard}; pub use std::sync::*; pub use std::{thread, thread_local}; @@ -173,48 +121,6 @@ pub mod shim { pub use std::sync::atomic::*; } - pub mod papaya { - use std::hash::{BuildHasher, Hash}; - - pub use papaya::LocalGuard; - - pub struct HashMap(papaya::HashMap); - - impl Default for HashMap { - fn default() -> Self { - Self( - papaya::HashMap::builder() - .capacity(256) // A relatively large capacity to hopefully avoid resizing. - .resize_mode(papaya::ResizeMode::Blocking) - .hasher(S::default()) - .build(), - ) - } - } - - impl HashMap - where - K: Eq + Hash, - V: Clone, - S: BuildHasher, - { - #[inline] - pub fn guard(&self) -> LocalGuard<'_> { - self.0.guard() - } - - #[inline] - pub fn get(&self, key: &K, guard: &LocalGuard<'_>) -> Option { - self.0.get(key, guard).cloned() - } - - #[inline] - pub fn insert(&self, key: K, value: V, guard: &LocalGuard<'_>) { - self.0.insert(key, value, guard); - } - } - } - /// A wrapper around parking-lot's `Condvar` to mirror shuttle's API. pub struct Condvar(parking_lot::Condvar); diff --git a/src/table/memo.rs b/src/table/memo.rs index 2ee10134..63d3671a 100644 --- a/src/table/memo.rs +++ b/src/table/memo.rs @@ -3,19 +3,32 @@ use std::fmt::Debug; use std::mem; use std::ptr::{self, NonNull}; -use portable_atomic::hint::spin_loop; -use thin_vec::ThinVec; - use crate::sync::atomic::{AtomicPtr, Ordering}; -use crate::sync::{OnceLock, RwLock}; use crate::{zalsa::MemoIngredientIndex, zalsa_local::QueryOriginRef}; /// The "memo table" stores the memoized results of tracked function calls. /// Every tracked function must take a salsa struct as its first argument /// and memo tables are attached to those salsa structs as auxiliary data. -#[derive(Default)] pub(crate) struct MemoTable { - memos: RwLock>, + memos: Box<[MemoEntry]>, +} + +impl MemoTable { + /// Create a `MemoTable` with slots for memos from the provided `MemoTableTypes`. + pub fn new(types: &MemoTableTypes) -> Self { + Self { + memos: (0..types.len()).map(|_| MemoEntry::default()).collect(), + } + } + + /// Reset any memos in the table. + /// + /// Note that the memo entries should be freed manually before calling this function. + pub fn reset(&mut self) { + for memo in &mut self.memos { + *memo = MemoEntry::default(); + } + } } pub trait Memo: Any + Send + Sync { @@ -50,13 +63,8 @@ struct MemoEntry { atomic_memo: AtomicPtr, } -#[derive(Default)] -pub struct MemoEntryType { - data: OnceLock, -} - #[derive(Clone, Copy, Debug)] -struct MemoEntryTypeData { +pub struct MemoEntryType { /// The `type_id` of the erased memo type `M` type_id: TypeId, @@ -89,17 +97,10 @@ impl MemoEntryType { #[inline] pub fn of() -> Self { Self { - data: OnceLock::from(MemoEntryTypeData { - type_id: TypeId::of::(), - to_dyn_fn: Self::to_dyn_fn::(), - }), + type_id: TypeId::of::(), + to_dyn_fn: Self::to_dyn_fn::(), } } - - #[inline] - fn load(&self) -> Option<&MemoEntryTypeData> { - self.data.get() - } } /// Dummy placeholder type that we use when erasing the memo type `M` in [`MemoEntryData`][]. @@ -127,43 +128,21 @@ impl Memo for DummyMemo { #[derive(Default)] pub struct MemoTableTypes { - types: boxcar::Vec, + types: Vec, } impl MemoTableTypes { pub(crate) fn set( - &self, + &mut self, memo_ingredient_index: MemoIngredientIndex, - memo_type: &MemoEntryType, + memo_type: MemoEntryType, ) { - let memo_ingredient_index = memo_ingredient_index.as_usize(); + self.types + .insert(memo_ingredient_index.as_usize(), memo_type); + } - // Try to create our entry if it has not already been created. - if memo_ingredient_index >= self.types.count() { - while self.types.push(MemoEntryType::default()) < memo_ingredient_index {} - } - - loop { - let Some(memo_entry_type) = self.types.get(memo_ingredient_index) else { - // It's possible that someone else began pushing to our index but has not - // completed the entry's initialization yet, as `boxcar` is lock-free. This - // is extremely unlikely given initialization is just a handful of instructions. - // Additionally, this function is generally only called on startup, so we can - // just spin here. - spin_loop(); - continue; - }; - - memo_entry_type - .data - .set( - *memo_type.data.get().expect( - "cannot provide an empty `MemoEntryType` for `MemoEntryType::set()`", - ), - ) - .expect("memo type should only be set once"); - break; - } + pub fn len(&self) -> usize { + self.types.len() } /// # Safety @@ -204,59 +183,25 @@ impl MemoTableWithTypes<'_> { assert_eq!( self.types .types - .get(memo_ingredient_index.as_usize()) - .and_then(MemoEntryType::load)? + .get(memo_ingredient_index.as_usize())? .type_id, TypeId::of::(), "inconsistent type-id for `{memo_ingredient_index:?}`" ); - // If the memo slot is already occupied, it must already have the - // right type info etc, and we only need the read-lock. - if let Some(MemoEntry { atomic_memo }) = self + // The memo table is pre-sized on creation based on the corresponding `MemoTableTypes`. + let MemoEntry { atomic_memo } = self .memos .memos - .read() .get(memo_ingredient_index.as_usize()) - { - let old_memo = - atomic_memo.swap(MemoEntryType::to_dummy(memo).as_ptr(), Ordering::AcqRel); + .expect("accessed memo table with invalid index"); - let old_memo = NonNull::new(old_memo); + let old_memo = atomic_memo.swap(MemoEntryType::to_dummy(memo).as_ptr(), Ordering::AcqRel); - // SAFETY: `type_id` check asserted above - return old_memo.map(|old_memo| unsafe { MemoEntryType::from_dummy(old_memo) }); - } + let old_memo = NonNull::new(old_memo); - // Otherwise we need the write lock. - self.insert_cold(memo_ingredient_index, memo) - } - - #[cold] - fn insert_cold( - self, - memo_ingredient_index: MemoIngredientIndex, - memo: NonNull, - ) -> Option> { - let memo_ingredient_index = memo_ingredient_index.as_usize(); - let mut memos = self.memos.memos.write(); - - // Grow the table if needed. - if memos.len() <= memo_ingredient_index { - let additional_len = memo_ingredient_index - memos.len() + 1; - memos.reserve(additional_len); - while memos.len() <= memo_ingredient_index { - memos.push(MemoEntry::default()); - } - } - - let old_entry = mem::replace( - memos[memo_ingredient_index].atomic_memo.get_mut(), - MemoEntryType::to_dummy(memo).as_ptr(), - ); - - // SAFETY: The `TypeId` is asserted in `insert()`. - NonNull::new(old_entry).map(|memo| unsafe { MemoEntryType::from_dummy(memo) }) + // SAFETY: `type_id` check asserted above + old_memo.map(|old_memo| unsafe { MemoEntryType::from_dummy(old_memo) }) } #[inline] @@ -264,13 +209,8 @@ impl MemoTableWithTypes<'_> { self, memo_ingredient_index: MemoIngredientIndex, ) -> Option> { - let read = self.memos.memos.read(); - let memo = read.get(memo_ingredient_index.as_usize())?; - let type_ = self - .types - .types - .get(memo_ingredient_index.as_usize()) - .and_then(MemoEntryType::load)?; + let memo = self.memos.memos.get(memo_ingredient_index.as_usize())?; + let type_ = self.types.types.get(memo_ingredient_index.as_usize())?; assert_eq!( type_.type_id, TypeId::of::(), @@ -284,13 +224,12 @@ impl MemoTableWithTypes<'_> { #[cfg(feature = "salsa_unstable")] pub(crate) fn memory_usage(&self) -> Vec { let mut memory_usage = Vec::new(); - let memos = self.memos.memos.read(); - for (index, memo) in memos.iter().enumerate() { + for (index, memo) in self.memos.memos.iter().enumerate() { let Some(memo) = NonNull::new(memo.atomic_memo.load(Ordering::Acquire)) else { continue; }; - let Some(type_) = self.types.types.get(index).and_then(MemoEntryType::load) else { + let Some(type_) = self.types.types.get(index) else { continue; }; @@ -317,12 +256,7 @@ impl MemoTableWithTypesMut<'_> { memo_ingredient_index: MemoIngredientIndex, f: impl FnOnce(&mut M), ) { - let Some(type_) = self - .types - .types - .get(memo_ingredient_index.as_usize()) - .and_then(MemoEntryType::load) - else { + let Some(type_) = self.types.types.get(memo_ingredient_index.as_usize()) else { return; }; assert_eq!( @@ -331,13 +265,13 @@ impl MemoTableWithTypesMut<'_> { "inconsistent type-id for `{memo_ingredient_index:?}`" ); - // If the memo slot is already occupied, it must already have the - // right type info etc, and we only need the read-lock. - let memos = self.memos.memos.get_mut(); - let Some(MemoEntry { atomic_memo }) = memos.get_mut(memo_ingredient_index.as_usize()) + // The memo table is pre-sized on creation based on the corresponding `MemoTableTypes`. + let Some(MemoEntry { atomic_memo }) = + self.memos.memos.get_mut(memo_ingredient_index.as_usize()) else { return; }; + let Some(memo) = NonNull::new(*atomic_memo.get_mut()) else { return; }; @@ -357,7 +291,7 @@ impl MemoTableWithTypesMut<'_> { #[inline] pub unsafe fn drop(&mut self) { let types = self.types.types.iter(); - for ((_, type_), memo) in std::iter::zip(types, self.memos.memos.get_mut()) { + for (type_, memo) in std::iter::zip(types, &mut self.memos.memos) { // SAFETY: The types match as per our constructor invariant. unsafe { memo.take(type_) }; } @@ -371,12 +305,12 @@ impl MemoTableWithTypesMut<'_> { &mut self, mut f: impl FnMut(MemoIngredientIndex, Box), ) { - let memos = self.memos.memos.get_mut(); - memos + self.memos + .memos .iter_mut() .zip(self.types.types.iter()) .enumerate() - .filter_map(|(index, (memo, (_, type_)))| { + .filter_map(|(index, (memo, type_))| { // SAFETY: The types match as per our constructor invariant. let memo = unsafe { memo.take(type_)? }; Some((MemoIngredientIndex::from_usize(index), memo)) @@ -393,7 +327,6 @@ impl MemoEntry { unsafe fn take(&mut self, type_: &MemoEntryType) -> Option> { let memo = mem::replace(self.atomic_memo.get_mut(), ptr::null_mut()); let memo = NonNull::new(memo)?; - let type_ = type_.load()?; // SAFETY: Our preconditions. Some(unsafe { Box::from_raw((type_.to_dyn_fn)(memo).as_ptr()) }) } diff --git a/src/tracked_struct.rs b/src/tracked_struct.rs index ef7f9926..fd93250a 100644 --- a/src/tracked_struct.rs +++ b/src/tracked_struct.rs @@ -110,9 +110,8 @@ impl Default for JarImpl { impl Jar for JarImpl { fn create_ingredients( - _zalsa: &Zalsa, + _zalsa: &mut Zalsa, struct_index: crate::zalsa::IngredientIndex, - _dependencies: crate::memo_ingredient_indices::IngredientIndices, ) -> Vec> { let struct_ingredient = >::new(struct_index); @@ -444,7 +443,7 @@ where // lifetime erase for storage fields: unsafe { mem::transmute::, C::Fields<'static>>(fields) }, revisions: C::new_revisions(current_deps.changed_at), - memos: Default::default(), + memos: MemoTable::new(self.memo_table_types()), }; while let Some(id) = self.free_list.pop() { @@ -601,11 +600,11 @@ where // Note that we hold the lock and have exclusive access to the tracked struct data, // so there should be no live instances of IDs from the previous generation. We clear // the memos and return a new ID here as if we have allocated a new slot. - let mut table = data.take_memo_table(); + let memo_table = data.memo_table_mut(); // SAFETY: The memo table belongs to a value that we allocated, so it has the // correct type. - unsafe { self.clear_memos(zalsa, &mut table, id) }; + unsafe { self.clear_memos(zalsa, memo_table, id) }; id = id .next_generation() @@ -674,11 +673,11 @@ where // SAFETY: We have acquired the write lock let data = unsafe { &mut *data_raw }; - let mut memo_table = data.take_memo_table(); + let memo_table = data.memo_table_mut(); // SAFETY: The memo table belongs to a value that we allocated, so it // has the correct type. - unsafe { self.clear_memos(zalsa, &mut memo_table, id) }; + unsafe { self.clear_memos(zalsa, memo_table, id) }; // now that all cleanup has occurred, make available for re-use self.free_list.push(id); @@ -724,6 +723,9 @@ where }; mem::forget(table_guard); + + // Reset the table after having dropped any memos. + memo_table.reset(); } /// Return reference to the field data ignoring dependency tracking. @@ -849,8 +851,12 @@ where C::DEBUG_NAME } - fn memo_table_types(&self) -> Arc { - self.memo_table_types.clone() + fn memo_table_types(&self) -> &Arc { + &self.memo_table_types + } + + fn memo_table_types_mut(&mut self) -> &mut Arc { + &mut self.memo_table_types } /// Returns memory usage information about any tracked structs. @@ -891,13 +897,12 @@ where unsafe { mem::transmute::<&C::Fields<'static>, &C::Fields<'_>>(&self.fields) } } - fn take_memo_table(&mut self) -> MemoTable { + fn memo_table_mut(&mut self) -> &mut MemoTable { // This fn is only called after `updated_at` has been set to `None`; // this ensures that there is no concurrent access // (and that the `&mut self` is accurate...). assert!(self.updated_at.load().is_none()); - - mem::take(&mut self.memos) + &mut self.memos } fn read_lock(&self, current_revision: Revision) { diff --git a/src/tracked_struct/tracked_field.rs b/src/tracked_struct/tracked_field.rs index 5ec38c68..ad3e871e 100644 --- a/src/tracked_struct/tracked_field.rs +++ b/src/tracked_struct/tracked_field.rs @@ -82,7 +82,11 @@ where C::TRACKED_FIELD_NAMES[self.field_index] } - fn memo_table_types(&self) -> Arc { + fn memo_table_types(&self) -> &Arc { + unreachable!("tracked field does not allocate pages") + } + + fn memo_table_types_mut(&mut self) -> &mut Arc { unreachable!("tracked field does not allocate pages") } } diff --git a/src/views.rs b/src/views.rs index a1485289..01a0a2de 100644 --- a/src/views.rs +++ b/src/views.rs @@ -80,16 +80,16 @@ impl Views { } /// Add a new downcaster from `dyn Database` to `dyn DbView`. - pub fn add(&self, func: DatabaseDownCasterSig) { - let target_type_id = TypeId::of::(); - if self - .view_casters - .iter() - .any(|(_, u)| u.target_type_id == target_type_id) - { - return; + pub fn add( + &self, + func: DatabaseDownCasterSig, + ) -> DatabaseDownCaster { + if let Some(view) = self.try_downcaster_for() { + return view; } + self.view_casters.push(ViewCaster::new::(func)); + DatabaseDownCaster(self.source_type_id, func) } /// Retrieve an downcaster function from `dyn Database` to `dyn DbView`. @@ -98,23 +98,31 @@ impl Views { /// /// If the underlying type of `db` is not the same as the database type this upcasts was created for. pub fn downcaster_for(&self) -> DatabaseDownCaster { + self.try_downcaster_for().unwrap_or_else(|| { + panic!( + "No downcaster registered for type `{}` in `Views`", + std::any::type_name::(), + ) + }) + } + + /// Retrieve an downcaster function from `dyn Database` to `dyn DbView`, if it exists. + #[inline] + pub fn try_downcaster_for(&self) -> Option> { let view_type_id = TypeId::of::(); - for (_idx, view) in self.view_casters.iter() { + for (_, view) in self.view_casters.iter() { if view.target_type_id == view_type_id { // SAFETY: We are unerasing the type erased function pointer having made sure the - // TypeId matches. - return DatabaseDownCaster(self.source_type_id, unsafe { + // `TypeId` matches. + return Some(DatabaseDownCaster(self.source_type_id, unsafe { std::mem::transmute::>( view.cast, ) - }); + })); } } - panic!( - "No downcaster registered for type `{}` in `Views`", - std::any::type_name::(), - ); + None } } diff --git a/src/zalsa.rs b/src/zalsa.rs index cc23881c..c1c46296 100644 --- a/src/zalsa.rs +++ b/src/zalsa.rs @@ -1,18 +1,13 @@ use std::any::{Any, TypeId}; use std::hash::BuildHasherDefault; -use std::marker::PhantomData; -use std::mem; -use std::num::NonZeroU32; use std::panic::RefUnwindSafe; +use hashbrown::HashMap; use rustc_hash::FxHashMap; use crate::hash::TypeIdHasher; use crate::ingredient::{Ingredient, Jar}; -use crate::nonce::{Nonce, NonceGenerator}; use crate::runtime::Runtime; -use crate::sync::atomic::{AtomicU64, Ordering}; -use crate::sync::{papaya, Mutex, RwLock}; use crate::table::memo::MemoTableWithTypes; use crate::table::Table; use crate::views::Views; @@ -62,13 +57,14 @@ pub unsafe trait ZalsaDatabase: Any { pub fn views(db: &Db) -> &Views { db.zalsa().views() } - /// Nonce type representing the underlying database storage. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg(not(feature = "inventory"))] pub struct StorageNonce; // Generator for storage nonces. -static NONCE: NonceGenerator = NonceGenerator::new(); +#[cfg(not(feature = "inventory"))] +static NONCE: crate::nonce::NonceGenerator = crate::nonce::NonceGenerator::new(); /// An ingredient index identifies a particular [`Ingredient`] in the database. /// @@ -83,10 +79,16 @@ impl IngredientIndex { /// This reserves one bit for an optional tag. const MAX_INDEX: u32 = 0x7FFF_FFFF; - /// Create an ingredient index from a `usize`. - pub(crate) fn from(v: usize) -> Self { - assert!(v <= Self::MAX_INDEX as usize); - Self(v as u32) + /// Create an ingredient index from a `u32`. + pub(crate) fn from(v: u32) -> Self { + assert!(v <= Self::MAX_INDEX); + Self(v) + } + + /// Create an ingredient index from a `u32`, without performing validating + /// that the index is valid. + pub(crate) fn from_unchecked(v: u32) -> Self { + Self(v) } /// Convert the ingredient index back into a `u32`. @@ -134,28 +136,24 @@ impl MemoIngredientIndex { pub struct Zalsa { views_of: Views, - nonce: Nonce, + #[cfg(not(feature = "inventory"))] + nonce: crate::nonce::Nonce, /// Map from the [`IngredientIndex::as_usize`][] of a salsa struct to a list of /// [ingredient-indices](`IngredientIndex`) for tracked functions that have this salsa struct /// as input. - memo_ingredient_indices: RwLock>>, + memo_ingredient_indices: Vec>, /// Map from the type-id of an `impl Jar` to the index of its first ingredient. - jar_map: papaya::HashMap>, - - /// The write-lock for `jar_map`. - jar_map_lock: Mutex<()>, + jar_map: HashMap>, /// A map from the `IngredientIndex` to the `TypeId` of its ID struct. /// /// Notably this is not the reverse mapping of `jar_map`. - ingredient_to_id_struct_type_id_map: RwLock>, + ingredient_to_id_struct_type_id_map: FxHashMap, /// Vector of ingredients. - /// - /// Immutable unless the mutex on `ingredients_map` is held. - ingredients_vec: boxcar::Vec>, + ingredients_vec: Vec>, /// Indices of ingredients that require reset when a new revision starts. ingredients_requiring_reset: boxcar::Vec, @@ -177,22 +175,43 @@ impl RefUnwindSafe for Zalsa {} impl Zalsa { pub(crate) fn new( event_callback: Option>, + jars: Vec, ) -> Self { - Self { + let mut zalsa = Self { views_of: Views::new::(), - nonce: NONCE.nonce(), - jar_map: papaya::HashMap::default(), - jar_map_lock: Mutex::default(), + jar_map: HashMap::default(), ingredient_to_id_struct_type_id_map: Default::default(), - ingredients_vec: boxcar::Vec::new(), + ingredients_vec: Vec::new(), ingredients_requiring_reset: boxcar::Vec::new(), runtime: Runtime::default(), memo_ingredient_indices: Default::default(), event_callback, + #[cfg(not(feature = "inventory"))] + nonce: NONCE.nonce(), + }; + + // Collect and initialize all registered ingredients. + #[cfg(feature = "inventory")] + let mut jars = inventory::iter::() + .copied() + .chain(jars) + .collect::>(); + + #[cfg(not(feature = "inventory"))] + let mut jars = jars; + + // Ensure structs are initialized before tracked functions. + jars.sort_by_key(|jar| jar.kind); + + for jar in jars { + zalsa.insert_jar(jar); } + + zalsa } - pub(crate) fn nonce(&self) -> Nonce { + #[cfg(not(feature = "inventory"))] + pub(crate) fn nonce(&self) -> crate::nonce::Nonce { self.nonce } @@ -218,7 +237,7 @@ impl Zalsa { } #[inline] - pub(crate) fn lookup_ingredient(&self, index: IngredientIndex) -> &dyn Ingredient { + pub fn lookup_ingredient(&self, index: IngredientIndex) -> &dyn Ingredient { let index = index.as_u32() as usize; self.ingredients_vec .get(index) @@ -231,7 +250,7 @@ impl Zalsa { struct_ingredient_index: IngredientIndex, memo_ingredient_index: MemoIngredientIndex, ) -> IngredientIndex { - self.memo_ingredient_indices.read()[struct_ingredient_index.as_u32() as usize] + self.memo_ingredient_indices[struct_ingredient_index.as_u32() as usize] [memo_ingredient_index.as_usize()] } @@ -239,7 +258,7 @@ impl Zalsa { pub(crate) fn ingredients(&self) -> impl Iterator { self.ingredients_vec .iter() - .map(|(_, ingredient)| ingredient.as_ref()) + .map(|ingredient| ingredient.as_ref()) } /// Starts unwinding the stack if the current revision is cancelled. @@ -259,11 +278,11 @@ impl Zalsa { } pub(crate) fn next_memo_ingredient_index( - &self, + &mut self, struct_ingredient_index: IngredientIndex, ingredient_index: IngredientIndex, ) -> MemoIngredientIndex { - let mut memo_ingredients = self.memo_ingredient_indices.write(); + let memo_ingredients = &mut self.memo_ingredient_indices; let idx = struct_ingredient_index.as_u32() as usize; let memo_ingredients = if let Some(memo_ingredients) = memo_ingredients.get_mut(idx) { memo_ingredients @@ -291,7 +310,6 @@ impl Zalsa { let ingredient_index = self.ingredient_index(id); *self .ingredient_to_id_struct_type_id_map - .read() .get(&ingredient_index) .expect("should have the ingredient index available") } @@ -299,44 +317,36 @@ impl Zalsa { /// **NOT SEMVER STABLE** #[doc(hidden)] #[inline] - pub fn lookup_jar_by_type(&self) -> JarEntry<'_, J> { + pub fn lookup_jar_by_type(&self) -> IngredientIndex { let jar_type_id = TypeId::of::(); - let guard = self.jar_map.guard(); - match self.jar_map.get(&jar_type_id, &guard) { - Some(index) => JarEntry::Occupied(index), - None => JarEntry::Vacant { - guard, - zalsa: self, - _jar: PhantomData, - }, - } + *self.jar_map.get(&jar_type_id).unwrap_or_else(|| { + panic!( + "ingredient `{}` was not registered", + std::any::type_name::() + ) + }) } - #[cold] - #[inline(never)] - fn add_or_lookup_jar_by_type(&self, guard: &papaya::LocalGuard<'_>) -> IngredientIndex { - let jar_type_id = TypeId::of::(); - let dependencies = J::create_dependencies(self); + fn insert_jar(&mut self, jar: ErasedJar) { + let jar_type_id = (jar.type_id)(); - let jar_map_lock = self.jar_map_lock.lock(); + let index = IngredientIndex::from(self.ingredients_vec.len() as u32); - let index = IngredientIndex::from(self.ingredients_vec.count()); + if self.jar_map.contains_key(&jar_type_id) { + return; + } - // Someone made it earlier than us. - if let Some(index) = self.jar_map.get(&jar_type_id, guard) { - return index; - }; - - let ingredients = J::create_ingredients(self, index, dependencies); + let ingredients = (jar.create_ingredients)(self, index); for ingredient in ingredients { let expected_index = ingredient.ingredient_index(); - if ingredient.requires_reset_for_new_revision() { self.ingredients_requiring_reset.push(expected_index); } - let actual_index = self.ingredients_vec.push(ingredient); + self.ingredients_vec.push(ingredient); + + let actual_index = self.ingredients_vec.len() - 1; assert_eq!( expected_index.as_u32() as usize, actual_index, @@ -347,17 +357,10 @@ impl Zalsa { ); } - // Insert the index after all ingredients are inserted to avoid exposing - // partially initialized jars to readers. - self.jar_map.insert(jar_type_id, index, guard); - - drop(jar_map_lock); + self.jar_map.insert(jar_type_id, index); self.ingredient_to_id_struct_type_id_map - .write() - .insert(index, J::id_struct_type_id()); - - index + .insert(index, (jar.id_struct_type_id)()); } /// **NOT SEMVER STABLE** @@ -434,139 +437,69 @@ impl Zalsa { } } -pub enum JarEntry<'a, J> { - Occupied(IngredientIndex), - Vacant { - zalsa: &'a Zalsa, - guard: papaya::LocalGuard<'a>, - _jar: PhantomData, - }, +/// A type-erased `Jar`, used for ingredient registration. +#[derive(Clone, Copy)] +pub struct ErasedJar { + kind: JarKind, + type_id: fn() -> TypeId, + id_struct_type_id: fn() -> TypeId, + create_ingredients: fn(&mut Zalsa, IngredientIndex) -> Vec>, } -impl JarEntry<'_, J> -where - J: Jar, -{ - #[inline] - pub fn get(&self) -> Option { - match *self { - JarEntry::Occupied(index) => Some(index), - JarEntry::Vacant { .. } => None, - } - } +/// The kind of an `Jar`. +/// +/// Note that the ordering of the variants is important. Struct ingredients must be +/// initialized before tracked functions, as tracked function ingredients depend on +/// their input struct. +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)] +pub enum JarKind { + /// An input/tracked/interned struct. + Struct, - #[inline] - pub fn get_or_create(&self) -> IngredientIndex { - match self { - JarEntry::Occupied(index) => *index, - JarEntry::Vacant { zalsa, guard, _jar } => zalsa.add_or_lookup_jar_by_type::(guard), - } - } + /// A tracked function. + TrackedFn, } -/// Caches a pointer to an ingredient in a database. -/// Optimized for the case of a single database. -pub struct IngredientCache -where - I: Ingredient, -{ - // A packed representation of `Option<(Nonce, IngredientIndex)>`. - // - // This allows us to replace a lock in favor of an atomic load. This works thanks to `Nonce` - // having a niche, which means the entire type can fit into an `AtomicU64`. - cached_data: AtomicU64, - phantom: PhantomData I>, -} - -impl Default for IngredientCache -where - I: Ingredient, -{ - fn default() -> Self { - Self::new() - } -} - -impl IngredientCache -where - I: Ingredient, -{ - const UNINITIALIZED: u64 = 0; - - /// Create a new cache - pub const fn new() -> Self { +impl ErasedJar { + /// Performs type-erasure of a given ingredient. + pub const fn erase() -> Self { Self { - cached_data: AtomicU64::new(Self::UNINITIALIZED), - phantom: PhantomData, + kind: I::KIND, + type_id: TypeId::of::, + create_ingredients: ::create_ingredients, + id_struct_type_id: ::id_struct_type_id, } } - - /// Get a reference to the ingredient in the database. - /// If the ingredient is not already in the cache, it will be created. - #[inline(always)] - pub fn get_or_create<'db>( - &self, - zalsa: &'db Zalsa, - create_index: impl Fn() -> IngredientIndex, - ) -> &'db I { - let index = self.get_or_create_index(zalsa, create_index); - zalsa.lookup_ingredient(index).assert_type::() - } - - /// Get a reference to the ingredient in the database. - /// If the ingredient is not already in the cache, it will be created. - #[inline(always)] - pub fn get_or_create_index( - &self, - zalsa: &Zalsa, - create_index: impl Fn() -> IngredientIndex, - ) -> IngredientIndex { - const _: () = assert!( - mem::size_of::<(Nonce, IngredientIndex)>() == mem::size_of::() - ); - let cached_data = self.cached_data.load(Ordering::Acquire); - if cached_data == Self::UNINITIALIZED { - #[cold] - #[inline(never)] - fn get_or_create_index_slow( - this: &IngredientCache, - zalsa: &Zalsa, - create_index: impl Fn() -> IngredientIndex, - ) -> IngredientIndex { - let index = create_index(); - let nonce = zalsa.nonce().into_u32().get() as u64; - let packed = (nonce << u32::BITS) | (index.as_u32() as u64); - debug_assert_ne!(packed, IngredientCache::::UNINITIALIZED); - - // Discard the result, whether we won over the cache or not does not matter - // we know that something has been cached now - _ = this.cached_data.compare_exchange( - IngredientCache::::UNINITIALIZED, - packed, - Ordering::Release, - Ordering::Acquire, - ); - // and we already have our index computed so we can just use that - index - } - - return get_or_create_index_slow(self, zalsa, create_index); - }; - - // unpack our u64 - // SAFETY: We've checked against `UNINITIALIZED` (0) above and so the upper bits must be non-zero - let nonce = Nonce::::from_u32(unsafe { - NonZeroU32::new_unchecked((cached_data >> u32::BITS) as u32) - }); - let mut index = IngredientIndex(cached_data as u32); - - if zalsa.nonce() != nonce { - index = create_index(); - } - index - } } +/// A salsa ingredient that can be registered in the database. +/// +/// This trait is implemented for tracked functions and salsa structs. +pub trait HasJar { + /// The [`Jar`] associated with this ingredient. + type Jar: Jar; + + /// The [`JarKind`] for `Self::Jar`. + const KIND: JarKind; +} + +// Collect jars statically at compile-time if supported. +#[cfg(feature = "inventory")] +inventory::collect!(ErasedJar); + +#[cfg(feature = "inventory")] +pub use inventory::submit as register_jar; + +#[cfg(not(feature = "inventory"))] +#[macro_export] +#[doc(hidden)] +macro_rules! register_jar { + ($($_:tt)*) => {}; +} + +#[cfg(not(feature = "inventory"))] +pub use crate::register_jar; + /// Given a wide pointer `T`, extracts the data pointer (typed as `U`). /// /// # Safety diff --git a/src/zalsa_local.rs b/src/zalsa_local.rs index 80e24e7f..51c28c9c 100644 --- a/src/zalsa_local.rs +++ b/src/zalsa_local.rs @@ -754,7 +754,7 @@ impl QueryOrigin { QueryOriginKind::Assigned => { // SAFETY: `data.index` is initialized when the tag is `QueryOriginKind::Assigned`. let index = unsafe { self.data.index }; - let ingredient_index = IngredientIndex::from(self.metadata as usize); + let ingredient_index = IngredientIndex::from(self.metadata); QueryOriginRef::Assigned(DatabaseKeyIndex::new(ingredient_index, index)) } diff --git a/tests/accumulate-chain.rs b/tests/accumulate-chain.rs index d51e67c1..18d4bb56 100644 --- a/tests/accumulate-chain.rs +++ b/tests/accumulate-chain.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that when having nested tracked functions //! we don't drop any values when accumulating. diff --git a/tests/accumulate-custom-debug.rs b/tests/accumulate-custom-debug.rs index a4c078ab..18015604 100644 --- a/tests/accumulate-custom-debug.rs +++ b/tests/accumulate-custom-debug.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + mod common; use expect_test::expect; diff --git a/tests/accumulate-dag.rs b/tests/accumulate-dag.rs index 6786d0f8..41d9b390 100644 --- a/tests/accumulate-dag.rs +++ b/tests/accumulate-dag.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + mod common; use expect_test::expect; diff --git a/tests/accumulate-execution-order.rs b/tests/accumulate-execution-order.rs index 28aeb5d7..1a0d3e23 100644 --- a/tests/accumulate-execution-order.rs +++ b/tests/accumulate-execution-order.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Demonstrates that accumulation is done in the order //! in which things were originally executed. diff --git a/tests/accumulate-from-tracked-fn.rs b/tests/accumulate-from-tracked-fn.rs index 5fba8a68..67e59168 100644 --- a/tests/accumulate-from-tracked-fn.rs +++ b/tests/accumulate-from-tracked-fn.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Accumulate values from within a tracked function. //! Then mutate the values so that the tracked function re-executes. //! Check that we accumulate the appropriate, new values. diff --git a/tests/accumulate-no-duplicates.rs b/tests/accumulate-no-duplicates.rs index 0907c469..8d21281e 100644 --- a/tests/accumulate-no-duplicates.rs +++ b/tests/accumulate-no-duplicates.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that we don't get duplicate accumulated values mod common; diff --git a/tests/accumulate-reuse-workaround.rs b/tests/accumulate-reuse-workaround.rs index 915a14c1..43c5bb3c 100644 --- a/tests/accumulate-reuse-workaround.rs +++ b/tests/accumulate-reuse-workaround.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Demonstrates the workaround of wrapping calls to //! `accumulated` in a tracked function to get better //! reuse. diff --git a/tests/accumulate-reuse.rs b/tests/accumulate-reuse.rs index b7d91887..1e6194de 100644 --- a/tests/accumulate-reuse.rs +++ b/tests/accumulate-reuse.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Accumulator re-use test. //! //! Tests behavior when a query's only inputs diff --git a/tests/accumulate.rs b/tests/accumulate.rs index dcacfb7a..54022a15 100644 --- a/tests/accumulate.rs +++ b/tests/accumulate.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + mod common; use common::{LogDatabase, LoggerDatabase}; use expect_test::expect; diff --git a/tests/accumulated_backdate.rs b/tests/accumulated_backdate.rs index ce8f6580..45759d1b 100644 --- a/tests/accumulated_backdate.rs +++ b/tests/accumulated_backdate.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Tests that accumulated values are correctly accounted for //! when backdating a value. diff --git a/tests/backtrace.rs b/tests/backtrace.rs index c64fd8ae..74124c1a 100644 --- a/tests/backtrace.rs +++ b/tests/backtrace.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use expect_test::expect; use salsa::{Backtrace, Database, DatabaseImpl}; use test_log::test; @@ -71,15 +73,15 @@ fn backtrace_works() { expect![[r#" query stacktrace: 0: query_e(Id(0)) - at tests/backtrace.rs:30 + at tests/backtrace.rs:32 1: query_d(Id(0)) - at tests/backtrace.rs:25 + at tests/backtrace.rs:27 2: query_c(Id(0)) - at tests/backtrace.rs:20 + at tests/backtrace.rs:22 3: query_b(Id(0)) - at tests/backtrace.rs:15 + at tests/backtrace.rs:17 4: query_a(Id(0)) - at tests/backtrace.rs:10 + at tests/backtrace.rs:12 "#]] .assert_eq(&backtrace); @@ -87,15 +89,15 @@ fn backtrace_works() { expect![[r#" query stacktrace: 0: query_e(Id(1)) -> (R1, Durability::LOW) - at tests/backtrace.rs:30 + at tests/backtrace.rs:32 1: query_d(Id(1)) -> (R1, Durability::HIGH) - at tests/backtrace.rs:25 + at tests/backtrace.rs:27 2: query_c(Id(1)) -> (R1, Durability::HIGH) - at tests/backtrace.rs:20 + at tests/backtrace.rs:22 3: query_b(Id(1)) -> (R1, Durability::HIGH) - at tests/backtrace.rs:15 + at tests/backtrace.rs:17 4: query_a(Id(1)) -> (R1, Durability::HIGH) - at tests/backtrace.rs:10 + at tests/backtrace.rs:12 "#]] .assert_eq(&backtrace); @@ -103,12 +105,12 @@ fn backtrace_works() { expect![[r#" query stacktrace: 0: query_e(Id(2)) - at tests/backtrace.rs:30 + at tests/backtrace.rs:32 1: query_cycle(Id(2)) - at tests/backtrace.rs:43 + at tests/backtrace.rs:45 cycle heads: query_cycle(Id(2)) -> IterationCount(0) 2: query_f(Id(2)) - at tests/backtrace.rs:38 + at tests/backtrace.rs:40 "#]] .assert_eq(&backtrace); @@ -116,12 +118,12 @@ fn backtrace_works() { expect![[r#" query stacktrace: 0: query_e(Id(3)) -> (R1, Durability::LOW) - at tests/backtrace.rs:30 + at tests/backtrace.rs:32 1: query_cycle(Id(3)) -> (R1, Durability::HIGH, iteration = IterationCount(0)) - at tests/backtrace.rs:43 + at tests/backtrace.rs:45 cycle heads: query_cycle(Id(3)) -> IterationCount(0) 2: query_f(Id(3)) -> (R1, Durability::HIGH) - at tests/backtrace.rs:38 + at tests/backtrace.rs:40 "#]] .assert_eq(&backtrace); } diff --git a/tests/check_auto_traits.rs b/tests/check_auto_traits.rs index 6e9c62c6..0d314a83 100644 --- a/tests/check_auto_traits.rs +++ b/tests/check_auto_traits.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that auto trait impls exist as expected. use std::panic::UnwindSafe; diff --git a/tests/compile_fail.rs b/tests/compile_fail.rs index 3b4a37fc..c43c0b4d 100644 --- a/tests/compile_fail.rs +++ b/tests/compile_fail.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + #[rustversion::all(stable, since(1.84))] #[test] fn compile_fail() { diff --git a/tests/cycle.rs b/tests/cycle.rs index f18cf92a..28266f2c 100644 --- a/tests/cycle.rs +++ b/tests/cycle.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test cases for fixpoint iteration cycle resolution. //! //! These test cases use a generic query setup that allows constructing arbitrary dependency diff --git a/tests/cycle_accumulate.rs b/tests/cycle_accumulate.rs index d547b576..fa31845d 100644 --- a/tests/cycle_accumulate.rs +++ b/tests/cycle_accumulate.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use std::collections::HashSet; mod common; diff --git a/tests/cycle_fallback_immediate.rs b/tests/cycle_fallback_immediate.rs index b2276720..374978d8 100644 --- a/tests/cycle_fallback_immediate.rs +++ b/tests/cycle_fallback_immediate.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! It is possible to omit the `cycle_fn`, only specifying `cycle_result` in which case //! an immediate fallback value is used as the cycle handling opposed to doing a fixpoint resolution. diff --git a/tests/cycle_initial_call_back_into_cycle.rs b/tests/cycle_initial_call_back_into_cycle.rs index 9dfe39a9..326fd46c 100644 --- a/tests/cycle_initial_call_back_into_cycle.rs +++ b/tests/cycle_initial_call_back_into_cycle.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Calling back into the same cycle from your cycle initial function will trigger another cycle. #[salsa::tracked] diff --git a/tests/cycle_initial_call_query.rs b/tests/cycle_initial_call_query.rs index 4c52fff2..cb10e77e 100644 --- a/tests/cycle_initial_call_query.rs +++ b/tests/cycle_initial_call_query.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! It's possible to call a Salsa query from within a cycle initial fn. #[salsa::tracked] diff --git a/tests/cycle_maybe_changed_after.rs b/tests/cycle_maybe_changed_after.rs index 2759c65f..6ee42d3a 100644 --- a/tests/cycle_maybe_changed_after.rs +++ b/tests/cycle_maybe_changed_after.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Tests for incremental validation for queries involved in a cycle. mod common; diff --git a/tests/cycle_output.rs b/tests/cycle_output.rs index 8a4d13e9..975c8a44 100644 --- a/tests/cycle_output.rs +++ b/tests/cycle_output.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test tracked struct output from a query in a cycle. mod common; use common::{HasLogger, LogDatabase, Logger}; diff --git a/tests/cycle_recovery_call_back_into_cycle.rs b/tests/cycle_recovery_call_back_into_cycle.rs index a4dc5e25..af7c1021 100644 --- a/tests/cycle_recovery_call_back_into_cycle.rs +++ b/tests/cycle_recovery_call_back_into_cycle.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Calling back into the same cycle from your cycle recovery function _can_ work out, as long as //! the overall cycle still converges. diff --git a/tests/cycle_recovery_call_query.rs b/tests/cycle_recovery_call_query.rs index a768017c..dcc31abe 100644 --- a/tests/cycle_recovery_call_query.rs +++ b/tests/cycle_recovery_call_query.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! It's possible to call a Salsa query from within a cycle recovery fn. #[salsa::tracked] diff --git a/tests/cycle_regression_455.rs b/tests/cycle_regression_455.rs index 5beff8d3..99c193ab 100644 --- a/tests/cycle_regression_455.rs +++ b/tests/cycle_regression_455.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use salsa::{Database, Setter}; #[salsa::tracked] diff --git a/tests/cycle_result_dependencies.rs b/tests/cycle_result_dependencies.rs index e7071a02..8e025f99 100644 --- a/tests/cycle_result_dependencies.rs +++ b/tests/cycle_result_dependencies.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use salsa::{Database, Setter}; #[salsa::input] diff --git a/tests/cycle_tracked.rs b/tests/cycle_tracked.rs index 30bf513f..b9ef6ed1 100644 --- a/tests/cycle_tracked.rs +++ b/tests/cycle_tracked.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Tests for cycles where the cycle head is stored on a tracked struct //! and that tracked struct is freed in a later revision. diff --git a/tests/cycle_tracked_own_input.rs b/tests/cycle_tracked_own_input.rs index e8e520f4..17e8b815 100644 --- a/tests/cycle_tracked_own_input.rs +++ b/tests/cycle_tracked_own_input.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test for cycle handling where a tracked struct created in the first revision //! is stored in the final value of the cycle but isn't recreated in the second //! iteration of the creating query. diff --git a/tests/dataflow.rs b/tests/dataflow.rs index f973e970..960cc33f 100644 --- a/tests/dataflow.rs +++ b/tests/dataflow.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test case for fixpoint iteration cycle resolution. //! //! This test case is intended to simulate a (very simplified) version of a real dataflow analysis diff --git a/tests/debug.rs b/tests/debug.rs index 03b59dca..da184b40 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that `DeriveWithDb` is correctly derived. use expect_test::expect; diff --git a/tests/debug_db_contents.rs b/tests/debug_db_contents.rs index 30efb173..6ab8b212 100644 --- a/tests/debug_db_contents.rs +++ b/tests/debug_db_contents.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + #[salsa::interned(debug)] struct InternedStruct<'db> { name: String, diff --git a/tests/deletion-cascade.rs b/tests/deletion-cascade.rs index 1e02c42f..6a17fe93 100644 --- a/tests/deletion-cascade.rs +++ b/tests/deletion-cascade.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Delete cascade: //! //! * when we delete memoized data, also delete outputs from that data diff --git a/tests/deletion-drops.rs b/tests/deletion-drops.rs index 52b0b512..6ce9e6a2 100644 --- a/tests/deletion-drops.rs +++ b/tests/deletion-drops.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Basic deletion test: //! //! * entities not created in a revision are deleted, as is any memoized data keyed on them. diff --git a/tests/deletion.rs b/tests/deletion.rs index c7c415e2..6202ea03 100644 --- a/tests/deletion.rs +++ b/tests/deletion.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Basic deletion test: //! //! * entities not created in a revision are deleted, as is any memoized data keyed on them. diff --git a/tests/derive_update.rs b/tests/derive_update.rs index e07d1783..4ce04cb7 100644 --- a/tests/derive_update.rs +++ b/tests/derive_update.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that the `Update` derive works as expected #[derive(salsa::Update)] diff --git a/tests/durability.rs b/tests/durability.rs index 3a8e244e..a39e3065 100644 --- a/tests/durability.rs +++ b/tests/durability.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Tests that code using the builder's durability methods compiles. use salsa::{Database, Durability, Setter}; diff --git a/tests/elided-lifetime-in-tracked-fn.rs b/tests/elided-lifetime-in-tracked-fn.rs index 4979c2ee..81aa3a5f 100644 --- a/tests/elided-lifetime-in-tracked-fn.rs +++ b/tests/elided-lifetime-in-tracked-fn.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. diff --git a/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs b/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs index cc0f0980..7a29d804 100644 --- a/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs +++ b/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that if field X of a tracked struct changes but not field Y, //! functions that depend on X re-execute, but those depending only on Y do not //! compiles and executes successfully. diff --git a/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs b/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs index c4a6ba56..218d875b 100644 --- a/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs +++ b/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that if field X of an input changes but not field Y, //! functions that depend on X re-execute, but those depending only on Y do not //! compiles and executes successfully. diff --git a/tests/hash_collision.rs b/tests/hash_collision.rs index 4efadfa3..f37c2aed 100644 --- a/tests/hash_collision.rs +++ b/tests/hash_collision.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use std::hash::Hash; #[test] diff --git a/tests/hello_world.rs b/tests/hello_world.rs index 4a083648..561fce91 100644 --- a/tests/hello_world.rs +++ b/tests/hello_world.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. diff --git a/tests/input_default.rs b/tests/input_default.rs index 5a4d2bd5..1fef2703 100644 --- a/tests/input_default.rs +++ b/tests/input_default.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Tests that fields attributed with `#[default]` are initialized with `Default::default()`. use salsa::Durability; diff --git a/tests/input_field_durability.rs b/tests/input_field_durability.rs index b65a512e..de15b666 100644 --- a/tests/input_field_durability.rs +++ b/tests/input_field_durability.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Tests that code using the builder's durability methods compiles. use salsa::Durability; diff --git a/tests/input_setter_preserves_durability.rs b/tests/input_setter_preserves_durability.rs index 529418bf..27edc06c 100644 --- a/tests/input_setter_preserves_durability.rs +++ b/tests/input_setter_preserves_durability.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use salsa::plumbing::ZalsaDatabase; use salsa::{Durability, Setter}; use test_log::test; diff --git a/tests/intern_access_in_different_revision.rs b/tests/intern_access_in_different_revision.rs index ff50ae7a..ab8957ca 100644 --- a/tests/intern_access_in_different_revision.rs +++ b/tests/intern_access_in_different_revision.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use salsa::{Durability, Setter}; #[salsa::interned(no_lifetime)] diff --git a/tests/interned-revisions.rs b/tests/interned-revisions.rs index f48393b8..225f24d4 100644 --- a/tests/interned-revisions.rs +++ b/tests/interned-revisions.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. diff --git a/tests/interned-structs.rs b/tests/interned-structs.rs index da9ec6ae..931b1ab6 100644 --- a/tests/interned-structs.rs +++ b/tests/interned-structs.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. diff --git a/tests/interned-structs_self_ref.rs b/tests/interned-structs_self_ref.rs index 69a19fbf..556c4960 100644 --- a/tests/interned-structs_self_ref.rs +++ b/tests/interned-structs_self_ref.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. @@ -35,7 +37,18 @@ struct InternedString<'db>( const _: () = { use salsa::plumbing as zalsa_; use zalsa_::interned as zalsa_struct_; + type Configuration_ = InternedString<'static>; + + impl<'db> zalsa_::HasJar for InternedString<'db> { + type Jar = zalsa_struct_::JarImpl; + const KIND: zalsa_::JarKind = zalsa_::JarKind::Struct; + } + + zalsa_::register_jar! { + zalsa_::ErasedJar::erase::>() + } + #[derive(Clone)] struct StructData<'db>(String, InternedString<'db>); @@ -87,9 +100,7 @@ const _: () = { let zalsa = db.zalsa(); CACHE.get_or_create(zalsa, || { - zalsa - .lookup_jar_by_type::>() - .get_or_create() + zalsa.lookup_jar_by_type::>() }) } } @@ -115,9 +126,8 @@ const _: () = { impl zalsa_::SalsaStructInDb for InternedString<'_> { type MemoIngredientMap = zalsa_::MemoIngredientSingletonIndex; - fn lookup_or_create_ingredient_index(aux: &Zalsa) -> salsa::plumbing::IngredientIndices { + fn lookup_ingredient_index(aux: &Zalsa) -> salsa::plumbing::IngredientIndices { aux.lookup_jar_by_type::>() - .get_or_create() .into() } diff --git a/tests/lru.rs b/tests/lru.rs index e1c11e50..1d417267 100644 --- a/tests/lru.rs +++ b/tests/lru.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn with lru options //! compiles and executes successfully. diff --git a/tests/manual_registration.rs b/tests/manual_registration.rs new file mode 100644 index 00000000..16d87dc5 --- /dev/null +++ b/tests/manual_registration.rs @@ -0,0 +1,92 @@ +#![cfg(not(feature = "inventory"))] + +mod ingredients { + #[salsa::input] + pub(super) struct MyInput { + field: u32, + } + + #[salsa::tracked] + pub(super) struct MyTracked<'db> { + pub(super) field: u32, + } + + #[salsa::interned] + pub(super) struct MyInterned<'db> { + pub(super) field: u32, + } + + #[salsa::tracked] + pub(super) fn intern<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyInterned<'db> { + MyInterned::new(db, input.field(db)) + } + + #[salsa::tracked] + pub(super) fn track<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyTracked<'db> { + MyTracked::new(db, input.field(db)) + } +} + +#[salsa::db] +#[derive(Clone, Default)] +pub struct DatabaseImpl { + storage: salsa::Storage, +} + +#[salsa::db] +impl salsa::Database for DatabaseImpl {} + +#[test] +fn single_database() { + let db = DatabaseImpl { + storage: salsa::Storage::builder() + .ingredient::() + .ingredient::() + .ingredient::() + .ingredient::>() + .ingredient::>() + .build(), + }; + + let input = ingredients::MyInput::new(&db, 1); + + let tracked = ingredients::track(&db, input); + let interned = ingredients::intern(&db, input); + + assert_eq!(tracked.field(&db), 1); + assert_eq!(interned.field(&db), 1); +} + +#[test] +fn multiple_databases() { + let db1 = DatabaseImpl { + storage: salsa::Storage::builder() + .ingredient::() + .ingredient::() + .ingredient::>() + .build(), + }; + + let input = ingredients::MyInput::new(&db1, 1); + let interned = ingredients::intern(&db1, input); + assert_eq!(interned.field(&db1), 1); + + // Create a second database with different ingredient indices. + let db2 = DatabaseImpl { + storage: salsa::Storage::builder() + .ingredient::() + .ingredient::() + .ingredient::() + .ingredient::>() + .ingredient::>() + .build(), + }; + + let input = ingredients::MyInput::new(&db2, 2); + let interned = ingredients::intern(&db2, input); + assert_eq!(interned.field(&db2), 2); + + let input = ingredients::MyInput::new(&db2, 3); + let tracked = ingredients::track(&db2, input); + assert_eq!(tracked.field(&db2), 3); +} diff --git a/tests/memory-usage.rs b/tests/memory-usage.rs index a990ff6a..f9fca29a 100644 --- a/tests/memory-usage.rs +++ b/tests/memory-usage.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use expect_test::expect; #[salsa::input] diff --git a/tests/mutate_in_place.rs b/tests/mutate_in_place.rs index 047373ee..5327df41 100644 --- a/tests/mutate_in_place.rs +++ b/tests/mutate_in_place.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a setting a field on a `#[salsa::input]` //! overwrites and returns the old value. diff --git a/tests/override_new_get_set.rs b/tests/override_new_get_set.rs index 367decf0..222ba7b4 100644 --- a/tests/override_new_get_set.rs +++ b/tests/override_new_get_set.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that the `constructor` macro overrides //! the `new` method's name and `get` and `set` //! change the name of the getter and setter of the fields. diff --git a/tests/panic-when-creating-tracked-struct-outside-of-tracked-fn.rs b/tests/panic-when-creating-tracked-struct-outside-of-tracked-fn.rs index 32b444c7..dfc4f972 100644 --- a/tests/panic-when-creating-tracked-struct-outside-of-tracked-fn.rs +++ b/tests/panic-when-creating-tracked-struct-outside-of-tracked-fn.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that creating a tracked struct outside of a //! tracked function panics with an assert message. diff --git a/tests/parallel/main.rs b/tests/parallel/main.rs index da5e5e4a..e1478042 100644 --- a/tests/parallel/main.rs +++ b/tests/parallel/main.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + mod setup; mod signal; diff --git a/tests/preverify-struct-with-leaked-data-2.rs b/tests/preverify-struct-with-leaked-data-2.rs index d7e3f8f9..f3d6c05d 100644 --- a/tests/preverify-struct-with-leaked-data-2.rs +++ b/tests/preverify-struct-with-leaked-data-2.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. diff --git a/tests/preverify-struct-with-leaked-data.rs b/tests/preverify-struct-with-leaked-data.rs index 5c0f8495..6af7c6e8 100644 --- a/tests/preverify-struct-with-leaked-data.rs +++ b/tests/preverify-struct-with-leaked-data.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. diff --git a/tests/return_mode.rs b/tests/return_mode.rs index 34f67628..fdf11600 100644 --- a/tests/return_mode.rs +++ b/tests/return_mode.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use salsa::Database; #[salsa::input] diff --git a/tests/singleton.rs b/tests/singleton.rs index 381db9b7..5c7d2c71 100644 --- a/tests/singleton.rs +++ b/tests/singleton.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Basic Singleton struct test: //! //! Singleton structs are created only once. Subsequent `get`s and `new`s after creation return the same `Id`. diff --git a/tests/specify-only-works-if-the-key-is-created-in-the-current-query.rs b/tests/specify-only-works-if-the-key-is-created-in-the-current-query.rs index a407aee6..44d1cf5e 100644 --- a/tests/specify-only-works-if-the-key-is-created-in-the-current-query.rs +++ b/tests/specify-only-works-if-the-key-is-created-in-the-current-query.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that `specify` only works if the key is a tracked struct created in the current query. //! compilation succeeds but execution panics #![allow(warnings)] diff --git a/tests/synthetic_write.rs b/tests/synthetic_write.rs index 9e3c2f30..ccd6d026 100644 --- a/tests/synthetic_write.rs +++ b/tests/synthetic_write.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a constant `tracked` fn (has no inputs) //! compiles and executes successfully. #![allow(warnings)] diff --git a/tests/tracked-struct-id-field-bad-eq.rs b/tests/tracked-struct-id-field-bad-eq.rs index 44deec6c..d2d1a9c5 100644 --- a/tests/tracked-struct-id-field-bad-eq.rs +++ b/tests/tracked-struct-id-field-bad-eq.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test an id field whose `PartialEq` impl is always true. use salsa::{Database, Setter}; diff --git a/tests/tracked-struct-id-field-bad-hash.rs b/tests/tracked-struct-id-field-bad-hash.rs index f60aa479..bcd765bf 100644 --- a/tests/tracked-struct-id-field-bad-hash.rs +++ b/tests/tracked-struct-id-field-bad-hash.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test for a tracked struct where an untracked field has a //! very poorly chosen hash impl (always returns 0). //! diff --git a/tests/tracked-struct-unchanged-in-new-rev.rs b/tests/tracked-struct-unchanged-in-new-rev.rs index e4633740..da782eb7 100644 --- a/tests/tracked-struct-unchanged-in-new-rev.rs +++ b/tests/tracked-struct-unchanged-in-new-rev.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use salsa::{Database as Db, Setter}; use test_log::test; diff --git a/tests/tracked-struct-value-field-bad-eq.rs b/tests/tracked-struct-value-field-bad-eq.rs index 3a02d63c..f05cfb59 100644 --- a/tests/tracked-struct-value-field-bad-eq.rs +++ b/tests/tracked-struct-value-field-bad-eq.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! 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. diff --git a/tests/tracked-struct-value-field-not-eq.rs b/tests/tracked-struct-value-field-not-eq.rs index e37d4af9..451099d1 100644 --- a/tests/tracked-struct-value-field-not-eq.rs +++ b/tests/tracked-struct-value-field-not-eq.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! 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. diff --git a/tests/tracked_assoc_fn.rs b/tests/tracked_assoc_fn.rs index 18e6b953..c369740f 100644 --- a/tests/tracked_assoc_fn.rs +++ b/tests/tracked_assoc_fn.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] diff --git a/tests/tracked_fn_constant.rs b/tests/tracked_fn_constant.rs index c6753ebf..04681fcd 100644 --- a/tests/tracked_fn_constant.rs +++ b/tests/tracked_fn_constant.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a constant `tracked` fn (has no inputs) //! compiles and executes successfully. #![allow(warnings)] diff --git a/tests/tracked_fn_high_durability_dependency.rs b/tests/tracked_fn_high_durability_dependency.rs index a05be178..17b7efe7 100644 --- a/tests/tracked_fn_high_durability_dependency.rs +++ b/tests/tracked_fn_high_durability_dependency.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "inventory")] #![allow(warnings)] use salsa::plumbing::HasStorage; diff --git a/tests/tracked_fn_interned_lifetime.rs b/tests/tracked_fn_interned_lifetime.rs index 99b33af4..0cce1c32 100644 --- a/tests/tracked_fn_interned_lifetime.rs +++ b/tests/tracked_fn_interned_lifetime.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + #[salsa::interned] struct Interned<'db> { field: i32, diff --git a/tests/tracked_fn_multiple_args.rs b/tests/tracked_fn_multiple_args.rs index 7c014356..5bed1808 100644 --- a/tests/tracked_fn_multiple_args.rs +++ b/tests/tracked_fn_multiple_args.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on multiple salsa struct args //! compiles and executes successfully. diff --git a/tests/tracked_fn_no_eq.rs b/tests/tracked_fn_no_eq.rs index 6f223b79..5a1a4ed0 100644 --- a/tests/tracked_fn_no_eq.rs +++ b/tests/tracked_fn_no_eq.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + mod common; use common::LogDatabase; diff --git a/tests/tracked_fn_on_input.rs b/tests/tracked_fn_on_input.rs index e588a40a..a177f4d4 100644 --- a/tests/tracked_fn_on_input.rs +++ b/tests/tracked_fn_on_input.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] diff --git a/tests/tracked_fn_on_input_with_high_durability.rs b/tests/tracked_fn_on_input_with_high_durability.rs index fbf122de..07c9aee9 100644 --- a/tests/tracked_fn_on_input_with_high_durability.rs +++ b/tests/tracked_fn_on_input_with_high_durability.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "inventory")] #![allow(warnings)] use common::{EventLoggerDatabase, HasLogger, LogDatabase, Logger}; diff --git a/tests/tracked_fn_on_interned.rs b/tests/tracked_fn_on_interned.rs index b551b880..78a8a9fd 100644 --- a/tests/tracked_fn_on_interned.rs +++ b/tests/tracked_fn_on_interned.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::interned` //! compiles and executes successfully. diff --git a/tests/tracked_fn_on_interned_enum.rs b/tests/tracked_fn_on_interned_enum.rs index 63fa03b6..df810919 100644 --- a/tests/tracked_fn_on_interned_enum.rs +++ b/tests/tracked_fn_on_interned_enum.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::interned` //! compiles and executes successfully. diff --git a/tests/tracked_fn_on_tracked.rs b/tests/tracked_fn_on_tracked.rs index 967bbd55..802e76fa 100644 --- a/tests/tracked_fn_on_tracked.rs +++ b/tests/tracked_fn_on_tracked.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. diff --git a/tests/tracked_fn_on_tracked_specify.rs b/tests/tracked_fn_on_tracked_specify.rs index 70e4997a..42d7f58c 100644 --- a/tests/tracked_fn_on_tracked_specify.rs +++ b/tests/tracked_fn_on_tracked_specify.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] diff --git a/tests/tracked_fn_orphan_escape_hatch.rs b/tests/tracked_fn_orphan_escape_hatch.rs index f94e9394..25d2d7f4 100644 --- a/tests/tracked_fn_orphan_escape_hatch.rs +++ b/tests/tracked_fn_orphan_escape_hatch.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] diff --git a/tests/tracked_fn_read_own_entity.rs b/tests/tracked_fn_read_own_entity.rs index 11ae72c1..a62c8279 100644 --- a/tests/tracked_fn_read_own_entity.rs +++ b/tests/tracked_fn_read_own_entity.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. diff --git a/tests/tracked_fn_read_own_specify.rs b/tests/tracked_fn_read_own_specify.rs index a96cba35..26b9a2f5 100644 --- a/tests/tracked_fn_read_own_specify.rs +++ b/tests/tracked_fn_read_own_specify.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use expect_test::expect; mod common; use common::LogDatabase; diff --git a/tests/tracked_fn_return_ref.rs b/tests/tracked_fn_return_ref.rs index 918f33b3..c7691c1b 100644 --- a/tests/tracked_fn_return_ref.rs +++ b/tests/tracked_fn_return_ref.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use salsa::Database; #[salsa::input] diff --git a/tests/tracked_method.rs b/tests/tracked_method.rs index d5953dea..f3ee7d79 100644 --- a/tests/tracked_method.rs +++ b/tests/tracked_method.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] diff --git a/tests/tracked_method_inherent_return_deref.rs b/tests/tracked_method_inherent_return_deref.rs index 2477b5a1..ac02bf92 100644 --- a/tests/tracked_method_inherent_return_deref.rs +++ b/tests/tracked_method_inherent_return_deref.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use salsa::Database; #[salsa::input] diff --git a/tests/tracked_method_inherent_return_ref.rs b/tests/tracked_method_inherent_return_ref.rs index 564bb31f..2d22336b 100644 --- a/tests/tracked_method_inherent_return_ref.rs +++ b/tests/tracked_method_inherent_return_ref.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use salsa::Database; #[salsa::input] diff --git a/tests/tracked_method_on_tracked_struct.rs b/tests/tracked_method_on_tracked_struct.rs index f7cb9f6d..e99a649b 100644 --- a/tests/tracked_method_on_tracked_struct.rs +++ b/tests/tracked_method_on_tracked_struct.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use salsa::Database; #[derive(Debug, PartialEq, Eq, Hash)] diff --git a/tests/tracked_method_trait_return_ref.rs b/tests/tracked_method_trait_return_ref.rs index ec74cf3a..e632a7ce 100644 --- a/tests/tracked_method_trait_return_ref.rs +++ b/tests/tracked_method_trait_return_ref.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + use salsa::Database; #[salsa::input] diff --git a/tests/tracked_method_with_self_ty.rs b/tests/tracked_method_with_self_ty.rs index 8f8b0678..a541af98 100644 --- a/tests/tracked_method_with_self_ty.rs +++ b/tests/tracked_method_with_self_ty.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a `tracked` fn with `Self` in its signature or body on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] diff --git a/tests/tracked_struct.rs b/tests/tracked_struct.rs index a1bba36c..e148bffd 100644 --- a/tests/tracked_struct.rs +++ b/tests/tracked_struct.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + mod common; use salsa::{Database, Setter}; diff --git a/tests/tracked_struct_db1_lt.rs b/tests/tracked_struct_db1_lt.rs index e5de757c..27766611 100644 --- a/tests/tracked_struct_db1_lt.rs +++ b/tests/tracked_struct_db1_lt.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that tracked structs with lifetimes not named `'db` //! compile successfully. diff --git a/tests/tracked_struct_disambiguates.rs b/tests/tracked_struct_disambiguates.rs index 663fedf4..db0435a2 100644 --- a/tests/tracked_struct_disambiguates.rs +++ b/tests/tracked_struct_disambiguates.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that disambiguation works, that is when we have a revision where we track multiple structs //! that have the same hash, we can still differentiate between them. #![allow(warnings)] diff --git a/tests/tracked_struct_durability.rs b/tests/tracked_struct_durability.rs index 7dfd8728..3608a5e7 100644 --- a/tests/tracked_struct_durability.rs +++ b/tests/tracked_struct_durability.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + /// Test that high durabilities can't cause "access tracked struct from previous revision" panic. /// /// The test models a situation where we have two File inputs (0, 1), where `File(0)` has LOW diff --git a/tests/tracked_struct_manual_update.rs b/tests/tracked_struct_manual_update.rs index 10c91117..e9dca14a 100644 --- a/tests/tracked_struct_manual_update.rs +++ b/tests/tracked_struct_manual_update.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + mod common; use std::sync::atomic::{AtomicBool, Ordering}; diff --git a/tests/tracked_struct_mixed_tracked_fields.rs b/tests/tracked_struct_mixed_tracked_fields.rs index a5630c63..50cd4cc7 100644 --- a/tests/tracked_struct_mixed_tracked_fields.rs +++ b/tests/tracked_struct_mixed_tracked_fields.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + mod common; use salsa::{Database, Setter}; diff --git a/tests/tracked_struct_recreate_new_revision.rs b/tests/tracked_struct_recreate_new_revision.rs index c1786a2d..d6a0a6e8 100644 --- a/tests/tracked_struct_recreate_new_revision.rs +++ b/tests/tracked_struct_recreate_new_revision.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that re-creating a `tracked` struct after it was deleted in a previous //! revision doesn't panic. #![allow(warnings)] diff --git a/tests/tracked_struct_with_interned_query.rs b/tests/tracked_struct_with_interned_query.rs index 4476ccea..c4160531 100644 --- a/tests/tracked_struct_with_interned_query.rs +++ b/tests/tracked_struct_with_interned_query.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + mod common; use salsa::Setter; diff --git a/tests/tracked_with_intern.rs b/tests/tracked_with_intern.rs index 1b3a381d..a2ab5d6a 100644 --- a/tests/tracked_with_intern.rs +++ b/tests/tracked_with_intern.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a setting a field on a `#[salsa::input]` //! overwrites and returns the old value. diff --git a/tests/tracked_with_struct_db.rs b/tests/tracked_with_struct_db.rs index 9af7b550..323f9fac 100644 --- a/tests/tracked_with_struct_db.rs +++ b/tests/tracked_with_struct_db.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that a setting a field on a `#[salsa::input]` //! overwrites and returns the old value. diff --git a/tests/tracked_with_struct_ord.rs b/tests/tracked_with_struct_ord.rs index 51fef9cc..1b3c6a79 100644 --- a/tests/tracked_with_struct_ord.rs +++ b/tests/tracked_with_struct_ord.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "inventory")] + //! Test that `PartialOrd` and `Ord` can be derived for tracked structs use salsa::{Database, DatabaseImpl};