diff --git a/404.html b/404.html index be3b5e29..91045d2e 100644 --- a/404.html +++ b/404.html @@ -84,7 +84,7 @@ diff --git a/about_salsa.html b/about_salsa.html index 17440e90..cd51fb1e 100644 --- a/about_salsa.html +++ b/about_salsa.html @@ -83,7 +83,7 @@ diff --git a/common_patterns.html b/common_patterns.html index a9f9dc9f..1eaad4ce 100644 --- a/common_patterns.html +++ b/common_patterns.html @@ -83,7 +83,7 @@ diff --git a/common_patterns/on_demand_inputs.html b/common_patterns/on_demand_inputs.html index 13f80621..b3bd3e7b 100644 --- a/common_patterns/on_demand_inputs.html +++ b/common_patterns/on_demand_inputs.html @@ -83,7 +83,7 @@ diff --git a/common_patterns/selection.html b/common_patterns/selection.html index 5bc2a546..9020c7c4 100644 --- a/common_patterns/selection.html +++ b/common_patterns/selection.html @@ -83,7 +83,7 @@ diff --git a/cycles.html b/cycles.html index c0c0f207..357396e6 100644 --- a/cycles.html +++ b/cycles.html @@ -83,7 +83,7 @@ diff --git a/cycles/fallback.html b/cycles/fallback.html index 51815f32..4e1d92cf 100644 --- a/cycles/fallback.html +++ b/cycles/fallback.html @@ -83,7 +83,7 @@ diff --git a/how_salsa_works.html b/how_salsa_works.html index f52a989b..dc8cd8f7 100644 --- a/how_salsa_works.html +++ b/how_salsa_works.html @@ -83,7 +83,7 @@ diff --git a/index.html b/index.html index c13b60d5..c389b1c3 100644 --- a/index.html +++ b/index.html @@ -83,7 +83,7 @@ diff --git a/meta.html b/meta.html index 3d50d3ce..17d24458 100644 --- a/meta.html +++ b/meta.html @@ -83,7 +83,7 @@ diff --git a/overview.html b/overview.html index ac59606c..a711891b 100644 --- a/overview.html +++ b/overview.html @@ -83,7 +83,7 @@ diff --git a/plumbing.html b/plumbing.html index 4f81786c..236c7aa3 100644 --- a/plumbing.html +++ b/plumbing.html @@ -83,7 +83,7 @@ @@ -145,12 +145,26 @@

Plumbing

+
+

⚠️ IN-PROGRESS VERSION OF SALSA. ⚠️

+

This page describes the unreleased "Salsa 2022" version, which is a major departure from older versions of salsa. The code here works but is only available on github and from the salsa-2022 crate.

+

This chapter documents the code that salsa generates and its "inner workings". We refer to this as the "plumbing".

-

History

+

Overview

+

The plumbing section is broken up into chapters:

@@ -161,7 +175,7 @@ We refer to this as the "plumbing".

- @@ -175,7 +189,7 @@ We refer to this as the "plumbing".

- diff --git a/plumbing/cycles.html b/plumbing/cycles.html index ce027fbd..080be943 100644 --- a/plumbing/cycles.html +++ b/plumbing/cycles.html @@ -83,7 +83,7 @@
diff --git a/plumbing/database.html b/plumbing/database.html deleted file mode 100644 index 7bf313b4..00000000 --- a/plumbing/database.html +++ /dev/null @@ -1,364 +0,0 @@ - - - - - - Database - Salsa - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - -
-
-

Database

-

Continuing our dissection, the other thing which a user must define is a -database, which looks something like this:

-
#[salsa::database(HelloWorldStorage)]
-#[derive(Default)]
-struct DatabaseStruct {
-    storage: salsa::Storage<Self>,
-}
-
-impl salsa::Database for DatabaseStruct {}
-
-

The salsa::database procedural macro takes a list of query group -structs (like HelloWorldStorage) and generates the following items:

-
    -
  • a copy of the database struct it is applied to
  • -
  • a struct __SalsaDatabaseStorage that contains all the storage structs for -each query group. Note: these are the structs full of hashmaps etc that are -generaetd by the query group procdural macro, not the HelloWorldStorage -struct itself.
  • -
  • an impl of HasQueryGroup<G> for each query group G
  • -
  • an impl of salsa::plumbing::DatabaseStorageTypes for the database struct
  • -
  • an impl of salsa::plumbing::DatabaseOps for the database struct
  • -
-

Key constraint: we do not know the names of individual queries

-

There is one key constraint in the design here. None of this code knows the -names of individual queries. It only knows the name of the query group storage -struct. This means that we often delegate things to the group -- e.g., the -database key is composed of group keys. This is similar to how none of the code -in the query group knows the full set of query groups, and so it must use -associated types from the Database trait whenever it needs to put something in -a "global" context.

-

The database storage struct

-

The __SalsaDatabaseStorage struct concatenates all of the query group storage -structs. In the hello world example, it looks something like:

-
struct __SalsaDatabaseStorage {
-    hello_world: <HelloWorldStorage as salsa::plumbing::QueryGroup<DatabaseStruct>>::GroupStorage
-}
-
-

We also generate a Default impl for __SalsaDatabaseStorage. It invokes -a new method on each group storage with the unique index assigned to that group. -This invokes the inherent new method generated by the #[salsa::query_group] macro.

-

The HasQueryGroup impl

-

The HasQueryGroup trait allows a given query group to access its definition -within the greater database. The impl is generated here:

