diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 4602916ed..b7d4edc80 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -7,7 +7,6 @@ - [Overview](./overview.md) - [Tutorial: calc language](./tutorial.md) - [Basic structure](./tutorial/structure.md) - - [Jars and databases](./tutorial/jar.md) - [Defining the database struct](./tutorial/db.md) - [Defining the IR: the various "salsa structs"](./tutorial/ir.md) - [Defining the parser: memoized functions and inputs](./tutorial/parser.md) @@ -28,7 +27,6 @@ - [How Salsa works](./how_salsa_works.md) - [Videos](./videos.md) - [Plumbing](./plumbing.md) - - [Jars and ingredients](./plumbing/jars_and_ingredients.md) - [Databases and runtime](./plumbing/database_and_runtime.md) - [The db lifetime on tracked/interned structs](./plumbing/db_lifetime.md) - [Tracked structures](./plumbing/tracked_structs.md) diff --git a/book/src/plumbing.md b/book/src/plumbing.md index f6eaab739..5faa49bf3 100644 --- a/book/src/plumbing.md +++ b/book/src/plumbing.md @@ -7,7 +7,6 @@ We refer to this as the "plumbing". The plumbing section is broken up into chapters: -- The [jars and ingredients](./plumbing/jars_and_ingredients.md) covers how each salsa item (like a tracked function) specifies what data it needs and runtime, and how links between items work. - The [database and runtime](./plumbing/database_and_runtime.md) covers the data structures that are used at runtime to coordinate workers, trigger cancellation, track which functions are active and what dependencies they have accrued, and so forth. - The [query operations](./plumbing/query_ops.md) chapter describes how the major operations on function ingredients work. This text was written for an older version of salsa but the logic is the same: - The [maybe changed after](./plumbing/maybe_changed_after.md) operation determines when a memoized value for a tracked function is out of date. diff --git a/book/src/plumbing/database_and_runtime.md b/book/src/plumbing/database_and_runtime.md index cc889c546..f53d98e20 100644 --- a/book/src/plumbing/database_and_runtime.md +++ b/book/src/plumbing/database_and_runtime.md @@ -4,7 +4,7 @@ A salsa database struct is declared by the user with the `#[salsa::db]` annotati It contains all the data that the program needs to execute: ```rust,ignore -#[salsa::db(jar0...jarn)] +#[salsa::db] struct MyDatabase { storage: Storage, maybe_other_fields: u32, @@ -28,12 +28,12 @@ The `Snapshot` method returns a `Snapshot` type, which prevents these clones 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](./jars_and_ingredients.md), but it also contains some synchronization information (a cond var). This is used for cancellation, as described below. +- The `Shared` struct, which contains the data stored across all snapshots, such as 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](./jars_and_ingredients.md). 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 `Routes` struct, which contains the information to find any particular ingredient -- this is also shared across all handles. 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 shared data of its own. -## Incrementing the revision counter and getting mutable access to the jars +## Incrementing the revision counter 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` type that permits only `&`-deref, @@ -47,12 +47,6 @@ 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: - -```rust -{{#include ../../../src/storage.rs:jars_mut}} -``` - The key initial point is that it invokes `cancel_other_workers` before proceeding: ```rust diff --git a/book/src/plumbing/fetch.md b/book/src/plumbing/fetch.md index e0319d1c8..66533e4df 100644 --- a/book/src/plumbing/fetch.md +++ b/book/src/plumbing/fetch.md @@ -1,9 +1,5 @@ # Fetch -```rust,no_run,noplayground -{{#include ../../../src/plumbing.rs:fetch}} -``` - The `fetch` operation computes the value of a query. It prefers to reuse memoized values when it can. ## Input queries diff --git a/book/src/plumbing/jars_and_ingredients.md b/book/src/plumbing/jars_and_ingredients.md deleted file mode 100644 index 065c41277..000000000 --- a/book/src/plumbing/jars_and_ingredients.md +++ /dev/null @@ -1,208 +0,0 @@ -# Jars and ingredients - -This page covers how data is organized in Salsa and how links between Salsa items (e.g., dependency tracking) work. - -## 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: - -```rust -#[salsa::tracked] -fn foo(db: &dyn Db, input: MyInput) { } -``` - -...and so is a Salsa input... - -```rust -#[salsa::input] -struct MyInput { } -``` - -...or a tracked struct: - -```rust -#[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. - -[`FunctionIngredient`]: https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/function.rs#L42 -[`TrackedStructIngredient`]: https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/tracked_struct.rs#L18 - -### 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. - -[`TrackedStruct::new_struct`]: https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/tracked_struct.rs#L76 -[`TrackedFunction::fetch`]: https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/function/fetch.rs#L15 - -### The `Ingredient` trait - -Each ingredient implements the [`Ingredient`] 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: - -[`Ingredient`]: https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/ingredient.rs#L15 -[`maybe_changed_after`]: https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/ingredient.rs#L21-L22 - -We'll see below that each database `DB` is able to take an `IngredientIndex` and use that to get an `&dyn Ingredient` for the corresponding ingredient. -This allows the database to perform generic operations on an indexed 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: - -```rust,ignore -#[salsa::jar] -struct Jar( - foo, - MyInput, - MyStruct -); -``` - -This expands to a struct like so: - -```rust -struct Jar( - ::Ingredient, - ::Ingredient, - ::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 `::Ingredient` gives the ingredients needed by `I`. - -### A 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 doesn'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... - -```rust,ignore -#[salsa::db(Jar1, ..., JarN)] -struct MyDatabase { - storage: salsa::Storage -} -``` - -...the `salsa::db` macro would generate a `HasJars` impl that (among other things) contains `type Jars = (Jar1, ..., JarN)`: - -```rust,ignore -{{#include ../../../components/salsa-macros/src/db.rs:HasJars}} -``` - -In turn, the `salsa::Storage` 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. - -[`IngredientIndex`]: https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/routes.rs#L5-L9 - -### 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` reference. -The route table allows us to go from the `IngredientIndex` for a particular ingredient -to its `&dyn Ingredient` 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`: - -```rust,ignore -{{#include ../../../src/key.rs:DatabaseKeyIndex}} -``` - -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` -- and then invoking the `maybe_changed_since` method on that trait object. - -### `HasJarsDyn` - -There is one catch in the above setup. -The user's code always interacts with a `dyn crate::Db` value, where `crate::Db` is the trait defined by the jar; the `crate::Db` trait extends `salsa::HasJar` which in turn extends `salsa::Database`. -Ideally, we would have `salsa::Database` extend `salsa::HasJars`, which is the main trait that gives access to the jars data. -But we don't want to do that because `HasJars` defines an associated type `Jars`, and that would mean that every reference to `dyn crate::Db` would have to specify the jars type using something like `dyn crate::Db`. -This would be unergonomic, but what's worse, it would actually be impossible: the final Jars type combines the jars from multiple crates, and so it is not known to any individual jar crate. -To workaround this, `salsa::Database` in fact extends _another_ trait, `HasJarsDyn`, that doesn't reveal the `Jars` or ingredient types directly, but just has various method that can be performed on an ingredient, given its `IngredientIndex`. -Traits like `Ingredient` require knowing the full `DB` type. -If we had one function ingredient directly invoke a method on `Ingredient`, 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 a method that combines the "find ingredient, invoking method" steps into one method: - -```rust,ignore aasaaasdfijjAasdfa -{{#include ../../../src/storage.rs:HasJarsDyn}} -``` - -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 discuss is how the database is initialized. -The `Default` implementation for `Storage` does the work: - -```rust,ignore -{{#include ../../../src/storage.rs: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 invokes `salsa::plumbing::create_jars_inplace` to allocate memory for the jars, and then invokes the `Jar::init_jar` method on each of the jars to initialize them: - -```rust,ignore -{{#include ../../../components/salsa-macros/src/db.rs:create_jars}} -``` - -This implementation for `init_jar` is generated by the `#[salsa::jar]` macro, and simply walks over the representative type for each salsa item and asks _it_ to create its ingredients - -```rust,ignore -{{#include ../../../components/salsa-macros/src/jar.rs:init_jar}} -``` - -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 a function such as `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/book/src/plumbing/maybe_changed_after.md b/book/src/plumbing/maybe_changed_after.md index 754ebd269..d41a20441 100644 --- a/book/src/plumbing/maybe_changed_after.md +++ b/book/src/plumbing/maybe_changed_after.md @@ -1,9 +1,5 @@ # Maybe changed after -```rust,no_run,noplayground -{{#include ../../../src/plumbing.rs:maybe_changed_after}} -``` - The `maybe_changed_after` operation computes whether a query's value *may have changed* **after** the given revision. In other words, `Q.maybe_change_since(R)` is true if the value of the query `Q` may have changed in the revisions `(R+1)..R_now`, where `R_now` is the current revision. Note that it doesn't make sense to ask `maybe_changed_after(R_now)`. ## Input queries diff --git a/book/src/plumbing/query_ops.md b/book/src/plumbing/query_ops.md index f94d0f28f..eca4e396c 100644 --- a/book/src/plumbing/query_ops.md +++ b/book/src/plumbing/query_ops.md @@ -1,14 +1,6 @@ # Query operations -Each of the query storage struct implements the `QueryStorageOps` trait found in the [`plumbing`] module: - -```rust,no_run,noplayground -{{#include ../../../src/plumbing.rs:QueryStorageOps}} -``` - - which defines the basic operations that all queries support. The most important are these two: +The most important basic operations that all queries support are: * [maybe changed after](./maybe_changed_after.md): Returns true if the value of the query (for the given key) may have changed since the given revision. * [Fetch](./fetch.md): Returns the up-to-date value for the given K (or an error in the case of an "unrecovered" cycle). - -[`plumbing`]: https://github.com/salsa-rs/salsa/blob/master/src/plumbing.rs diff --git a/book/src/plumbing/terminology/ingredient.md b/book/src/plumbing/terminology/ingredient.md index 18ed541b0..c10774280 100644 --- a/book/src/plumbing/terminology/ingredient.md +++ b/book/src/plumbing/terminology/ingredient.md @@ -1,4 +1,3 @@ # Ingredient -An *ingredient* is an individual piece of storage used to create a [salsa item](./salsa_item.md) -See the [jars and ingredients](../jars_and_ingredients.md) chapter for more details. \ No newline at end of file +An *ingredient* is an individual piece of storage used to create a [salsa item](./salsa_item.md) \ No newline at end of file diff --git a/book/src/plumbing/terminology/salsa_item.md b/book/src/plumbing/terminology/salsa_item.md index cff09c762..db04bbfc5 100644 --- a/book/src/plumbing/terminology/salsa_item.md +++ b/book/src/plumbing/terminology/salsa_item.md @@ -1,4 +1,3 @@ # Salsa item -A salsa item is something that is decorated with a `#[salsa::foo]` macro, like a tracked function or struct. -See the [jars and ingredients](../jars_and_ingredients.md) chapter for more details. \ No newline at end of file +A salsa item is something that is decorated with a `#[salsa::foo]` macro, like a tracked function or struct. \ No newline at end of file diff --git a/book/src/tutorial/db.md b/book/src/tutorial/db.md index 04a77e92e..570d4040f 100644 --- a/book/src/tutorial/db.md +++ b/book/src/tutorial/db.md @@ -1,7 +1,6 @@ # Defining the database struct -Now that we have defined a [jar](./jar.md), we need to create the **database struct**. -The database struct is where all the jars come together. +First, we need to create the **database struct**. Typically it is only used by the "driver" of your application; the one which starts up the program, supplies the inputs, and relays the outputs. @@ -13,11 +12,8 @@ In `calc`, the database struct is in the [`db`] module, and it looks like this: {{#include ../../../examples/calc/db.rs:db_struct}} ``` -The `#[salsa::db(...)]` attribute takes a list of all the jars to include. -The struct must have a field named `storage` whose type is `salsa::Storage`, but it can also contain whatever other fields you want. -The `storage` struct owns all the data for the jars listed in the `db` attribute. - -The `salsa::db` attribute autogenerates a bunch of impls for things like the `salsa::HasJar` trait that we saw earlier. +The `#[salsa::db]` attribute marks the struct as a database. +It must have a field named `storage` whose type is `salsa::Storage`, but it can also contain whatever other fields you want. ## Implementing the `salsa::Database` trait @@ -34,11 +30,3 @@ If you want to permit accessing your database from multiple threads at once, the ```rust {{#include ../../../examples/calc/db.rs:par_db_impl}} ``` - -## Implementing the traits for each jar - -The `Database` struct also needs to implement the [database traits for each jar](./jar.md#database-trait-for-the-jar). -In our case, though, we already wrote that impl as a [blanket impl alongside the jar itself](./jar.md#implementing-the-database-trait-for-the-jar), -so no action is needed. -This is the recommended strategy unless your trait has custom members that depend on fields of the `Database` itself -(for example, sometimes the `Database` holds some kind of custom resource that you want to give access to). diff --git a/book/src/tutorial/ir.md b/book/src/tutorial/ir.md index 7949c60bc..8938f5df5 100644 --- a/book/src/tutorial/ir.md +++ b/book/src/tutorial/ir.md @@ -17,9 +17,6 @@ All Salsa structs store the actual values of their fields in the Salsa database. This permits us to track when the values of those fields change to figure out what work will need to be re-executed. When you annotate a struct with one of the above Salsa attributes, Salsa actually generates a bunch of code to link that struct into the database. -This code must be connected to some [jar](./jar.md). -By default, this is `crate::Jar`, but you can specify a different jar with the `jar=` attribute (e.g., `#[salsa::input(jar = MyJar)]`). -You must also list the struct in the jar definition itself, or you will get errors. ## Input structs diff --git a/book/src/tutorial/jar.md b/book/src/tutorial/jar.md deleted file mode 100644 index 9f453ae92..000000000 --- a/book/src/tutorial/jar.md +++ /dev/null @@ -1,72 +0,0 @@ -# Jars and databases - -Before we can define the interesting parts of our Salsa program, we have to setup a bit of structure that defines the Salsa **database**. -The database is a struct that ultimately stores all of Salsa's intermediate state, such as the memoized return values from [tracked functions]. - -[tracked functions]: ../overview.md#tracked-functions - -The database itself is defined in terms of intermediate structures, called **jars**[^jar], which themselves contain the data for each function. -This setup allows Salsa programs to be divided amongst many crates. -Typically, you define one jar struct per crate, and then when you construct the final database, you simply list the jar structs. -This permits the crates to define private functions and other things that are members of the jar struct, but not known directly to the database. - -[^jar]: Jars of salsa -- get it? Get it??[^java] - -[^java]: OK, maybe it also brings to mind Java `.jar` files, but there's no real relationship. A jar is just a Rust struct, not a packaging format. - -## Defining a jar struct - -To define a jar struct, you create a tuple struct with the `#[salsa::jar]` annotation: - -```rust -{{#include ../../../examples/calc/main.rs:jar_struct}} -``` - -Although it's not required, it's highly recommended to put the `jar` struct at the root of your crate, so that it can be referred to as `crate::Jar`. -All of the other Salsa annotations reference a jar struct, and they all default to the path `crate::Jar`. -If you put the jar somewhere else, you will have to override that default. - -## Defining the database trait - -The `#[salsa::jar]` annotation also includes a `db = Db` field. -The value of this field (normally `Db`) is the name of a trait that represents the database. -Salsa programs never refer _directly_ to the database; instead, they take a `&dyn Db` argument. -This allows for separate compilation, where you have a database that contains the data for two jars, but those jars don't depend on one another. - -The database trait for our `calc` crate is very simple: - -```rust -{{#include ../../../examples/calc/main.rs:jar_db}} -``` - -When you define a database trait like `Db`, the one thing that is required is that it must have a supertrait `salsa::DbWithJar`, -where `Jar` is the jar struct. If your jar depends on other jars, you can have multiple such supertraits (e.g., `salsa::DbWithJar`). - -Typically the `Db` trait has no other members or supertraits, but you are also free to add whatever other things you want in the trait. -When you define your final database, it will implement the trait, and you can then define the implementation of those other things. -This allows you to create a way for your jar to request context or other info from the database that is not moderated through Salsa, -should you need that. - -## Implementing the database trait for the jar - -The `Db` trait must be implemented by the database struct. -We're going to define the database struct in a [later section](./db.md), -and one option would be to simply implement the jar `Db` trait there. -However, since we don't define any custom logic in the trait, -a common choice is to write a blanket impl for any type that implements `DbWithJar`, -and that's what we do here: - -```rust -{{#include ../../../examples/calc/main.rs:jar_db_impl}} -``` - -## Summary - -If the concept of a jar seems a bit abstract to you, don't overthink it. The TL;DR is that when you create a Salsa program, you need to perform the following steps: - -- In each of your crates: - - Define a `#[salsa::jar(db = Db)]` struct, typically at `crate::Jar`, and list each of your various Salsa-annotated things inside of it. - - Define a `Db` trait, typically at `crate::Db`, that you will use in memoized functions and elsewhere to refer to the database struct. -- Once, typically in your final crate: - - Define a database `D`, as described in the [next section](./db.md), that will contain a list of each of the jars for each of your crates. - - Implement the `Db` traits for each jar for your database type `D` (often we do this through blanket impls in the jar crates). diff --git a/book/src/tutorial/parser.md b/book/src/tutorial/parser.md index 43e86f51d..ffa493e65 100644 --- a/book/src/tutorial/parser.md +++ b/book/src/tutorial/parser.md @@ -50,12 +50,8 @@ Setting up a scheme like this is relatively easy in Salsa and uses the same prin ### Parameters to a tracked function The **first** parameter to a tracked function is **always** the database, `db: &dyn crate::Db`. -It must be a `dyn` value of whatever database is associated with the jar. The **second** parameter to a tracked function is **always** some kind of Salsa struct. -The first parameter to a memoized function is always the database, -which should be a `dyn Trait` value for the database trait associated with the jar -(the default jar is `crate::Jar`). Tracked functions may take other arguments as well, though our examples here do not. Functions that take additional arguments are less efficient and flexible.