-
        has_group_impls.extend(quote! {
-            impl salsa::plumbing::HasQueryGroup<#group_path> for #database_name {
-                fn group_storage(&self) -> &#group_storage {
-                    &self.#db_storage_field.query_store().#group_name_snake
-                }
-
-                fn group_storage_mut(&mut self) -> (&#group_storage, &mut salsa::Runtime) {
-                    let (query_store_mut, runtime) = self.#db_storage_field.query_store_mut();
-                    (&query_store_mut.#group_name_snake, runtime)
-                }
-            }
-        });
-
-

The HasQueryGroup impl combines with the blanket impl from the -#[salsa::query_group] macro so that the database can implement the query group -trait (e.g., the HelloWorld trait) but without knowing all the names of the -query methods and the like.

-

The DatabaseStorageTypes impl

-

Then there are a variety of other impls, like this one for DatabaseStorageTypes:

-
    output.extend(quote! {
-        impl salsa::plumbing::DatabaseStorageTypes for #database_name {
-            type DatabaseStorage = __SalsaDatabaseStorage;
-        }
-    });
-
-

The DatabaseOps impl

-

Or this one for DatabaseOps, which defines the for-each method to -invoke an operation on every kind of query in the database. It ultimately -delegates to the for_each methods for the groups:

-
    let mut fmt_ops = proc_macro2::TokenStream::new();
-    let mut maybe_changed_ops = proc_macro2::TokenStream::new();
-    let mut cycle_recovery_strategy_ops = proc_macro2::TokenStream::new();
-    let mut for_each_ops = proc_macro2::TokenStream::new();
-    for ((QueryGroup { group_path }, group_storage), group_index) in query_groups
-        .iter()
-        .zip(&query_group_storage_names)
-        .zip(0_u16..)
-    {
-        fmt_ops.extend(quote! {
-            group_index => {
-                let storage: &#group_storage =
-                    <Self as salsa::plumbing::HasQueryGroup<#group_path>>::group_storage(self);
-                storage.fmt_index(self, input, fmt)
-            }
-        });
-        maybe_changed_ops.extend(quote! {
-            group_index => {
-                let storage: &#group_storage =
-                    <Self as salsa::plumbing::HasQueryGroup<#group_path>>::group_storage(self);
-                storage.maybe_changed_after(self, input, revision)
-            }
-        });
-        cycle_recovery_strategy_ops.extend(quote! {
-            group_index => {
-                let storage: &#group_storage =
-                    <Self as salsa::plumbing::HasQueryGroup<#group_path>>::group_storage(self);
-                storage.cycle_recovery_strategy(self, input)
-            }
-        });
-        for_each_ops.extend(quote! {
-            let storage: &#group_storage =
-                <Self as salsa::plumbing::HasQueryGroup<#group_path>>::group_storage(self);
-            storage.for_each_query(runtime, &mut op);
-        });
-    }
-    output.extend(quote! {
-        impl salsa::plumbing::DatabaseOps for #database_name {
-            fn ops_database(&self) -> &dyn salsa::Database {
-                self
-            }
-
-            fn ops_salsa_runtime(&self) -> &salsa::Runtime {
-                self.#db_storage_field.salsa_runtime()
-            }
-
-            fn ops_salsa_runtime_mut(&mut self) -> &mut salsa::Runtime {
-                self.#db_storage_field.salsa_runtime_mut()
-            }
-
-            fn fmt_index(
-                &self,
-                input: salsa::DatabaseKeyIndex,
-                fmt: &mut std::fmt::Formatter<'_>,
-            ) -> std::fmt::Result {
-                match input.group_index() {
-                    fmt_ops
-                    i => panic!("salsa: invalid group index {}", i)
-                }
-            }
-
-            fn maybe_changed_after(
-                &self,
-                input: salsa::DatabaseKeyIndex,
-                revision: salsa::Revision
-            ) -> bool {
-                match input.group_index() {
-                    maybe_changed_ops
-                    i => panic!("salsa: invalid group index {}", i)
-                }
-            }
-
-            fn cycle_recovery_strategy(
-                &self,
-                input: salsa::DatabaseKeyIndex,
-            ) -> salsa::plumbing::CycleRecoveryStrategy {
-                match input.group_index() {
-                    cycle_recovery_strategy_ops
-                    i => panic!("salsa: invalid group index {}", i)
-                }
-            }
-
-            fn for_each_query(
-                &self,
-                mut op: &mut dyn FnMut(&dyn salsa::plumbing::QueryStorageMassOps),
-            ) {
-                let runtime = salsa::Database::salsa_runtime(self);
-                for_each_ops
-            }
-        }
-    });
-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - diff --git a/plumbing/database_and_runtime.html b/plumbing/database_and_runtime.html new file mode 100644 index 00000000..5c7a1f5d --- /dev/null +++ b/plumbing/database_and_runtime.html @@ -0,0 +1,309 @@ + + + + + + Databases and runtime - Salsa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+
+

Database and runtime

+

A salsa database struct is declared by the user with the #[salsa::db] annotation. +It contains all the data that the program needs to execute:

+
#[salsa::db(jar0...jarn)]
+struct MyDatabase {
+    storage: Storage<Self>,
+    maybe_other_fields: u32,
+}
+
+

This data is divided into two categories:

+
    +
  • Salsa-governed storage, contained in the Storage<Self> field. This data is mandatory.
  • +
  • Other fields (like maybe_other_fields) defined by the user. This can be anything. This allows for you to give access to special resources or whatever.
  • +
+

Parallel handles

+

When used across parallel threads, the database type defined by the user must support a "snapshot" operation. +This snapshot should create a clone of the database that can be used by the parallel threads. +The Storage operation itself supports snapshot. +The Snapshot method returns a Snapshot<DB> type, which prevents these clones from being accessed via an &mut reference.

+

The Storage struct

+

The salsa Storage struct contains all the data that salsa itself will use and work with. +There are three key bits of data:

+
    +
  • The Shared struct, which contains the data stored across all snapshots. This is primarily the ingredients described in the jars and ingredients chapter, but it also contains some synchronization information (a cond var). This is used for cancellation, as described below. +
      +
    • The data in the Shared struct is only shared across threads when other threads are active. Some operations, like mutating an input, require an &mut handle to the Shared struct. This is obtained by using the Arc::get_mut methods; obviously this is only possible when all snapshots and threads have ceased executing, since there must be a single handle to the Arc.
    • +
    +
  • +
  • The Routes struct, which contains the information to find any particular ingredient -- this is also shared across all handles, and its construction is also described in the jars and ingredients chapter. The routes are separated out from the Shared struct because they are truly immutable at all times, and we want to be able to hold a handle to them while getting &mut access to the Shared struct.
  • +
  • The Runtime struct, which is specific to a particular database instance. It contains the data for a single active thread, along with some links to shraed data of its own.
  • +
+

Incrementing the revision counter and getting mutable access to the jars

+

Salsa's general model is that there is a single "master" copy of the database and, potentially, multiple snapshots. +The snapshots are not directly owned, they are instead enclosed in a Snapshot<DB> type that permits only &-deref, +and so the only database that can be accessed with an &mut-ref is the master database. +Each of the snapshots however onlys another handle on the Arc in Storage that stores the ingredients.

+

Whenever the user attempts to do an &mut-operation, such as modifying an input field, that needs to +first cancel any parallel snapshots and wait for those parallel threads to finish. +Once the snapshots have completed, we can use Arc::get_mut to get an &mut reference to the ingredient data. +This allows us to get &mut access without any unsafe code and +guarantees that we have successfully managed to cancel the other worker threads +(or gotten ourselves into a deadlock).

+

The code to acquire &mut access to the database is the jars_mut method:

+

+#![allow(unused)]
+fn main() {
+    /// Gets mutable access to the jars. This will trigger a new revision
+    /// and it will also cancel any ongoing work in the current revision.
+    /// Any actual writes that occur to data in a jar should use
+    /// [`Runtime::report_tracked_write`].
+    pub fn jars_mut(&mut self) -> (&mut DB::Jars, &mut Runtime) {
+        // Wait for all snapshots to be dropped.
+        self.cancel_other_workers();
+
+        // Increment revision counter.
+        self.runtime.new_revision();
+
+        // Acquire `&mut` access to `self.shared` -- this is only possible because
+        // the snapshots have all been dropped, so we hold the only handle to the `Arc`.
+        let shared = Arc::get_mut(&mut self.shared).unwrap();
+
+        // Inform other ingredients that a new revision has begun.
+        // This gives them a chance to free resources that were being held until the next revision.
+        let routes = self.routes.clone();
+        for route in routes.reset_routes() {
+            route(&mut shared.jars).reset_for_new_revision();
+        }
+
+        // Return mut ref to jars + runtime.
+        (&mut shared.jars, &mut self.runtime)
+    }
+}
+
+

The key initial point is that it invokes cancel_other_workers before proceeding:

+

+#![allow(unused)]
+fn main() {
+    /// Sets cancellation flag and blocks until all other workers with access
+    /// to this storage have completed.
+    ///
+    /// This could deadlock if there is a single worker with two handles to the
+    /// same database!
+    fn cancel_other_workers(&mut self) {
+        loop {
+            self.runtime.set_cancellation_flag();
+
+            // If we have unique access to the jars, we are done.
+            if Arc::get_mut(&mut self.shared).is_some() {
+                return;
+            }
+
+            // Otherwise, wait until some other storage entites have dropped.
+            // We create a mutex here because the cvar api requires it, but we
+            // don't really need one as the data being protected is actually
+            // the jars above.
+            //
+            // The cvar `self.shared.cvar` is notified by the `Drop` impl.
+            let mutex = parking_lot::Mutex::new(());
+            let mut guard = mutex.lock();
+            self.shared.cvar.wait(&mut guard);
+        }
+    }
+}
+
+

The Salsa runtime

+

The salsa runtime offers helper methods that are accessed by the ingredients. +It tracks, for example, the active query stack, and contains methods for adding dependencies between queries (e.g., report_tracked_read) or resolving cycles. +It also tracks the current revision and information about when values with low or high durability last changed.

+

Basically, the ingredient structures store the "data at rest" -- like memoized values -- and things that are "per ingredient".

+

The runtime stores the "active, in-progress" data, such as which queries are on the stack, and/or the dependencies accessed by the currently active query.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/plumbing/derived_flowchart.html b/plumbing/derived_flowchart.html index dcb09f9e..49e5f70b 100644 --- a/plumbing/derived_flowchart.html +++ b/plumbing/derived_flowchart.html @@ -83,7 +83,7 @@ diff --git a/plumbing/diagram.html b/plumbing/diagram.html deleted file mode 100644 index 50bbdb8d..00000000 --- a/plumbing/diagram.html +++ /dev/null @@ -1,246 +0,0 @@ - - - - - - Diagram - Salsa - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - -
-
-

Diagram

-

This diagram shows the items that get generated from the Hello World query group and database struct. You can click on each item to be taken to the explanation of its purpose. The diagram is wide so be sure to scroll over!

-
graph LR
-    classDef diagramNode text-align:left;
-    subgraph query group
-        HelloWorldTrait["trait HelloWorld: Database + HasQueryGroup(HelloWorldStroage)"]
-        HelloWorldImpl["impl&lt;DB&gt; HelloWorld for DB<br>where DB: HasQueryGroup(HelloWorldStorage)"]
-        click HelloWorldImpl "http:query_groups.html#impl-of-the-hello-world-trait" "more info"
-        HelloWorldStorage["struct HelloWorldStorage"]
-        click HelloWorldStorage "http:query_groups.html#the-group-struct-and-querygroup-trait" "more info"
-        QueryGroupImpl["impl QueryGroup for HelloWorldStorage<br>&nbsp;&nbsp;type DynDb = dyn HelloWorld<br>&nbsp;&nbsp;type Storage = HelloWorldGroupStorage__;"]
-        click QueryGroupImpl "http:query_groups.html#the-group-struct-and-querygroup-trait" "more info"
-        HelloWorldGroupStorage["struct HelloWorldGroupStorage__"]
-        click HelloWorldGroupStorage "http:query_groups.html#group-storage" "more info"
-        subgraph for each query...
-            LengthQuery[struct LengthQuery]
-            LengthQueryImpl["impl Query for LengthQuery<br>&nbsp;&nbsp;type Key = ()<br>&nbsp;&nbsp;type Value = usize<br>&nbsp;&nbsp;type Storage = salsa::DerivedStorage(Self)<br>&nbsp;&nbsp;type QueryGroup = HelloWorldStorage"]
-            LengthQueryFunctionImpl["impl QueryFunction for LengthQuery<br>&nbsp;&nbsp;fn execute(db: &dyn HelloWorld, key: ()) -> usize"]
-            click LengthQuery "http:query_groups.html#for-each-query-a-query-struct" "more info"
-            click LengthQueryImpl "http:query_groups.html#for-each-query-a-query-struct" "more info"
-            click LengthQueryFunctionImpl "http:query_groups.html#for-each-query-a-query-struct" "more info"
-        end
-        class HelloWorldTrait,HelloWorldImpl,HelloWorldStorage,QueryGroupImpl,HelloWorldGroupStorage diagramNode;
-        class LengthQuery,LengthQueryImpl,LengthQueryFunctionImpl diagramNode;
-    end
-    subgraph database
-        DatabaseStruct["struct Database { .. storage: Storage(Self) .. }"]
-        subgraph for each group...
-            HasQueryGroup["impl plumbing::HasQueryGroup(HelloWorldStorage) for DatabaseStruct"]
-            click HasQueryGroup "http:database.html#the-hasquerygroup-impl" "more info"
-        end
-        DatabaseStorageTypes["impl plumbing::DatabaseStorageTypes for DatabaseStruct<br>&nbsp;&nbsp;type DatabaseStorage = __SalsaDatabaseStorage"]
-        click DatabaseStorageTypes "http:database.html#the-databasestoragetypes-impl" "more info"
-        DatabaseStorage["struct __SalsaDatabaseStorage"]
-        click DatabaseStorage "http:database.html#the-database-storage-struct" "more info"
-        DatabaseOps["impl plumbing::DatabaseOps for DatabaseStruct"]
-        click DatabaseOps "http:database.html#the-databaseops-impl" "more info"
-        class DatabaseStruct,DatabaseStorage,DatabaseStorageTypes,DatabaseOps,HasQueryGroup diagramNode;
-    end
-    subgraph salsa crate
-        DerivedStorage["DerivedStorage"]
-        class DerivedStorage diagramNode;
-    end
-    LengthQueryImpl --> DerivedStorage;
-    DatabaseStruct -- "used by" --> HelloWorldImpl
-    HasQueryGroup -- "used by" --> HelloWorldImpl
-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - diff --git a/plumbing/fetch.html b/plumbing/fetch.html index fcc8ca9f..75d4c338 100644 --- a/plumbing/fetch.html +++ b/plumbing/fetch.html @@ -83,7 +83,7 @@ diff --git a/plumbing/generated_code.html b/plumbing/generated_code.html deleted file mode 100644 index 808381ae..00000000 --- a/plumbing/generated_code.html +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - Generated code - Salsa - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - -
-
-

Generated code

-

This page walks through the "Hello, World!" example and explains the code that -it generates. Please take it with a grain of salt: while we make an effort to -keep this documentation up to date, this sort of thing can fall out of date -easily. See the page history below for major updates.

-

If you'd like to see for yourself, you can set the environment variable -SALSA_DUMP to 1 while the procedural macro runs, and it will dump the full -output to stdout. I recommend piping the output through rustfmt.

-

Sources

-

The main parts of the source that we are focused on are as follows.

-

Query group

-
#[salsa::query_group(HelloWorldStorage)]
-trait HelloWorld {
-    // For each query, we give the name, some input keys (here, we
-    // have one key, `()`) and the output type `Arc<String>`. We can
-    // use attributes to give other configuration:
-    //
-    // - `salsa::input` indicates that this is an "input" to the system,
-    //   which must be explicitly set. The `salsa::query_group` method
-    //   will autogenerate a `set_input_string` method that can be
-    //   used to set the input.
-    #[salsa::input]
-    fn input_string(&self, key: ()) -> Arc<String>;
-
-    // This is a *derived query*, meaning its value is specified by
-    // a function (see Step 2, below).
-    fn length(&self, key: ()) -> usize;
-}
-
-

Database

-
#[salsa::database(HelloWorldStorage)]
-#[derive(Default)]
-struct DatabaseStruct {
-    storage: salsa::Storage<Self>,
-}
-
-impl salsa::Database for DatabaseStruct {}
-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - diff --git a/plumbing/jars_and_ingredients.html b/plumbing/jars_and_ingredients.html new file mode 100644 index 00000000..8dbfc4ca --- /dev/null +++ b/plumbing/jars_and_ingredients.html @@ -0,0 +1,431 @@ + + + + + + Jars and ingredients - Salsa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+
+

Jars and ingredients

+
+

⚠️ IN-PROGRESS VERSION OF SALSA. ⚠️

+

This page describes the unreleased "Salsa 2022" version, which is a major departure from older versions of salsa. The code here works but is only available on github and from the salsa-2022 crate.

+
+

This page covers how data is organized in salsa and how links between salsa items (e.g., dependency tracking) works.

+

Salsa items and ingredients

+

A salsa item is some item annotated with a salsa annotation that can be included in a jar. +For example, a tracked function is a salsa item:

+

+#![allow(unused)]
+fn main() {
+#[salsa::tracked]
+fn foo(db: &dyn Db, input: MyInput) { }
+}
+
+

...and so is a salsa input...

+

+#![allow(unused)]
+fn main() {
+#[salsa::input]
+struct MyInput { }
+}
+
+

...or a tracked struct:

+

+#![allow(unused)]
+fn main() {
+#[salsa::tracked]
+struct MyStruct { }
+}
+
+

Each salsa item needs certain bits of data at runtime to operate. +These bits of data are called ingredients. +Most salsa items generate a single ingredient, but sometimes they make more than one. +For example, a tracked function generates a FunctionIngredient. +A tracked struct however generates several ingredients, one for the struct itself (a TrackedStructIngredient, +and one FunctionIngredient for each value field.

+

Ingredients define the core logic of salsa

+

Most of the interesting salsa code lives in these ingredients. +For example, when you create a new tracked struct, the method TrackedStruct::new_struct is invoked; +it is responsible for determining the tracked struct's id. +Similarly, when you call a tracked function, that is translated into a call to TrackedFunction::fetch, +which decides whether there is a valid memoized value to return, +or whether the function must be executed.

+

Ingredient interfaces are not stable or subject to semver

+

Interfaces are not meant to be directly used by salsa users. +The salsa macros generate code that invokes the ingredients. +The APIs may change in arbitrary ways across salsa versions, +as the macros are kept in sync.

+

The Ingredient trait

+

Each ingredient implements the Ingredient<DB> trait, which defines generic operations supported by any kind of ingredient. +For example, the method maybe_changed_after can be used to check whether some particular piece of data stored in the ingredient may have changed since a given revision:

+

We'll see below that each database DB is able to take an IngredientIndex and use that to get a &dyn Ingredient<DB> for the corresponding ingredient. +This allows the database to perform generic operations on a numbered ingredient without knowing exactly what the type of that ingredient is.

+

Jars are a collection of ingredients

+

When you declare a salsa jar, you list out each of the salsa items that are included in that jar:

+
#[salsa::jar]
+struct Jar(
+    foo,
+    MyInput,
+    MyStruct
+);
+
+

This expands to a struct like so:

+

+#![allow(unused)]
+fn main() {
+struct Jar(
+    <foo as IngredientsFor>::Ingredient,
+    <MyInput as IngredientsFor>::Ingredient,
+    <MyStruct as IngredientsFor>::Ingredient,
+)
+}
+
+

The IngredientsFor trait is used to define the ingredients needed by some salsa item, such as the tracked function foo +or the tracked struct MyInput. +Each salsa item defines a type I, so that <I as IngredientsFor>::Ingredient gives the ingredients needed by I.

+

Database is a tuple of jars

+

Salsa's database storage ultimately boils down to a tuple of jar structs, +where each jar struct (as we just saw) itself contains the ingredients +for the salsa items within that jar. +The database can thus be thought of as a list of ingredients, +although that list is organized into a 2-level hierarchy.

+

The reason for this 2-level hierarchy is that it permits separate compilation and privacy. +The crate that lists the jars doens't have to know the contents of the jar to embed the jar struct in the database. +And some of the types that appear in the jar may be private to another struct.

+

The HasJars trait and the Jars type

+

Each salsa database implements the HasJars trait, +generated by the salsa::db procedural macro. +The HarJars trait, among other things, defines a Jars associated type that maps to a tuple of the jars in the trait.

+

For example, given a database like this...

+
#[salsa::db(Jar1, ..., JarN)]
+struct MyDatabase {
+    storage: salsa::Storage<Self>
+}
+
+

...the salsa::db macro would generate a HasJars impl that (among other things) contains type Jars = (Jar1, ..., JarN):

+
        impl salsa::storage::HasJars for #db {
+            type Jars = (#(#jar_paths,)*);
+
+

In turn, the salsa::Storage<DB> type ultimately contains a struct Shared that embeds DB::Jars, thus embedding all the data for each jar.

+

Ingredient indices

+

During initialization, each ingredient in the database is assigned a unique index called the IngredientIndex. +This is a 32-bit number that identifies a particular ingredient from a particular jar.

+

Routes

+

In addition to an index, each ingredient in the database also has a corresponding route. +A route is a closure that, given a reference to the DB::Jars tuple, +returns a &dyn Ingredient<DB> reference. +The route table allows us to go from the IngredientIndex for a particular ingredient +to its &dyn Ingredient<DB> trait object. +The route table is created while the database is being initialized, +as described shortly.

+

Database keys and dependency keys

+

A DatabaseKeyIndex identifies a specific value stored in some specific ingredient. +It combines an IngredientIndex with a key_index, which is a salsa::Id:

+
/// An "active" database key index represents a database key index
+/// that is actively executing. In that case, the `key_index` cannot be
+/// None.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+pub struct DatabaseKeyIndex {
+    pub(crate) ingredient_index: IngredientIndex,
+    pub(crate) key_index: Id,
+}
+
+

A DependencyIndex is similar, but the key_index is optional. +This is used when we sometimes wish to refer to the ingredient as a whole, and not any specific value within the ingredient.

+

These kinds of indices are used to store connetions between ingredients. +For example, each memoized value has to track its inputs. +Those inputs are stored as dependency indices. +We can then do things like ask, "did this input change since revision R?" by

+
    +
  • using the ingredient index to find the route and get a &dyn Ingredient<DB>
  • +
  • and then invoking the maybe_changed_since method on that trait object.
  • +
+

HasJarsDyn

+

There is one catch in the above setup. +We need the database to be dyn-safe, and we also need to be able to define the database trait and so forth without knowing the final database type to enable separate compilation. +Traits like Ingredient<DB> require knowing the full DB type. +If we had one function ingredient directly invoke a method on Ingredient<DB>, that would imply that it has to be fully generic and only instantiated at the final crate, when the full database type is available.

+

We solve this via the HasJarsDyn trait. The HasJarsDyn trait exports method that combine the "find ingredient, invoking method" steps into one method:

+
/// Dyn friendly subset of HasJars
+pub trait HasJarsDyn {
+    fn runtime(&self) -> &Runtime;
+
+    fn maybe_changed_after(&self, input: DependencyIndex, revision: Revision) -> bool;
+
+    fn cycle_recovery_strategy(&self, input: IngredientIndex) -> CycleRecoveryStrategy;
+
+    fn origin(&self, input: DatabaseKeyIndex) -> Option<QueryOrigin>;
+
+    fn mark_validated_output(&self, executor: DatabaseKeyIndex, output: DependencyIndex);
+
+    /// Invoked when `executor` used to output `stale_output` but no longer does.
+    /// This method routes that into a call to the [`remove_stale_output`](`crate::ingredient::Ingredient::remove_stale_output`)
+    /// method on the ingredient for `stale_output`.
+    fn remove_stale_output(&self, executor: DatabaseKeyIndex, stale_output: DependencyIndex);
+
+    /// Informs `ingredient` that the salsa struct with id `id` has been deleted.
+    /// This means that `id` will not be used in this revision and hence
+    /// any memoized values keyed by that struct can be discarded.
+    ///
+    /// In order to receive this callback, `ingredient` must have registered itself
+    /// as a dependent function using
+    /// [`SalsaStructInDb::register_dependent_fn`](`crate::salsa_struct::SalsaStructInDb::register_dependent_fn`).
+    fn salsa_struct_deleted(&self, ingredient: IngredientIndex, id: Id);
+}
+
+

So, technically, to check if an input has changed, an ingredient:

+
    +
  • Invokes HasJarsDyn::maybe_changed_after on the dyn Database
  • +
  • The impl for this method (generated by #[salsa::db]): +
      +
    • gets the route for the ingredient from the ingredient index
    • +
    • uses the route to get a &dyn Ingredient
    • +
    • invokes maybe_changed_after on that ingredient
    • +
    +
  • +
+

Initializing the database

+

The last thing to dicsuss is how the database is initialized. +The Default implementation for Storage<DB> does the work:

+
impl<DB> Default for Storage<DB>
+where
+    DB: HasJars,
+{
+    fn default() -> Self {
+        let mut routes = Routes::new();
+        let jars = DB::create_jars(&mut routes);
+        Self {
+            shared: Arc::new(Shared {
+                jars,
+                cvar: Default::default(),
+            }),
+            routes: Arc::new(routes),
+            runtime: Runtime::default(),
+        }
+    }
+}
+
+

First, it creates an empty Routes instance. +Then it invokes the DB::create_jars method. +The implementation of this method is defined by the #[salsa::db] macro; it simply invokes the Jar::create_jar method on each of the jars:

+
            fn create_jars(routes: &mut salsa::routes::Routes<Self>) -> Self::Jars {
+                (
+                    (
+                        <#jar_paths as salsa::jar::Jar>::create_jar(routes),
+                    )*
+                )
+            }
+
+

This implementation for create_jar is geneated by the #[salsa::jar] macro, and simply walks over the representative type for each salsa item and ask it to create its ingredients

+
    quote! {
+        impl<'salsa_db> salsa::jar::Jar<'salsa_db> for #jar_struct {
+            type DynDb = dyn #jar_trait + 'salsa_db;
+
+            fn create_jar<DB>(routes: &mut salsa::routes::Routes<DB>) -> Self
+            where
+                DB: salsa::storage::JarFromJars<Self> + salsa::storage::DbWithJar<Self>,
+            {
+                (
+                    let #field_var_names = <#field_tys as salsa::storage::IngredientsFor>::create_ingredients(routes);
+                )*
+                Self(#(#field_var_names),*)
+            }
+        }
+    }
+
+

The code to create the ingredients for any particular item is generated by their associated macros (e.g., #[salsa::tracked], #[salsa::input]), but it always follows a particular structure. +To create an ingredient, we first invoke Routes::push which creates the routes to that ingredient and assigns it an IngredientIndex. +We can then invoke (e.g.) FunctionIngredient::new to create the structure. +The routes to an ingredient are defined as closures that, given the DB::Jars, can find the data for a particular ingredient.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/plumbing/maybe_changed_after.html b/plumbing/maybe_changed_after.html index 9dc09265..6f6464a2 100644 --- a/plumbing/maybe_changed_after.html +++ b/plumbing/maybe_changed_after.html @@ -83,7 +83,7 @@ diff --git a/plumbing/query_groups.html b/plumbing/query_groups.html deleted file mode 100644 index ac0e8690..00000000 --- a/plumbing/query_groups.html +++ /dev/null @@ -1,451 +0,0 @@ - - - - - - Query groups - Salsa - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - -
-
-

Query groups and query group structs

-

When you define a query group trait:

-
#[salsa::query_group(HelloWorldStorage)]
-trait HelloWorld {
-    // For each query, we give the name, some input keys (here, we
-    // have one key, `()`) and the output type `Arc<String>`. We can
-    // use attributes to give other configuration:
-    //
-    // - `salsa::input` indicates that this is an "input" to the system,
-    //   which must be explicitly set. The `salsa::query_group` method
-    //   will autogenerate a `set_input_string` method that can be
-    //   used to set the input.
-    #[salsa::input]
-    fn input_string(&self, key: ()) -> Arc<String>;
-
-    // This is a *derived query*, meaning its value is specified by
-    // a function (see Step 2, below).
-    fn length(&self, key: ()) -> usize;
-}
-
-

the salsa::query_group macro generates a number of things, shown in the sample -generated code below (details in the sections to come).

-

and associated storage struct) that represent things which don't have "public" -Note that there are a number of structs and types (e.g., the group descriptor -names. We currently generate mangled names with __ afterwards, but those names -are not meant to be exposed to the user (ideally we'd use hygiene to enforce -this).

-
// First, a copy of the trait, though with extra supertraits and
-// sometimes with some extra methods (e.g., `set_input_string`)
-trait HelloWorld: 
-    salsa::Database + 
-    salsa::plumbing::HasQueryGroup<HelloWorldStorage>
-{
-    fn input_string(&self, key: ()) -> Arc<String>;
-    fn set_input_string(&mut self, key: (), value: Arc<String>);
-    fn length(&self, key: ()) -> usize;
-}
-
-// Next, the "query group struct", whose name was given by the
-// user. This struct implements the `QueryGroup` trait which
-// defines a few associated types common to the entire group.
-struct HelloWorldStorage { }
-impl salsa::plumbing::QueryGroup for HelloWorldStorage {
-    type DynDb = dyn HelloWorld;
-    type GroupStorage = HelloWorldGroupStorage__;
-}
-
-// Next, a blanket impl of the `HelloWorld` trait. This impl
-// works for any database `DB` that implements the
-// appropriate `HasQueryGroup`.
-impl<DB> HelloWorld for DB
-where
-  DB: salsa::Database,
-  DB: salsa::plumbing::HasQueryGroup<HelloWorldStorage>,
-{
-  ...
-}
-
-// Next, for each query, a "query struct" that represents it.
-// The query struct has inherent methods like `in_db` and
-// implements the `Query` trait, which defines various
-// details about the query (e.g., its key, value, etc).
-pub struct InputQuery { }
-impl InputQuery { /* definition for `in_db`, etc */ }
-impl salsa::Query for InputQuery {
-    /* associated types */
-}
-
-// Same as above, but for the derived query `length`.
-// For derived queries, we also implement `QueryFunction`
-// which defines how to execute the query.
-pub struct LengthQuery { }
-impl salsa::Query for LengthQuery {
-    ...
-}
-impl salsa::QueryFunction for LengthQuery {
-    ...
-}
-
-// Finally, the group storage, which contains the actual
-// hashmaps and other data used to implement the queries.
-struct HelloWorldGroupStorage__ { .. }
-
-

The group struct and QueryGroup trait

-

The group struct is the only thing we generate whose name is known to the user. -For a query group named Foo, it is conventionally called FooStorage, hence -the name HelloWorldStorage in our example.

-

Despite the name "Storage", the struct itself has no fields. It exists only to -implement the QueryGroup trait. This trait has a number of associated types -that reference various bits of the query group, including the actual "group -storage" struct:

-
struct HelloWorldStorage { }
-impl salsa::plumbing::QueryGroup for HelloWorldStorage {
-    type DynDb = dyn HelloWorld;
-    type GroupStorage = HelloWorldGroupStorage__; // generated struct
-}
-
-

We'll go into detail on these types below and the role they play, but one that -we didn't mention yet is GroupData. That is a kind of hack used to manage -send/sync around slots, and it gets covered in the section on slots.

-

Impl of the hello world trait

-

Ultimately, every salsa query group is going to be implemented by your final -database type, which is not currently known to us (it is created by combining -multiple salsa query groups). In fact, this salsa query group could be composed -into multiple database types. However, we want to generate the impl of the query-group -trait here in this crate, because this is the point where the trait definition is visible -and known to us (otherwise, we'd have to duplicate the method definitions).

-

So what we do is that we define a different trait, called plumbing::HasQueryGroup<G>, -that can be implemented by the database type. HasQueryGroup is generic over -the query group struct. So then we can provide an impl of HelloWorld for any -database type DB where DB: HasQueryGroup<HelloWorldStorage>. This -HasQueryGroup defines a few methods that, given a DB, give access to the -data for the query group and a few other things.

-

Thus we can generate an impl that looks like:

-
impl<DB> HelloWorld for DB
-where
-    DB: salsa::Database,
-    DB: salsa::plumbing::HasQueryGroup<HelloWorld>
-{
-    ...
-    fn length(&self, key: ()) -> Arc<String> {
-      <Self as salsa::plumbing::GetQueryTable<HelloWorldLength__>>::get_query_table(self).get(())
-    }
-}
-
-

You can see that the various methods just hook into generic functions in the -salsa::plumbing module. These functions are generic over the query types -(HelloWorldLength__) that will be described shortly. The details of the "query -table" are covered in a future section, but in short this code pulls out the -hasmap for storing the length results and invokes the generic salsa logic to -check for a valid result, etc.

-

For each query, a query struct

-

As we referenced in the previous section, each query in the trait gets a struct -that represents it. This struct is named after the query, converted into snake -case and with the word Query appended. In typical Salsa workflows, these -structs are not meant to be named or used, but in some cases it may be required. -For e.g. the length query, this structs might look something like:

-
struct LengthQuery { }
-
-

The struct also implements the plumbing::Query trait, which defines -a bunch of metadata about the query (and repeats, for convenience, -some of the data about the group that the query is in):

-
            impl salsa::Query for #qt
-            {
-                type Key = (#(#keys),*);
-                type Value = #value;
-                type Storage = #storage;
-
-                const QUERY_INDEX: u16 = #query_index;
-
-                const QUERY_NAME: &'static str = #query_name;
-
-                fn query_storage<'a>(
-                    group_storage: &'a <Self as salsa::QueryDb<'_>>::GroupStorage,
-                ) -> &'a std::sync::Arc<Self::Storage> {
-                    &group_storage.#fn_name
-                }
-
-                fn query_storage_mut<'a>(
-                    group_storage: &'a <Self as salsa::QueryDb<'_>>::GroupStorage,
-                ) -> &'a std::sync::Arc<Self::Storage> {
-                    &group_storage.#fn_name
-                }
-            }
-
-

Depending on the kind of query, we may also generate other impls, such as an -impl of salsa::plumbing::QueryFunction, which defines the methods for -executing the body of a query. This impl would then include a call to the user's -actual function.

-
                impl salsa::plumbing::QueryFunction for #qt
-                {
-                    fn execute(db: &<Self as salsa::QueryDb<'_>>::DynDb, #key_pattern: <Self as salsa::Query>::Key)
-                        -> <Self as salsa::Query>::Value {
-                        invoke(db, #(#key_names),*)
-                    }
-
-                    recover
-                }
-
-

Group storage

-

The "group storage" is the actual struct that contains all the hashtables and -so forth for each query. The types of these are ultimately defined by the -Storage associated type for each query type. The struct is generic over the -final database type:

-
struct HelloWorldGroupStorage__ {
-    input: <InputQuery as Query::Storage,
-    length: <LengthQuery as Query>::Storage,
-}
-
-

We also generate some inherent methods. First, a new method that takes -the group index as a parameter and passes it along to each of the query -storage new methods:

-
        impl #group_storage {
-            trait_vis fn new(group_index: u16) -> Self {
-                group_storage {
-                    (
-                        queries_with_storage:
-                        std::sync::Arc::new(salsa::plumbing::QueryStorageOps::new(group_index)),
-                    )*
-                }
-            }
-        }
-
-

And then various methods that will dispatch from a DatabaseKeyIndex that -corresponds to this query group into the appropriate query within the group. -Each has a similar structure of matching on the query index and then delegating -to some method defined by the query storage:

-
        impl #group_storage {
-            trait_vis fn fmt_index(
-                &self,
-                db: &(#dyn_db + '_),
-                input: salsa::DatabaseKeyIndex,
-                fmt: &mut std::fmt::Formatter<'_>,
-            ) -> std::fmt::Result {
-                match input.query_index() {
-                    fmt_ops
-                    i => panic!("salsa: impossible query index {}", i),
-                }
-            }
-
-            trait_vis fn maybe_changed_after(
-                &self,
-                db: &(#dyn_db + '_),
-                input: salsa::DatabaseKeyIndex,
-                revision: salsa::Revision,
-            ) -> bool {
-                match input.query_index() {
-                    maybe_changed_ops
-                    i => panic!("salsa: impossible query index {}", i),
-                }
-            }
-
-            trait_vis fn cycle_recovery_strategy(
-                &self,
-                db: &(#dyn_db + '_),
-                input: salsa::DatabaseKeyIndex,
-            ) -> salsa::plumbing::CycleRecoveryStrategy {
-                match input.query_index() {
-                    cycle_recovery_strategy_ops
-                    i => panic!("salsa: impossible query index {}", i),
-                }
-            }
-
-            trait_vis fn for_each_query(
-                &self,
-                _runtime: &salsa::Runtime,
-                mut op: &mut dyn FnMut(&dyn salsa::plumbing::QueryStorageMassOps),
-            ) {
-                for_each_ops
-            }
-        }
-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - diff --git a/plumbing/query_ops.html b/plumbing/query_ops.html index 941e1d9e..21dfcfda 100644 --- a/plumbing/query_ops.html +++ b/plumbing/query_ops.html @@ -83,7 +83,7 @@ @@ -162,7 +162,7 @@ where