mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-08-04 11:00:05 +00:00
deploy: 7872a53ef2
This commit is contained in:
parent
f298ae4617
commit
a2ed179330
15 changed files with 218 additions and 226 deletions
|
@ -146,8 +146,8 @@
|
|||
<main>
|
||||
<h1 id="how-salsa-works"><a class="header" href="#how-salsa-works">How Salsa works</a></h1>
|
||||
<h2 id="video-available"><a class="header" href="#video-available">Video available</a></h2>
|
||||
<p>To get the most complete introduction to Salsa's inner works, check
|
||||
out <a href="https://youtu.be/_muY4HjSqVw">the "How Salsa Works" video</a>. If
|
||||
<p>To get the most complete introduction to Salsa's inner workings, check
|
||||
out <a href="https://youtu.be/_muY4HjSqVw">the "How Salsa Works" video</a>. If
|
||||
you'd like a deeper dive, <a href="https://www.youtube.com/watch?v=i_IhACacPRY">the "Salsa in more depth"
|
||||
video</a> digs into the
|
||||
details of the incremental algorithm.</p>
|
||||
|
@ -156,20 +156,20 @@ details of the incremental algorithm.</p>
|
|||
</blockquote>
|
||||
<h2 id="key-idea"><a class="header" href="#key-idea">Key idea</a></h2>
|
||||
<p>The key idea of <code>salsa</code> is that you define your program as a set of
|
||||
<strong>queries</strong>. Every query is used like function <code>K -> V</code> that maps from
|
||||
<strong>queries</strong>. Every query is used like a function <code>K -> V</code> that maps from
|
||||
some key of type <code>K</code> to a value of type <code>V</code>. Queries come in two basic
|
||||
varieties:</p>
|
||||
<ul>
|
||||
<li><strong>Inputs</strong>: the base inputs to your system. You can change these
|
||||
whenever you like.</li>
|
||||
<li><strong>Functions</strong>: pure functions (no side effects) that transform your
|
||||
inputs into other values. The results of queries is memoized to
|
||||
inputs into other values. The results of queries are memoized to
|
||||
avoid recomputing them a lot. When you make changes to the inputs,
|
||||
we'll figure out (fairly intelligently) when we can re-use these
|
||||
memoized values and when we have to recompute them.</li>
|
||||
</ul>
|
||||
<h2 id="how-to-use-salsa-in-three-easy-steps"><a class="header" href="#how-to-use-salsa-in-three-easy-steps">How to use Salsa in three easy steps</a></h2>
|
||||
<p>Using salsa is as easy as 1, 2, 3...</p>
|
||||
<p>Using Salsa is as easy as 1, 2, 3...</p>
|
||||
<ol>
|
||||
<li>Define one or more <strong>query groups</strong> that contain the inputs
|
||||
and queries you will need. We'll start with one such group, but
|
||||
|
@ -186,7 +186,7 @@ example</a>, which has a number of comments explaining how
|
|||
things work.</p>
|
||||
<h2 id="digging-into-the-plumbing"><a class="header" href="#digging-into-the-plumbing">Digging into the plumbing</a></h2>
|
||||
<p>Check out the <a href="plumbing.html">plumbing</a> chapter to see a deeper explanation of the
|
||||
code that salsa generates and how it connects to the salsa library.</p>
|
||||
code that Salsa generates and how it connects to the Salsa library.</p>
|
||||
|
||||
</main>
|
||||
|
||||
|
|
|
@ -149,12 +149,12 @@
|
|||
<p>⚠️ <strong>IN-PROGRESS VERSION OF SALSA.</strong> ⚠️</p>
|
||||
<p>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 <code>salsa-2022</code> crate.</p>
|
||||
</blockquote>
|
||||
<p>This page contains a brief overview of the pieces of a salsa program.
|
||||
<p>This page contains a brief overview of the pieces of a Salsa program.
|
||||
For a more detailed look, check out the <a href="./tutorial.html">tutorial</a>, which walks through the creation of an entire project end-to-end.</p>
|
||||
<h2 id="goal-of-salsa"><a class="header" href="#goal-of-salsa">Goal of Salsa</a></h2>
|
||||
<p>The goal of salsa is to support efficient <strong>incremental recomputation</strong>.
|
||||
salsa is used in rust-analyzer, for example, to help it recompile your program quickly as you type.</p>
|
||||
<p>The basic idea of a salsa program is like this:</p>
|
||||
<p>The goal of Salsa is to support efficient <strong>incremental recomputation</strong>.
|
||||
Salsa is used in rust-analyzer, for example, to help it recompile your program quickly as you type.</p>
|
||||
<p>The basic idea of a Salsa program is like this:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -178,9 +178,9 @@ But this picture still conveys a few important concepts:</p>
|
|||
<li>The mutation of inputs always happens outside of <code>your_program</code>, as part of this master loop.</li>
|
||||
</ul>
|
||||
<h2 id="database"><a class="header" href="#database">Database</a></h2>
|
||||
<p>Each time you run your program, salsa remembers the values of each computation in a <strong>database</strong>.
|
||||
<p>Each time you run your program, Salsa remembers the values of each computation in a <strong>database</strong>.
|
||||
When the inputs change, it consults this database to look for values that can be reused.
|
||||
The database is also used to implement interning (making a canonical version of a value that can be copied around and cheaply compared for equality) and other convenient salsa features.</p>
|
||||
The database is also used to implement interning (making a canonical version of a value that can be copied around and cheaply compared for equality) and other convenient Salsa features.</p>
|
||||
<h2 id="inputs"><a class="header" href="#inputs">Inputs</a></h2>
|
||||
<p>Every Salsa program begins with an <strong>input</strong>.
|
||||
Inputs are special structs that define the starting point of your program.
|
||||
|
@ -208,8 +208,8 @@ Because the values of input fields are stored in the database, you also give an
|
|||
);
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<h3 id="salsa-structs-are-just-an-integer"><a class="header" href="#salsa-structs-are-just-an-integer">Salsa structs are just an integer</a></h3>
|
||||
<p>The <code>ProgramFile</code> struct generates by the <code>salsa::input</code> macro doesn't actually store any data. It's just a newtyped integer id:</p>
|
||||
<h3 id="salsa-structs-are-just-integers"><a class="header" href="#salsa-structs-are-just-integers">Salsa structs are just integers</a></h3>
|
||||
<p>The <code>ProgramFile</code> struct generated by the <code>salsa::input</code> macro doesn't actually store any data. It's just a newtyped integer id:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -271,10 +271,10 @@ fn parse_file(db: &dyn crate::Db, file: ProgramFile) -> Ast {
|
|||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>When you call a tracked function, salsa will track which inputs it accesses (in this example, <code>file.contents(db)</code>).
|
||||
<p>When you call a tracked function, Salsa will track which inputs it accesses (in this example, <code>file.contents(db)</code>).
|
||||
It will also memoize the return value (the <code>Ast</code>, in this case).
|
||||
If you call a tracked function twice, salsa checks if the inputs have changed; if not, it can return the memoized value.
|
||||
The algorithm salsa uses to decide when a tracked function needs to be re-executed is called the <a href="./reference/algorithm.html">red-green algorithm</a>, and it's where the name salsa comes from.</p>
|
||||
If you call a tracked function twice, Salsa checks if the inputs have changed; if not, it can return the memoized value.
|
||||
The algorithm Salsa uses to decide when a tracked function needs to be re-executed is called the <a href="./reference/algorithm.html">red-green algorithm</a>, and it's where the name Salsa comes from.</p>
|
||||
<p>Tracked functions have to follow a particular structure:</p>
|
||||
<ul>
|
||||
<li>They must take a <code>&</code>-reference to the database as their first argument.
|
||||
|
@ -282,7 +282,7 @@ The algorithm salsa uses to decide when a tracked function needs to be re-execut
|
|||
<li>Note that because this is an <code>&</code>-reference, it is not possible to create or modify inputs during a tracked function!</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>They must take a "salsa struct" as the second argument -- in our example, this is an input struct, but there are other kinds of salsa structs we'll describe shortly.</li>
|
||||
<li>They must take a "Salsa struct" as the second argument -- in our example, this is an input struct, but there are other kinds of Salsa structs we'll describe shortly.</li>
|
||||
<li>They <em>can</em> take additional arguments, but it's faster and better if they don't.</li>
|
||||
</ul>
|
||||
<p>Tracked functions can return any clone-able type. A clone is required since, when the value is cached, the result will be cloned out of the database. Tracked functions can also be annotated with <code>#[return_ref]</code> if you would prefer to return a reference into the database instead (if <code>parse_file</code> were so annotated, then callers would actually get back an <code>&Ast</code>, for example).</p>
|
||||
|
@ -339,7 +339,7 @@ struct Item {
|
|||
<p>Maybe our parser first creates an <code>Item</code> with the name <code>foo</code> and then later a second <code>Item</code> with the name <code>bar</code>.
|
||||
Then the user changes the input to reorder the functions.
|
||||
Although we are still creating the same number of items, we are now creating them in the reverse order, so the naive algorithm will match up the <em>old</em> <code>foo</code> struct with the new <code>bar</code> struct.
|
||||
This will look to salsa as though the <code>foo</code> function was renamed to <code>bar</code> and the <code>bar</code> function was renamed to <code>foo</code>.
|
||||
This will look to Salsa as though the <code>foo</code> function was renamed to <code>bar</code> and the <code>bar</code> function was renamed to <code>foo</code>.
|
||||
We'll still get the right result, but we might do more recomputation than we needed to do if we understood that they were just reordered.</p>
|
||||
<p>To address this, you can tag fields in a tracked struct as <code>#[id]</code>. These fields are then used to "match up" struct instances across executions:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
@ -353,7 +353,7 @@ struct Item {
|
|||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<h3 id="specified-the-result-of-tracked-functions-for-particular-structs"><a class="header" href="#specified-the-result-of-tracked-functions-for-particular-structs">Specified the result of tracked functions for particular structs</a></h3>
|
||||
<h3 id="specify-the-result-of-tracked-functions-for-particular-structs"><a class="header" href="#specify-the-result-of-tracked-functions-for-particular-structs">Specify the result of tracked functions for particular structs</a></h3>
|
||||
<p>Sometimes it is useful to define a tracked function but specify its value for some particular struct specially.
|
||||
For example, maybe the default way to compute the representation for a function is to read the AST, but you also have some built-in functions in your language and you want to hard-code their results.
|
||||
This can also be used to simulate a field that is initialized after the tracked struct is created.</p>
|
||||
|
@ -377,9 +377,9 @@ fn create_builtin_item(db: &dyn crate::Db) -> Item {
|
|||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>Specifying is only possible for tracked functions that take a single tracked struct as argument (besides the database).</p>
|
||||
<p>Specifying is only possible for tracked functions that take a single tracked struct as an argument (besides the database).</p>
|
||||
<h2 id="interned-structs"><a class="header" href="#interned-structs">Interned structs</a></h2>
|
||||
<p>The final kind of salsa struct are <strong>interned structs</strong>.
|
||||
<p>The final kind of Salsa struct are <strong>interned structs</strong>.
|
||||
Interned structs are useful for quick equality comparison.
|
||||
They are commonly used to represent strings or other primitive values.</p>
|
||||
<p>Most compilers, for example, will define a type to represent a user identifier:</p>
|
||||
|
@ -403,10 +403,10 @@ let w2 = Word::new(db, "bar".to_string());
|
|||
let w3 = Word::new(db, "foo".to_string());
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>When you create two interned structs with the same field values, you are guaranted to get back the same integer id. So here, we know that <code>assert_eq!(w1, w3)</code> is true and <code>assert_ne!(w1, w2)</code>.</p>
|
||||
<p>When you create two interned structs with the same field values, you are guaranteed to get back the same integer id. So here, we know that <code>assert_eq!(w1, w3)</code> is true and <code>assert_ne!(w1, w2)</code>.</p>
|
||||
<p>You can access the fields of an interned struct using a getter, like <code>word.text(db)</code>. These getters respect the <code>#[return_ref]</code> annotation. Like tracked structs, the fields of interned structs are immutable.</p>
|
||||
<h2 id="accumulators"><a class="header" href="#accumulators">Accumulators</a></h2>
|
||||
<p>The final salsa concept are <strong>accumulators</strong>. Accumulators are a way to report errors or other "side channel" information that is separate from the main return value of your function.</p>
|
||||
<p>The final Salsa concept are <strong>accumulators</strong>. Accumulators are a way to report errors or other "side channel" information that is separate from the main return value of your function.</p>
|
||||
<p>To create an accumulator, you declare a type as an <em>accumulator</em>:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
|
|
|
@ -149,10 +149,10 @@
|
|||
<p>⚠️ <strong>IN-PROGRESS VERSION OF SALSA.</strong> ⚠️</p>
|
||||
<p>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 <code>salsa-2022</code> crate.</p>
|
||||
</blockquote>
|
||||
<p>This page covers how data is organized in salsa and how links between salsa items (e.g., dependency tracking) works.</p>
|
||||
<p>This page covers how data is organized in Salsa and how links between Salsa items (e.g., dependency tracking) work.</p>
|
||||
<h2 id="salsa-items-and-ingredients"><a class="header" href="#salsa-items-and-ingredients">Salsa items and ingredients</a></h2>
|
||||
<p>A <strong>salsa item</strong> is some item annotated with a salsa annotation that can be included in a jar.
|
||||
For example, a tracked function is a salsa item:</p>
|
||||
<p>A <strong>Salsa item</strong> is some item annotated with a Salsa annotation that can be included in a jar.
|
||||
For example, a tracked function is a Salsa item:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -160,7 +160,7 @@ For example, a tracked function is a salsa item:</p>
|
|||
fn foo(db: &dyn Db, input: MyInput) { }
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>...and so is a salsa input...</p>
|
||||
<p>...and so is a Salsa input...</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -176,31 +176,26 @@ struct MyInput { }
|
|||
struct MyStruct { }
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>Each salsa item needs certain bits of data at runtime to operate.
|
||||
<p>Each Salsa item needs certain bits of data at runtime to operate.
|
||||
These bits of data are called <strong>ingredients</strong>.
|
||||
Most salsa items generate a single ingredient, but sometimes they make more than one.
|
||||
Most Salsa items generate a single ingredient, but sometimes they make more than one.
|
||||
For example, a tracked function generates a <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/function.rs#L42"><code>FunctionIngredient</code></a>.
|
||||
A tracked struct however generates several ingredients, one for the struct itself (a <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/tracked_struct.rs#L18"><code>TrackedStructIngredient</code></a>,
|
||||
A tracked struct, however, generates several ingredients, one for the struct itself (a <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/tracked_struct.rs#L18"><code>TrackedStructIngredient</code></a>,
|
||||
and one <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/function.rs#L42"><code>FunctionIngredient</code></a> for each value field.</p>
|
||||
<h3 id="ingredients-define-the-core-logic-of-salsa"><a class="header" href="#ingredients-define-the-core-logic-of-salsa">Ingredients define the core logic of salsa</a></h3>
|
||||
<p>Most of the interesting salsa code lives in these ingredients.
|
||||
<h3 id="ingredients-define-the-core-logic-of-salsa"><a class="header" href="#ingredients-define-the-core-logic-of-salsa">Ingredients define the core logic of Salsa</a></h3>
|
||||
<p>Most of the interesting Salsa code lives in these ingredients.
|
||||
For example, when you create a new tracked struct, the method <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/tracked_struct.rs#L76"><code>TrackedStruct::new_struct</code></a> 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 <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/function/fetch.rs#L15"><code>TrackedFunction::fetch</code></a>,
|
||||
which decides whether there is a valid memoized value to return,
|
||||
or whether the function must be executed.</p>
|
||||
<h3 id="ingredient-interfaces-are-not-stable-or-subject-to-semver"><a class="header" href="#ingredient-interfaces-are-not-stable-or-subject-to-semver">Ingredient interfaces are not stable or subject to semver</a></h3>
|
||||
<p>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.</p>
|
||||
<h3 id="the-ingredient-trait"><a class="header" href="#the-ingredient-trait">The <code>Ingredient</code> trait</a></h3>
|
||||
<p>Each ingredient implements the <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/ingredient.rs#L15"><code>Ingredient<DB></code></a> trait, which defines generic operations supported by any kind of ingredient.
|
||||
For example, the method <code>maybe_changed_after</code> can be used to check whether some particular piece of data stored in the ingredient may have changed since a given revision:</p>
|
||||
<p>We'll see below that each database <code>DB</code> is able to take an <code>IngredientIndex</code> and use that to get a <code>&dyn Ingredient<DB></code> 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.</p>
|
||||
<p>We'll see below that each database <code>DB</code> is able to take an <code>IngredientIndex</code> and use that to get an <code>&dyn Ingredient<DB></code> 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.</p>
|
||||
<h3 id="jars-are-a-collection-of-ingredients"><a class="header" href="#jars-are-a-collection-of-ingredients">Jars are a collection of ingredients</a></h3>
|
||||
<p>When you declare a salsa jar, you list out each of the salsa items that are included in that jar:</p>
|
||||
<p>When you declare a Salsa jar, you list out each of the Salsa items that are included in that jar:</p>
|
||||
<pre><code class="language-rust ignore">#[salsa::jar]
|
||||
struct Jar(
|
||||
foo,
|
||||
|
@ -219,20 +214,19 @@ struct Jar(
|
|||
)
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>The <code>IngredientsFor</code> trait is used to define the ingredients needed by some salsa item, such as the tracked function <code>foo</code>
|
||||
or the tracked struct <code>MyInput</code>.
|
||||
Each salsa item defines a type <code>I</code>, so that <code><I as IngredientsFor>::Ingredient</code> gives the ingredients needed by <code>I</code>.</p>
|
||||
<h3 id="database-is-a-tuple-of-jars"><a class="header" href="#database-is-a-tuple-of-jars">Database is a tuple of jars</a></h3>
|
||||
<p>Salsa's database storage ultimately boils down to a tuple of jar structs,
|
||||
<p>The <code>IngredientsFor</code> trait is used to define the ingredients needed by some Salsa item, such as the tracked function <code>foo</code> or the tracked struct <code>MyInput</code>.
|
||||
Each Salsa item defines a type <code>I</code> so that <code><I as IngredientsFor>::Ingredient</code> gives the ingredients needed by <code>I</code>.</p>
|
||||
<h3 id="a-database-is-a-tuple-of-jars"><a class="header" href="#a-database-is-a-tuple-of-jars">A database is a tuple of jars</a></h3>
|
||||
<p>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.
|
||||
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.</p>
|
||||
<p>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.</p>
|
||||
<h3 id="the-hasjars-trait-and-the-jars-type"><a class="header" href="#the-hasjars-trait-and-the-jars-type">The HasJars trait and the Jars type</a></h3>
|
||||
<p>Each salsa database implements the <code>HasJars</code> trait,
|
||||
<h3 id="the-hasjars-trait-and-the-jars-type"><a class="header" href="#the-hasjars-trait-and-the-jars-type">The <code>HasJars</code> trait and the <code>Jars</code> type</a></h3>
|
||||
<p>Each Salsa database implements the <code>HasJars</code> trait,
|
||||
generated by the <code>salsa::db</code> procedural macro.
|
||||
The <code>HarJars</code> trait, among other things, defines a <code>Jars</code> associated type that maps to a tuple of the jars in the trait.</p>
|
||||
<p>For example, given a database like this...</p>
|
||||
|
@ -279,12 +273,17 @@ We can then do things like ask, "did this input change since revision R?&qu
|
|||
<li>using the ingredient index to find the route and get a <code>&dyn Ingredient<DB></code></li>
|
||||
<li>and then invoking the <code>maybe_changed_since</code> method on that trait object.</li>
|
||||
</ul>
|
||||
<h3 id="hasjarsdyn"><a class="header" href="#hasjarsdyn">HasJarsDyn</a></h3>
|
||||
<h3 id="hasjarsdyn"><a class="header" href="#hasjarsdyn"><code>HasJarsDyn</code></a></h3>
|
||||
<p>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.
|
||||
The user's code always interacts with a <code>dyn crate::Db</code> value, where <code>crate::Db</code> is the trait defined by the jar; the <code>crate::Db</code> trait extends <code>salsa::HasJar</code> which in turn extends <code>salsa::Database</code>.
|
||||
Ideally, we would have <code>salsa::Database</code> extend <code>salsa::HasJars</code>, which is the main trait that gives access to the jars data.
|
||||
But we don't want to do that because <code>HasJars</code> defines an associated type <code>Jars</code>, and that would mean that every reference to <code>dyn crate::Db</code> would have to specify the jars type using something like <code>dyn crate::Db<Jars = J></code>.
|
||||
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, <code>salsa::Database</code> in fact extends <em>another</em> trait, <code>HasJarsDyn</code>, that doesn't reveal the <code>Jars</code> or ingredient types directly, but just has various method that can be performed on an ingredient, given its <code>IngredientIndex</code>.
|
||||
Traits like <code>Ingredient<DB></code> require knowing the full <code>DB</code> type.
|
||||
If we had one function ingredient directly invoke a method on <code>Ingredient<DB></code>, that would imply that it has to be fully generic and only instantiated at the final crate, when the full database type is available.</p>
|
||||
<p>We solve this via the <code>HasJarsDyn</code> trait. The <code>HasJarsDyn</code> trait exports method that combine the "find ingredient, invoking method" steps into one method:</p>
|
||||
<p>We solve this via the <code>HasJarsDyn</code> trait. The <code>HasJarsDyn</code> trait exports a method that combines the "find ingredient, invoking method" steps into one method:</p>
|
||||
<p>[Perhaps this code snippet should only preview the HasJarsDyn method that is being referred to]</p>
|
||||
<pre><code class="language-rust ignore">/// Dyn friendly subset of HasJars
|
||||
pub trait HasJarsDyn {
|
||||
fn runtime(&self) -> &Runtime;
|
||||
|
@ -357,7 +356,7 @@ The implementation of this method is defined by the <code>#[salsa::db]</code> ma
|
|||
)
|
||||
}
|
||||
</code></pre>
|
||||
<p>This implementation for <code>create_jar</code> is geneated by the <code>#[salsa::jar]</code> macro, and simply walks over the representative type for each salsa item and ask <em>it</em> to create its ingredients</p>
|
||||
<p>This implementation for <code>create_jar</code> is geneated by the <code>#[salsa::jar]</code> macro, and simply walks over the representative type for each salsa item and asks <em>it</em> to create its ingredients</p>
|
||||
<pre><code class="language-rust ignore"> quote! {
|
||||
impl<'salsa_db> salsa::jar::Jar<'salsa_db> for #jar_struct {
|
||||
type DynDb = dyn #jar_trait + 'salsa_db;
|
||||
|
@ -375,8 +374,8 @@ The implementation of this method is defined by the <code>#[salsa::db]</code> ma
|
|||
}
|
||||
</code></pre>
|
||||
<p>The code to create the ingredients for any particular item is generated by their associated macros (e.g., <code>#[salsa::tracked]</code>, <code>#[salsa::input]</code>), but it always follows a particular structure.
|
||||
To create an ingredient, we first invoke <code>Routes::push</code> which creates the routes to that ingredient and assigns it an <code>IngredientIndex</code>.
|
||||
We can then invoke (e.g.) <code>FunctionIngredient::new</code> to create the structure.
|
||||
To create an ingredient, we first invoke <code>Routes::push</code>, which creates the routes to that ingredient and assigns it an <code>IngredientIndex</code>.
|
||||
We can then invoke a function such as <code>FunctionIngredient::new</code> to create the structure.
|
||||
The <em>routes</em> to an ingredient are defined as closures that, given the <code>DB::Jars</code>, can find the data for a particular ingredient.</p>
|
||||
|
||||
</main>
|
||||
|
|
220
print.html
220
print.html
|
@ -166,12 +166,12 @@ contribute, please jump on to our Zulip instance at
|
|||
<p>⚠️ <strong>IN-PROGRESS VERSION OF SALSA.</strong> ⚠️</p>
|
||||
<p>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 <code>salsa-2022</code> crate.</p>
|
||||
</blockquote>
|
||||
<p>This page contains a brief overview of the pieces of a salsa program.
|
||||
<p>This page contains a brief overview of the pieces of a Salsa program.
|
||||
For a more detailed look, check out the <a href="./tutorial.html">tutorial</a>, which walks through the creation of an entire project end-to-end.</p>
|
||||
<h2 id="goal-of-salsa"><a class="header" href="#goal-of-salsa">Goal of Salsa</a></h2>
|
||||
<p>The goal of salsa is to support efficient <strong>incremental recomputation</strong>.
|
||||
salsa is used in rust-analyzer, for example, to help it recompile your program quickly as you type.</p>
|
||||
<p>The basic idea of a salsa program is like this:</p>
|
||||
<p>The goal of Salsa is to support efficient <strong>incremental recomputation</strong>.
|
||||
Salsa is used in rust-analyzer, for example, to help it recompile your program quickly as you type.</p>
|
||||
<p>The basic idea of a Salsa program is like this:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -195,9 +195,9 @@ But this picture still conveys a few important concepts:</p>
|
|||
<li>The mutation of inputs always happens outside of <code>your_program</code>, as part of this master loop.</li>
|
||||
</ul>
|
||||
<h2 id="database"><a class="header" href="#database">Database</a></h2>
|
||||
<p>Each time you run your program, salsa remembers the values of each computation in a <strong>database</strong>.
|
||||
<p>Each time you run your program, Salsa remembers the values of each computation in a <strong>database</strong>.
|
||||
When the inputs change, it consults this database to look for values that can be reused.
|
||||
The database is also used to implement interning (making a canonical version of a value that can be copied around and cheaply compared for equality) and other convenient salsa features.</p>
|
||||
The database is also used to implement interning (making a canonical version of a value that can be copied around and cheaply compared for equality) and other convenient Salsa features.</p>
|
||||
<h2 id="inputs"><a class="header" href="#inputs">Inputs</a></h2>
|
||||
<p>Every Salsa program begins with an <strong>input</strong>.
|
||||
Inputs are special structs that define the starting point of your program.
|
||||
|
@ -225,8 +225,8 @@ Because the values of input fields are stored in the database, you also give an
|
|||
);
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<h3 id="salsa-structs-are-just-an-integer"><a class="header" href="#salsa-structs-are-just-an-integer">Salsa structs are just an integer</a></h3>
|
||||
<p>The <code>ProgramFile</code> struct generates by the <code>salsa::input</code> macro doesn't actually store any data. It's just a newtyped integer id:</p>
|
||||
<h3 id="salsa-structs-are-just-integers"><a class="header" href="#salsa-structs-are-just-integers">Salsa structs are just integers</a></h3>
|
||||
<p>The <code>ProgramFile</code> struct generated by the <code>salsa::input</code> macro doesn't actually store any data. It's just a newtyped integer id:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -288,10 +288,10 @@ fn parse_file(db: &dyn crate::Db, file: ProgramFile) -> Ast {
|
|||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>When you call a tracked function, salsa will track which inputs it accesses (in this example, <code>file.contents(db)</code>).
|
||||
<p>When you call a tracked function, Salsa will track which inputs it accesses (in this example, <code>file.contents(db)</code>).
|
||||
It will also memoize the return value (the <code>Ast</code>, in this case).
|
||||
If you call a tracked function twice, salsa checks if the inputs have changed; if not, it can return the memoized value.
|
||||
The algorithm salsa uses to decide when a tracked function needs to be re-executed is called the <a href="./reference/algorithm.html">red-green algorithm</a>, and it's where the name salsa comes from.</p>
|
||||
If you call a tracked function twice, Salsa checks if the inputs have changed; if not, it can return the memoized value.
|
||||
The algorithm Salsa uses to decide when a tracked function needs to be re-executed is called the <a href="./reference/algorithm.html">red-green algorithm</a>, and it's where the name Salsa comes from.</p>
|
||||
<p>Tracked functions have to follow a particular structure:</p>
|
||||
<ul>
|
||||
<li>They must take a <code>&</code>-reference to the database as their first argument.
|
||||
|
@ -299,7 +299,7 @@ The algorithm salsa uses to decide when a tracked function needs to be re-execut
|
|||
<li>Note that because this is an <code>&</code>-reference, it is not possible to create or modify inputs during a tracked function!</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>They must take a "salsa struct" as the second argument -- in our example, this is an input struct, but there are other kinds of salsa structs we'll describe shortly.</li>
|
||||
<li>They must take a "Salsa struct" as the second argument -- in our example, this is an input struct, but there are other kinds of Salsa structs we'll describe shortly.</li>
|
||||
<li>They <em>can</em> take additional arguments, but it's faster and better if they don't.</li>
|
||||
</ul>
|
||||
<p>Tracked functions can return any clone-able type. A clone is required since, when the value is cached, the result will be cloned out of the database. Tracked functions can also be annotated with <code>#[return_ref]</code> if you would prefer to return a reference into the database instead (if <code>parse_file</code> were so annotated, then callers would actually get back an <code>&Ast</code>, for example).</p>
|
||||
|
@ -356,7 +356,7 @@ struct Item {
|
|||
<p>Maybe our parser first creates an <code>Item</code> with the name <code>foo</code> and then later a second <code>Item</code> with the name <code>bar</code>.
|
||||
Then the user changes the input to reorder the functions.
|
||||
Although we are still creating the same number of items, we are now creating them in the reverse order, so the naive algorithm will match up the <em>old</em> <code>foo</code> struct with the new <code>bar</code> struct.
|
||||
This will look to salsa as though the <code>foo</code> function was renamed to <code>bar</code> and the <code>bar</code> function was renamed to <code>foo</code>.
|
||||
This will look to Salsa as though the <code>foo</code> function was renamed to <code>bar</code> and the <code>bar</code> function was renamed to <code>foo</code>.
|
||||
We'll still get the right result, but we might do more recomputation than we needed to do if we understood that they were just reordered.</p>
|
||||
<p>To address this, you can tag fields in a tracked struct as <code>#[id]</code>. These fields are then used to "match up" struct instances across executions:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
@ -370,7 +370,7 @@ struct Item {
|
|||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<h3 id="specified-the-result-of-tracked-functions-for-particular-structs"><a class="header" href="#specified-the-result-of-tracked-functions-for-particular-structs">Specified the result of tracked functions for particular structs</a></h3>
|
||||
<h3 id="specify-the-result-of-tracked-functions-for-particular-structs"><a class="header" href="#specify-the-result-of-tracked-functions-for-particular-structs">Specify the result of tracked functions for particular structs</a></h3>
|
||||
<p>Sometimes it is useful to define a tracked function but specify its value for some particular struct specially.
|
||||
For example, maybe the default way to compute the representation for a function is to read the AST, but you also have some built-in functions in your language and you want to hard-code their results.
|
||||
This can also be used to simulate a field that is initialized after the tracked struct is created.</p>
|
||||
|
@ -394,9 +394,9 @@ fn create_builtin_item(db: &dyn crate::Db) -> Item {
|
|||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>Specifying is only possible for tracked functions that take a single tracked struct as argument (besides the database).</p>
|
||||
<p>Specifying is only possible for tracked functions that take a single tracked struct as an argument (besides the database).</p>
|
||||
<h2 id="interned-structs"><a class="header" href="#interned-structs">Interned structs</a></h2>
|
||||
<p>The final kind of salsa struct are <strong>interned structs</strong>.
|
||||
<p>The final kind of Salsa struct are <strong>interned structs</strong>.
|
||||
Interned structs are useful for quick equality comparison.
|
||||
They are commonly used to represent strings or other primitive values.</p>
|
||||
<p>Most compilers, for example, will define a type to represent a user identifier:</p>
|
||||
|
@ -420,10 +420,10 @@ let w2 = Word::new(db, "bar".to_string());
|
|||
let w3 = Word::new(db, "foo".to_string());
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>When you create two interned structs with the same field values, you are guaranted to get back the same integer id. So here, we know that <code>assert_eq!(w1, w3)</code> is true and <code>assert_ne!(w1, w2)</code>.</p>
|
||||
<p>When you create two interned structs with the same field values, you are guaranteed to get back the same integer id. So here, we know that <code>assert_eq!(w1, w3)</code> is true and <code>assert_ne!(w1, w2)</code>.</p>
|
||||
<p>You can access the fields of an interned struct using a getter, like <code>word.text(db)</code>. These getters respect the <code>#[return_ref]</code> annotation. Like tracked structs, the fields of interned structs are immutable.</p>
|
||||
<h2 id="accumulators"><a class="header" href="#accumulators">Accumulators</a></h2>
|
||||
<p>The final salsa concept are <strong>accumulators</strong>. Accumulators are a way to report errors or other "side channel" information that is separate from the main return value of your function.</p>
|
||||
<p>The final Salsa concept are <strong>accumulators</strong>. Accumulators are a way to report errors or other "side channel" information that is separate from the main return value of your function.</p>
|
||||
<p>To create an accumulator, you declare a type as an <em>accumulator</em>:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
|
@ -478,8 +478,8 @@ print 11 * 2
|
|||
<p>If the program contains errors (e.g., a reference to an undefined function), it prints those out too.
|
||||
And, of course, it will be reactive, so small changes to the input don't require recompiling (or rexecuting, necessarily) the entire thing.</p>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="basic-structure"><a class="header" href="#basic-structure">Basic structure</a></h1>
|
||||
<p>Before we do anything with salsa, let's talk about the basic structure of the calc compiler.
|
||||
Part of salsa's design is that you are able to write programs that feel 'pretty close' to what a natural Rust program looks like.</p>
|
||||
<p>Before we do anything with Salsa, let's talk about the basic structure of the calc compiler.
|
||||
Part of Salsa's design is that you are able to write programs that feel 'pretty close' to what a natural Rust program looks like.</p>
|
||||
<h2 id="example-program"><a class="header" href="#example-program">Example program</a></h2>
|
||||
<p>This is our example calc program:</p>
|
||||
<pre><code>x = 5
|
||||
|
@ -508,7 +508,7 @@ print z
|
|||
Print(Expression),
|
||||
}
|
||||
|
||||
/// Defines `fn <name>(<args>) = <body>`
|
||||
/// Defines `fn <name>(<args>) = <body>`
|
||||
struct Function {
|
||||
name: FunctionId,
|
||||
args: Vec<VariableId>,
|
||||
|
@ -551,15 +551,15 @@ type VariableId = /* interned string */;
|
|||
We're going to write the checker in a "context-less" style,
|
||||
which is a bit less intuitive but allows for more incremental re-use.
|
||||
The idea is to compute, for a given expression, which variables it references.
|
||||
Then there is a function "check" which ensures that those variables are a subset of those that are already defined.</p>
|
||||
Then there is a function <code>check</code> which ensures that those variables are a subset of those that are already defined.</p>
|
||||
<h2 id="interpreter"><a class="header" href="#interpreter">Interpreter</a></h2>
|
||||
<p>The interpreter will execute the program and print the result. We don't bother with much incremental re-use here,
|
||||
though it's certainly possible.</p>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="jars-and-databases"><a class="header" href="#jars-and-databases">Jars and databases</a></h1>
|
||||
<p>Before we can define the interesting parts of our salsa program, we have to setup a bit of structure that defines the salsa <strong>database</strong>.
|
||||
The database is a struct that ultimately stores all of salsa's intermediate state, such as the memoized return values from <a href="tutorial/../overview.html#tracked-functions">tracked functions</a>.</p>
|
||||
<p>Before we can define the interesting parts of our Salsa program, we have to setup a bit of structure that defines the Salsa <strong>database</strong>.
|
||||
The database is a struct that ultimately stores all of Salsa's intermediate state, such as the memoized return values from <a href="tutorial/../overview.html#tracked-functions">tracked functions</a>.</p>
|
||||
<p>The database itself is defined in terms of intermediate structures, called <strong>jars</strong><sup class="footnote-reference"><a href="#jar">1</a></sup>, which themselves contain the data for each function.
|
||||
This setup allows salsa programs to be divided amongst many crates.
|
||||
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.</p>
|
||||
<div class="footnote-definition" id="jar"><sup class="footnote-definition-label">1</sup>
|
||||
|
@ -591,7 +591,7 @@ pub struct Jar(
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>Although it's not required, it's highly recommended to put the <code>jar</code> struct at the root of your crate, so that it can be referred to as <code>crate::Jar</code>.
|
||||
All of the other salsa annotations reference a jar struct, and they all default to the path <code>crate::Jar</code>.
|
||||
All of the other Salsa annotations reference a jar struct, and they all default to the path <code>crate::Jar</code>.
|
||||
If you put the jar somewhere else, you will have to override that default.</p>
|
||||
<h2 id="defining-the-database-trait"><a class="header" href="#defining-the-database-trait">Defining the database trait</a></h2>
|
||||
<p>The <code>#[salsa::jar]</code> annotation also includes a <code>db = Db</code> field.
|
||||
|
@ -609,7 +609,7 @@ This allows for separate compilation, where you have a database that contains th
|
|||
where <code>Jar</code> is the jar struct. If your jar depends on other jars, you can have multiple such supertraits (e.g., <code>salsa::DbWithJar<other_crate::Jar></code>).</p>
|
||||
<p>Typically the <code>Db</code> 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,
|
||||
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.</p>
|
||||
<h2 id="implementing-the-database-trait-for-the-jar"><a class="header" href="#implementing-the-database-trait-for-the-jar">Implementing the database trait for the jar</a></h2>
|
||||
<p>The <code>Db</code> trait must be implemented by the database struct.
|
||||
|
@ -625,11 +625,11 @@ and that's what we do here:</p>
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
|
||||
<p>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 do:</p>
|
||||
<p>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:</p>
|
||||
<ul>
|
||||
<li>In each of your crates:
|
||||
<ul>
|
||||
<li>Define a <code>#[salsa::jar(db = Db)]</code> struct, typically at <code>crate::Jar</code>, and list each of your various salsa-annotated things inside of it.</li>
|
||||
<li>Define a <code>#[salsa::jar(db = Db)]</code> struct, typically at <code>crate::Jar</code>, and list each of your various Salsa-annotated things inside of it.</li>
|
||||
<li>Define a <code>Db</code> trait, typically at <code>crate::Db</code>, that you will use in memoized functions and elsewhere to refer to the database struct.</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
@ -660,10 +660,9 @@ pub(crate) struct Database {
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>The <code>#[salsa::db(...)]</code> attribute takes a list of all the jars to include.
|
||||
The struct must have a field named <code>storage</code> whose types is <code>salsa::Storage<Self></code>, but it can also contain whatever other fields you want.
|
||||
The struct must have a field named <code>storage</code> whose type is <code>salsa::Storage<Self></code>, but it can also contain whatever other fields you want.
|
||||
The <code>storage</code> struct owns all the data for the jars listed in the <code>db</code> attribute.</p>
|
||||
<p>The <code>salsa::db</code> attribute autogenerates a bunch of impls for things like the <code>salsa::HasJar<crate::Jar></code> trait that we saw earlier.
|
||||
This means that</p>
|
||||
<p>The <code>salsa::db</code> attribute autogenerates a bunch of impls for things like the <code>salsa::HasJar<crate::Jar></code> trait that we saw earlier.</p>
|
||||
<h2 id="implementing-the-salsadatabase-trait"><a class="header" href="#implementing-the-salsadatabase-trait">Implementing the <code>salsa::Database</code> trait</a></h2>
|
||||
<p>In addition to the struct itself, we must add an impl of <code>salsa::Database</code>:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
@ -722,7 +721,7 @@ This means that</p>
|
|||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<h2 id="implementing-the-traits-for-each-jar"><a class="header" href="#implementing-the-traits-for-each-jar">Implementing the traits for each Jar</a></h2>
|
||||
<h2 id="implementing-the-traits-for-each-jar"><a class="header" href="#implementing-the-traits-for-each-jar">Implementing the traits for each jar</a></h2>
|
||||
<p>The <code>Database</code> struct also needs to implement the <a href="tutorial/./jar.html#database-trait-for-the-jar">database traits for each jar</a>.
|
||||
In our case, though, we already wrote that impl as a <a href="tutorial/./jar.html#implementing-the-database-trait-for-the-jar">blanket impl alongside the jar itself</a>,
|
||||
so no action is needed.
|
||||
|
@ -733,22 +732,22 @@ This is the recommended strategy unless your trait has custom members that depen
|
|||
In the <a href="tutorial/./structure.html">basic structure</a>, we defined some "pseudo-Rust" structures like <code>Statement</code> and <code>Expression</code>;
|
||||
now we are going to define them for real.</p>
|
||||
<h2 id="salsa-structs"><a class="header" href="#salsa-structs">"Salsa structs"</a></h2>
|
||||
<p>In addition to regular Rust types, we will make use of various <strong>salsa structs</strong>.
|
||||
A salsa struct is a struct that has been annotated with one of the salsa annotations:</p>
|
||||
<p>In addition to regular Rust types, we will make use of various <strong>Salsa structs</strong>.
|
||||
A Salsa struct is a struct that has been annotated with one of the Salsa annotations:</p>
|
||||
<ul>
|
||||
<li><a href="tutorial/ir.html#input-structs"><code>#[salsa::input]</code></a>, which designates the "base inputs" to your computation;</li>
|
||||
<li><a href="tutorial/ir.html#tracked-structs"><code>#[salsa::tracked]</code></a>, which designate intermediate values created during your computation;</li>
|
||||
<li><a href="tutorial/ir.html#interned-structs"><code>#[salsa::interned]</code></a>, which designate small values that are easy to compare for equality.</li>
|
||||
</ul>
|
||||
<p>All salsa structs store the actual values of their fields in the salsa database.
|
||||
<p>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.</p>
|
||||
<p>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.
|
||||
<p>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 <a href="tutorial/./jar.html">jar</a>.
|
||||
By default, this is <code>crate::Jar</code>, but you can specify a different jar with the <code>jar=</code> attribute (e.g., <code>#[salsa::input(jar = MyJar)]</code>).
|
||||
You must also list the struct in the jar definition itself, or you will get errors.</p>
|
||||
<h2 id="input-structs"><a class="header" href="#input-structs">Input structs</a></h2>
|
||||
<p>The first thing we will define is our <strong>input</strong>.
|
||||
Every salsa program has some basic inputs that drive the rest of the computation.
|
||||
Every Salsa program has some basic inputs that drive the rest of the computation.
|
||||
The rest of the program must be some deterministic function of those base inputs,
|
||||
such that when those inputs change, we can try to efficiently recompute the new result of that function.</p>
|
||||
<p>Inputs are defined as Rust structs with a <code>#[salsa::input]</code> annotation:</p>
|
||||
|
@ -764,8 +763,8 @@ pub struct SourceProgram {
|
|||
</span></code></pre></pre>
|
||||
<p>In our compiler, we have just one simple input, the <code>SourceProgram</code>, which has a <code>text</code> field (the string).</p>
|
||||
<h3 id="the-data-lives-in-the-database"><a class="header" href="#the-data-lives-in-the-database">The data lives in the database</a></h3>
|
||||
<p>Although they are declared like other Rust structs, salsa structs are implemented quite differently.
|
||||
The values of their fields are stored in the salsa database, and the struct itself just contains a numeric identifier.
|
||||
<p>Although they are declared like other Rust structs, Salsa structs are implemented quite differently.
|
||||
The values of their fields are stored in the Salsa database, and the struct itself just contains a numeric identifier.
|
||||
This means that the struct instances are copy (no matter what fields they contain).
|
||||
Creating instances of the struct and accessing fields is done by invoking methods like <code>new</code> as well as getters and setters.</p>
|
||||
<p>More concretely, the <code>#[salsa::input]</code> annotation will generate a struct for <code>SourceProgram</code> like this:</p>
|
||||
|
@ -808,7 +807,7 @@ pub struct Program {
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>Like with an input, the fields of a tracked struct are also stored in the database.
|
||||
Unlike an input, those fields are immutable (they cannot be "set"), and salsa compares them across revisions to know when they have changed.
|
||||
Unlike an input, those fields are immutable (they cannot be "set"), and Salsa compares them across revisions to know when they have changed.
|
||||
In this case, if parsing the input produced the same <code>Program</code> result (e.g., because the only change to the input was some trailing whitespace, perhaps),
|
||||
then subsequent parts of the computation won't need to re-execute.
|
||||
(We'll revisit the role of tracked structs in reuse more in future parts of the IR.)</p>
|
||||
|
@ -823,9 +822,7 @@ then subsequent parts of the computation won't need to re-execute.
|
|||
</ul>
|
||||
<h2 id="representing-functions"><a class="header" href="#representing-functions">Representing functions</a></h2>
|
||||
<p>We will also use a tracked struct to represent each function:
|
||||
Next we will define a <strong>tracked struct</strong>.
|
||||
Whereas inputs represent the <em>start</em> of a computation, tracked structs represent intermediate values created during your computation.</p>
|
||||
<p>The <code>Function</code> struct is going to be created by the parser to represent each of the functions defined by the user:</p>
|
||||
The <code>Function</code> struct is going to be created by the parser to represent each of the functions defined by the user:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -844,9 +841,7 @@ pub struct Function {
|
|||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>Like with an input, the fields of a tracked struct are also stored in the database.
|
||||
Unlike an input, those fields are immutable (they cannot be "set"), and salsa compares them across revisions to know when they have changed.
|
||||
If we had created some <code>Function</code> instance <code>f</code>, for example, we might find that <code>the f.body</code> field changes
|
||||
<p>If we had created some <code>Function</code> instance <code>f</code>, for example, we might find that <code>the f.body</code> field changes
|
||||
because the user changed the definition of <code>f</code>.
|
||||
This would mean that we have to re-execute those parts of the code that depended on <code>f.body</code>
|
||||
(but not those parts of the code that depended on the body of <em>other</em> functions).</p>
|
||||
|
@ -862,7 +857,7 @@ This indicates that, across two revisions R1 and R2, if two functions are create
|
|||
Adding <code>#[id]</code> attributes is an optimization and never affects correctness.
|
||||
For more details, see the <a href="tutorial/../reference/algorithm.html">algorithm</a> page of the reference.</p>
|
||||
<h2 id="interned-structs-1"><a class="header" href="#interned-structs-1">Interned structs</a></h2>
|
||||
<p>The final kind of salsa struct are <em>interned structs</em>.
|
||||
<p>The final kind of Salsa struct are <em>interned structs</em>.
|
||||
As with input and tracked structs, the data for an interned struct is stored in the database, and you just pass around a single integer.
|
||||
Unlike those structs, if you intern the same data twice, you get back the <strong>same integer</strong>.</p>
|
||||
<p>A classic use of interning is for small strings like function names and variables.
|
||||
|
@ -897,13 +892,13 @@ assert_eq!(f1, f2);
|
|||
</span></code></pre></pre>
|
||||
<h3 id="interned-ids-are-guaranteed-to-be-consistent-within-a-revision-but-not-across-revisions-but-you-dont-have-to-care"><a class="header" href="#interned-ids-are-guaranteed-to-be-consistent-within-a-revision-but-not-across-revisions-but-you-dont-have-to-care">Interned ids are guaranteed to be consistent within a revision, but not across revisions (but you don't have to care)</a></h3>
|
||||
<p>Interned ids are guaranteed not to change within a single revision, so you can intern things from all over your program and get back consistent results.
|
||||
When you change the inputs, however, salsa may opt to clear some of the interned values and choose different integers.
|
||||
When you change the inputs, however, Salsa may opt to clear some of the interned values and choose different integers.
|
||||
However, if this happens, it will also be sure to re-execute every function that interned that value, so all of them still see a consistent value,
|
||||
just a different one than they saw in a previous revision.</p>
|
||||
<p>In other words, within a salsa computation, you can assume that interning produces a single consistent integer, and you don't have to think about it.
|
||||
If however you export interned identifiers outside the computation, and then change the inputs, they may not longer be valid or may refer to different values.</p>
|
||||
<p>In other words, within a Salsa computation, you can assume that interning produces a single consistent integer, and you don't have to think about it.
|
||||
If, however, you export interned identifiers outside the computation, and then change the inputs, they may no longer be valid or may refer to different values.</p>
|
||||
<h3 id="expressions-and-statements"><a class="header" href="#expressions-and-statements">Expressions and statements</a></h3>
|
||||
<p>We'll won't use any special "salsa structs" for expressions and statements:</p>
|
||||
<p>We won't use any special "Salsa structs" for expressions and statements:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -949,7 +944,7 @@ pub enum Op {
|
|||
<p>Since statements and expressions are not tracked, this implies that we are only attempting to get incremental re-use at the granularity of functions --
|
||||
whenever anything in a function body changes, we consider the entire function body dirty and re-execute anything that depended on it.
|
||||
It usually makes sense to draw some kind of "reasonably coarse" boundary like this.</p>
|
||||
<p>One downside of the way we have set things up: we inlined the position into each of the structs.</p>
|
||||
<p>One downside of the way we have set things up: we inlined the position into each of the structs [what exactly does this mean?].</p>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="defining-the-parser-memoized-functions-and-inputs"><a class="header" href="#defining-the-parser-memoized-functions-and-inputs">Defining the parser: memoized functions and inputs</a></h1>
|
||||
<p>The next step in the <code>calc</code> compiler is to define the parser.
|
||||
The role of the parser will be to take the <code>ProgramSource</code> input,
|
||||
|
@ -958,7 +953,7 @@ and create the <code>Statement</code>, <code>Function</code>, and <code>Expressi
|
|||
<p>To minimize dependencies, we are going to write a <a href="https://en.wikipedia.org/wiki/Recursive_descent_parser">recursive descent parser</a>.
|
||||
Another option would be to use a <a href="https://rustrepo.com/catalog/rust-parsing_newest_1">Rust parsing framework</a>.
|
||||
We won't cover the parsing itself in this tutorial -- you can read the code if you want to see how it works.
|
||||
We're going to focus only on the salsa-related aspects.</p>
|
||||
We're going to focus only on the Salsa-related aspects.</p>
|
||||
<h2 id="the-parse_statements-function"><a class="header" href="#the-parse_statements-function">The <code>parse_statements</code> function</a></h2>
|
||||
<p>The starting point for the parser is the <code>parse_statements</code> function:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
@ -1004,16 +999,16 @@ pub fn parse_statements(db: &dyn crate::Db, source: SourceProgram) -> Pro
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>This function is annotated as <code>#[salsa::tracked]</code>.
|
||||
That means that, when it is called, salsa will track what inputs it reads as well as what value it returns.
|
||||
That means that, when it is called, Salsa will track what inputs it reads as well as what value it returns.
|
||||
The return value is <em>memoized</em>,
|
||||
which means that if you call this function again without changing the inputs,
|
||||
salsa will just clone the result rather than re-execute it.</p>
|
||||
Salsa will just clone the result rather than re-execute it.</p>
|
||||
<h3 id="tracked-functions-are-the-unit-of-reuse"><a class="header" href="#tracked-functions-are-the-unit-of-reuse">Tracked functions are the unit of reuse</a></h3>
|
||||
<p>Tracked functions are the core part of how salsa enables incremental reuse.
|
||||
<p>Tracked functions are the core part of how Salsa enables incremental reuse.
|
||||
The goal of the framework is to avoid re-executing tracked functions and instead to clone their result.
|
||||
Salsa uses the <a href="tutorial/../reference/algorithm.html">red-green algorithm</a> to decide when to re-execute a function.
|
||||
The short version is that a tracked function is re-executed if either (a) it directly reads an input, and that input has changed
|
||||
or (b) it directly invokes another tracked function, and that function's return value has changed.
|
||||
The short version is that a tracked function is re-executed if either (a) it directly reads an input, and that input has changed,
|
||||
or (b) it directly invokes another tracked function and that function's return value has changed.
|
||||
In the case of <code>parse_statements</code>, it directly reads <code>ProgramSource::text</code>, so if the text changes, then the parser will re-execute.</p>
|
||||
<p>By choosing which functions to mark as <code>#[tracked]</code>, you control how much reuse you get.
|
||||
In our case, we're opting to mark the outermost parsing function as tracked, but not the inner ones.
|
||||
|
@ -1024,19 +1019,19 @@ and because (b) since strings are just a big blob-o-bytes without any structure,
|
|||
Some systems do choose to do more granular reparsing, often by doing a "first pass" over the string to give it a bit of structure,
|
||||
e.g. to identify the functions,
|
||||
but deferring the parsing of the body of each function until later.
|
||||
Setting up a scheme like this is relatively easy in salsa, and uses the same principles that we will use later to avoid re-executing the type checker.</p>
|
||||
Setting up a scheme like this is relatively easy in Salsa and uses the same principles that we will use later to avoid re-executing the type checker.</p>
|
||||
<h3 id="parameters-to-a-tracked-function"><a class="header" href="#parameters-to-a-tracked-function">Parameters to a tracked function</a></h3>
|
||||
<p>The <strong>first</strong> parameter to a tracked function is <strong>always</strong> the database, <code>db: &dyn crate::Db</code>.
|
||||
It must be a <code>dyn</code> value of whatever database is associated with the jar.</p>
|
||||
<p>The <strong>second</strong> parameter to a tracked function is <strong>always</strong> some kind of salsa struct.
|
||||
<p>The <strong>second</strong> parameter to a tracked function is <strong>always</strong> some kind of Salsa struct.
|
||||
The first parameter to a memoized function is always the database,
|
||||
which should be a <code>dyn Trait</code> value for the database trait associated with the jar
|
||||
(the default jar is <code>crate::Jar</code>).</p>
|
||||
<p>Tracked functions may take other arguments as well, though our examples here do not.
|
||||
Functions that take additional arguments are less efficient and flexible.
|
||||
It's generally better to structure tracked functions as functions of a single salsa struct if possible.</p>
|
||||
It's generally better to structure tracked functions as functions of a single Salsa struct if possible.</p>
|
||||
<h3 id="the-return_ref-annotation"><a class="header" href="#the-return_ref-annotation">The <code>return_ref</code> annotation</a></h3>
|
||||
<p>You may have noticed that <code>parse_statements</code> is tagged with <code>#[salsa::tracked(return_ref)]</code>.
|
||||
<p>You may have noticed that <code>parse_statements</code> is tagged with <code>#[salsa::tracked(return_ref)]</code>.
|
||||
Ordinarily, when you call a tracked function, the result you get back is cloned out of the database.
|
||||
The <code>return_ref</code> attribute means that a reference into the database is returned instead.
|
||||
So, when called, <code>parse_statements</code> will return an <code>&Vec<Statement></code> rather than cloning the <code>Vec</code>.
|
||||
|
@ -1045,9 +1040,10 @@ This is useful as a performance optimization.
|
|||
where it was placed on struct fields, with roughly the same meaning.)</p>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="defining-the-parser-reporting-errors"><a class="header" href="#defining-the-parser-reporting-errors">Defining the parser: reporting errors</a></h1>
|
||||
<p>The last interesting case in the parser is how to handle a parse error.
|
||||
Because salsa functions are memoized and may not execute, they should not have side-effects,
|
||||
Because Salsa functions are memoized and may not execute, they should not have side-effects,
|
||||
so we don't just want to call <code>eprintln!</code>.
|
||||
If we did so, the error would only be reported the first time the function was called.</p>
|
||||
If we did so, the error would only be reported the first time the function was called, but not
|
||||
on subsequent calls in the situation where the simply returns its memoized value.</p>
|
||||
<p>Salsa defines a mechanism for managing this called an <strong>accumulator</strong>.
|
||||
In our case, we define an accumulator struct called <code>Diagnostics</code> in the <code>ir</code> module:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
@ -1067,7 +1063,7 @@ pub struct Diagnostic {
|
|||
<p>Accumulator structs are always newtype structs with a single field, in this case of type <code>Diagnostic</code>.
|
||||
Memoized functions can <em>push</em> <code>Diagnostic</code> values onto the accumulator.
|
||||
Later, you can invoke a method to find all the values that were pushed by the memoized functions
|
||||
or any function that it called
|
||||
or any functions that they called
|
||||
(e.g., we could get the set of <code>Diagnostic</code> values produced by the <code>parse_statements</code> function).</p>
|
||||
<p>The <code>Parser::report_error</code> method contains an example of pushing a diagnostic:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
@ -1109,7 +1105,7 @@ To do so, we will create a database, set the input source text, run the parser,
|
|||
Before we can do that, though, we have to address one question: how do we inspect the value of an interned type like <code>Expression</code>?</p>
|
||||
<h2 id="the-debugwithdb-trait"><a class="header" href="#the-debugwithdb-trait">The <code>DebugWithDb</code> trait</a></h2>
|
||||
<p>Because an interned type like <code>Expression</code> just stores an integer, the traditional <code>Debug</code> trait is not very useful.
|
||||
To properly print a <code>Expression</code>, you need to access the salsa database to find out what its value is.
|
||||
To properly print a <code>Expression</code>, you need to access the Salsa database to find out what its value is.
|
||||
To solve this, <code>salsa</code> provides a <code>DebugWithDb</code> trait that acts like the regular <code>Debug</code>, but takes a database as argument.
|
||||
For types that implement this trait, you can invoke the <code>debug</code> method.
|
||||
This returns a temporary that implements the ordinary <code>Debug</code> trait, allowing you to write something like</p>
|
||||
|
@ -1120,7 +1116,7 @@ This returns a temporary that implements the ordinary <code>Debug</code> trait,
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>and get back the output you expect.</p>
|
||||
<p>The <code>DebugWithDb</code> trait is automatically derived for all <code>#[input]</code>, <code>#[interned]</code> and <code>#[tracked]</code> structs.</p>
|
||||
<p>The <code>DebugWithDb</code> trait is automatically derived for all <code>#[input]</code>, <code>#[interned]</code>, and <code>#[tracked]</code> structs.</p>
|
||||
<h2 id="forwarding-to-the-ordinary-debug-trait"><a class="header" href="#forwarding-to-the-ordinary-debug-trait">Forwarding to the ordinary <code>Debug</code> trait</a></h2>
|
||||
<p>For consistency, it is sometimes useful to have a <code>DebugWithDb</code> implementation even for types, like <code>Op</code>, that are just ordinary enums. You can do that like so:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
@ -1225,10 +1221,10 @@ fn parse_print() {
|
|||
<div style="break-before: page; page-break-before: always;"></div><h1 id="defining-the-interpreter"><a class="header" href="#defining-the-interpreter">Defining the interpreter</a></h1>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="reference"><a class="header" href="#reference">Reference</a></h1>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="the-red-green-algorithm"><a class="header" href="#the-red-green-algorithm">The "red-green" algorithm</a></h1>
|
||||
<p>This page explains the basic salsa incremental algorithm.
|
||||
The algorithm is called the "red-green" algorithm, which is where the name salsa comes from.</p>
|
||||
<p>This page explains the basic Salsa incremental algorithm.
|
||||
The algorithm is called the "red-green" algorithm, which is where the name Salsa comes from.</p>
|
||||
<h3 id="database-revisions-1"><a class="header" href="#database-revisions-1">Database revisions</a></h3>
|
||||
<p>The salsa database always tracks a single <strong>revision</strong>. Each time you set an input, the revision is incremented. So we start in revision <code>R1</code>, but when a <code>set</code> method is called, we will go to <code>R2</code>, then <code>R3</code>, and so on. For each input, we also track the revision in which it was last changed.</p>
|
||||
<p>The Salsa database always tracks a single <strong>revision</strong>. Each time you set an input, the revision is incremented. So we start in revision <code>R1</code>, but when a <code>set</code> method is called, we will go to <code>R2</code>, then <code>R3</code>, and so on. For each input, we also track the revision in which it was last changed.</p>
|
||||
<h3 id="basic-rule-when-inputs-change-re-execute"><a class="header" href="#basic-rule-when-inputs-change-re-execute">Basic rule: when inputs change, re-execute!</a></h3>
|
||||
<p>When you invoke a tracked function, in addition to storing the value that was returned, we also track what <em>other</em> tracked functions it depends on, and the revisions when their value last changed. When you invoke the function again, if the database is in a new revision, then we check whether any of the inputs to this function have changed in that new revision. If not, we can just return our cached value. But if the inputs <em>have</em> changed (or may have changed), we will re-execute the function to find the most up-to-date answer.</p>
|
||||
<p>Here is a simple example, where the <code>parse_module</code> function invokes the <code>module_text</code> function:</p>
|
||||
|
@ -1241,7 +1237,7 @@ fn parse_module(db: &dyn Db, module: Module) -> Ast {
|
|||
Ast::parse_text(module_text)
|
||||
}
|
||||
|
||||
#[salsa::tracked(ref)]
|
||||
#[salsa::tracked(return_ref)]
|
||||
fn module_text(db: &dyn Db, module: Module) -> String {
|
||||
panic!("text for module `{module:?}` not set")
|
||||
}
|
||||
|
@ -1286,7 +1282,9 @@ fn type_check(db: &dyn Db, module: Module) {
|
|||
<li>We will then compare the resulting AST. If it's the same as last time, we can <em>backdate</em> the result, meaning that we say that, even though the inputs changed, the output didn't.</li>
|
||||
</ul>
|
||||
<h2 id="durability-an-optimization"><a class="header" href="#durability-an-optimization">Durability: an optimization</a></h2>
|
||||
<p>As an optimization, salsa includes the concept of <strong>durability</strong>. When you set the value of a tracked function, you can also set it with a given <em>durability</em>:</p>
|
||||
<p>As an optimization, Salsa includes the concept of <strong>durability</strong>, which is the notion of how often some piece of tracked data changes. </p>
|
||||
<p>For example, when compiling a Rust program, you might mark the inputs from crates.io as <em>high durability</em> inputs, since they are unlikely to change. The current workspace could be marked as <em>low durability</em>, since changes to it are happening all the time.</p>
|
||||
<p>When you set the value of a tracked function, you can also set it with a given <em>durability</em>:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -1299,7 +1297,6 @@ fn type_check(db: &dyn Db, module: Module) {
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>For each durability, we track the revision in which <em>some input</em> with that durability changed. If a tracked function depends (transitively) only on high durability inputs, and you change a low durability input, then we can very easily determine that the tracked function result is still valid, avoiding the need to traverse the input edges one by one.</p>
|
||||
<p>An example: if compiling a Rust program, you might mark the inputs from crates.io as <em>high durability</em> inputs, since they are unlikely to change. The current workspace could be marked as <em>low durability</em>.</p>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="common-patterns"><a class="header" href="#common-patterns">Common patterns</a></h1>
|
||||
<p>This section documents patterns for using Salsa.</p>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="selection"><a class="header" href="#selection">Selection</a></h1>
|
||||
|
@ -1509,8 +1506,8 @@ by invoking <code>db.unwind_if_cancelled()</code>.</p>
|
|||
<p>If a cycle occurs and <em>some</em> of the participant queries have <code>#[salsa::recover]</code> annotations and others do not, then the query will be treated as irrecoverable and will simply panic. You can use the <code>Cycle::unexpected_participants</code> method to figure out why recovery did not succeed and add the appropriate <code>#[salsa::recover]</code> annotations.</p>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="how-salsa-works"><a class="header" href="#how-salsa-works">How Salsa works</a></h1>
|
||||
<h2 id="video-available"><a class="header" href="#video-available">Video available</a></h2>
|
||||
<p>To get the most complete introduction to Salsa's inner works, check
|
||||
out <a href="https://youtu.be/_muY4HjSqVw">the "How Salsa Works" video</a>. If
|
||||
<p>To get the most complete introduction to Salsa's inner workings, check
|
||||
out <a href="https://youtu.be/_muY4HjSqVw">the "How Salsa Works" video</a>. If
|
||||
you'd like a deeper dive, <a href="https://www.youtube.com/watch?v=i_IhACacPRY">the "Salsa in more depth"
|
||||
video</a> digs into the
|
||||
details of the incremental algorithm.</p>
|
||||
|
@ -1519,20 +1516,20 @@ details of the incremental algorithm.</p>
|
|||
</blockquote>
|
||||
<h2 id="key-idea"><a class="header" href="#key-idea">Key idea</a></h2>
|
||||
<p>The key idea of <code>salsa</code> is that you define your program as a set of
|
||||
<strong>queries</strong>. Every query is used like function <code>K -> V</code> that maps from
|
||||
<strong>queries</strong>. Every query is used like a function <code>K -> V</code> that maps from
|
||||
some key of type <code>K</code> to a value of type <code>V</code>. Queries come in two basic
|
||||
varieties:</p>
|
||||
<ul>
|
||||
<li><strong>Inputs</strong>: the base inputs to your system. You can change these
|
||||
whenever you like.</li>
|
||||
<li><strong>Functions</strong>: pure functions (no side effects) that transform your
|
||||
inputs into other values. The results of queries is memoized to
|
||||
inputs into other values. The results of queries are memoized to
|
||||
avoid recomputing them a lot. When you make changes to the inputs,
|
||||
we'll figure out (fairly intelligently) when we can re-use these
|
||||
memoized values and when we have to recompute them.</li>
|
||||
</ul>
|
||||
<h2 id="how-to-use-salsa-in-three-easy-steps"><a class="header" href="#how-to-use-salsa-in-three-easy-steps">How to use Salsa in three easy steps</a></h2>
|
||||
<p>Using salsa is as easy as 1, 2, 3...</p>
|
||||
<p>Using Salsa is as easy as 1, 2, 3...</p>
|
||||
<ol>
|
||||
<li>Define one or more <strong>query groups</strong> that contain the inputs
|
||||
and queries you will need. We'll start with one such group, but
|
||||
|
@ -1549,13 +1546,13 @@ example</a>, which has a number of comments explaining how
|
|||
things work.</p>
|
||||
<h2 id="digging-into-the-plumbing"><a class="header" href="#digging-into-the-plumbing">Digging into the plumbing</a></h2>
|
||||
<p>Check out the <a href="plumbing.html">plumbing</a> chapter to see a deeper explanation of the
|
||||
code that salsa generates and how it connects to the salsa library.</p>
|
||||
code that Salsa generates and how it connects to the Salsa library.</p>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="videos"><a class="header" href="#videos">Videos</a></h1>
|
||||
<p>There are currently two videos about Salsa available, but they describe an older version of Salsa and so they are rather outdated:</p>
|
||||
<ul>
|
||||
<li><a href="https://youtu.be/_muY4HjSqVw">How Salsa Works</a>, which gives a
|
||||
high-level introduction to the key concepts involved and shows how
|
||||
to use salsa;</li>
|
||||
to use Salsa;</li>
|
||||
<li><a href="https://www.youtube.com/watch?v=i_IhACacPRY">Salsa In More Depth</a>,
|
||||
which digs into the incremental algorithm and explains -- at a
|
||||
high-level -- how Salsa is implemented.</li>
|
||||
|
@ -1590,10 +1587,10 @@ We refer to this as the "plumbing".</p>
|
|||
<p>⚠️ <strong>IN-PROGRESS VERSION OF SALSA.</strong> ⚠️</p>
|
||||
<p>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 <code>salsa-2022</code> crate.</p>
|
||||
</blockquote>
|
||||
<p>This page covers how data is organized in salsa and how links between salsa items (e.g., dependency tracking) works.</p>
|
||||
<p>This page covers how data is organized in Salsa and how links between Salsa items (e.g., dependency tracking) work.</p>
|
||||
<h2 id="salsa-items-and-ingredients"><a class="header" href="#salsa-items-and-ingredients">Salsa items and ingredients</a></h2>
|
||||
<p>A <strong>salsa item</strong> is some item annotated with a salsa annotation that can be included in a jar.
|
||||
For example, a tracked function is a salsa item:</p>
|
||||
<p>A <strong>Salsa item</strong> is some item annotated with a Salsa annotation that can be included in a jar.
|
||||
For example, a tracked function is a Salsa item:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -1601,7 +1598,7 @@ For example, a tracked function is a salsa item:</p>
|
|||
fn foo(db: &dyn Db, input: MyInput) { }
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>...and so is a salsa input...</p>
|
||||
<p>...and so is a Salsa input...</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -1617,31 +1614,26 @@ struct MyInput { }
|
|||
struct MyStruct { }
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>Each salsa item needs certain bits of data at runtime to operate.
|
||||
<p>Each Salsa item needs certain bits of data at runtime to operate.
|
||||
These bits of data are called <strong>ingredients</strong>.
|
||||
Most salsa items generate a single ingredient, but sometimes they make more than one.
|
||||
Most Salsa items generate a single ingredient, but sometimes they make more than one.
|
||||
For example, a tracked function generates a <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/function.rs#L42"><code>FunctionIngredient</code></a>.
|
||||
A tracked struct however generates several ingredients, one for the struct itself (a <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/tracked_struct.rs#L18"><code>TrackedStructIngredient</code></a>,
|
||||
A tracked struct, however, generates several ingredients, one for the struct itself (a <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/tracked_struct.rs#L18"><code>TrackedStructIngredient</code></a>,
|
||||
and one <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/function.rs#L42"><code>FunctionIngredient</code></a> for each value field.</p>
|
||||
<h3 id="ingredients-define-the-core-logic-of-salsa"><a class="header" href="#ingredients-define-the-core-logic-of-salsa">Ingredients define the core logic of salsa</a></h3>
|
||||
<p>Most of the interesting salsa code lives in these ingredients.
|
||||
<h3 id="ingredients-define-the-core-logic-of-salsa"><a class="header" href="#ingredients-define-the-core-logic-of-salsa">Ingredients define the core logic of Salsa</a></h3>
|
||||
<p>Most of the interesting Salsa code lives in these ingredients.
|
||||
For example, when you create a new tracked struct, the method <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/tracked_struct.rs#L76"><code>TrackedStruct::new_struct</code></a> 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 <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/function/fetch.rs#L15"><code>TrackedFunction::fetch</code></a>,
|
||||
which decides whether there is a valid memoized value to return,
|
||||
or whether the function must be executed.</p>
|
||||
<h3 id="ingredient-interfaces-are-not-stable-or-subject-to-semver"><a class="header" href="#ingredient-interfaces-are-not-stable-or-subject-to-semver">Ingredient interfaces are not stable or subject to semver</a></h3>
|
||||
<p>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.</p>
|
||||
<h3 id="the-ingredient-trait"><a class="header" href="#the-ingredient-trait">The <code>Ingredient</code> trait</a></h3>
|
||||
<p>Each ingredient implements the <a href="https://github.com/salsa-rs/salsa/blob/becaade31e6ebc58cd0505fc1ee4b8df1f39f7de/components/salsa-2022/src/ingredient.rs#L15"><code>Ingredient<DB></code></a> trait, which defines generic operations supported by any kind of ingredient.
|
||||
For example, the method <code>maybe_changed_after</code> can be used to check whether some particular piece of data stored in the ingredient may have changed since a given revision:</p>
|
||||
<p>We'll see below that each database <code>DB</code> is able to take an <code>IngredientIndex</code> and use that to get a <code>&dyn Ingredient<DB></code> 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.</p>
|
||||
<p>We'll see below that each database <code>DB</code> is able to take an <code>IngredientIndex</code> and use that to get an <code>&dyn Ingredient<DB></code> 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.</p>
|
||||
<h3 id="jars-are-a-collection-of-ingredients"><a class="header" href="#jars-are-a-collection-of-ingredients">Jars are a collection of ingredients</a></h3>
|
||||
<p>When you declare a salsa jar, you list out each of the salsa items that are included in that jar:</p>
|
||||
<p>When you declare a Salsa jar, you list out each of the Salsa items that are included in that jar:</p>
|
||||
<pre><code class="language-rust ignore">#[salsa::jar]
|
||||
struct Jar(
|
||||
foo,
|
||||
|
@ -1660,20 +1652,19 @@ struct Jar(
|
|||
)
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>The <code>IngredientsFor</code> trait is used to define the ingredients needed by some salsa item, such as the tracked function <code>foo</code>
|
||||
or the tracked struct <code>MyInput</code>.
|
||||
Each salsa item defines a type <code>I</code>, so that <code><I as IngredientsFor>::Ingredient</code> gives the ingredients needed by <code>I</code>.</p>
|
||||
<h3 id="database-is-a-tuple-of-jars"><a class="header" href="#database-is-a-tuple-of-jars">Database is a tuple of jars</a></h3>
|
||||
<p>Salsa's database storage ultimately boils down to a tuple of jar structs,
|
||||
<p>The <code>IngredientsFor</code> trait is used to define the ingredients needed by some Salsa item, such as the tracked function <code>foo</code> or the tracked struct <code>MyInput</code>.
|
||||
Each Salsa item defines a type <code>I</code> so that <code><I as IngredientsFor>::Ingredient</code> gives the ingredients needed by <code>I</code>.</p>
|
||||
<h3 id="a-database-is-a-tuple-of-jars"><a class="header" href="#a-database-is-a-tuple-of-jars">A database is a tuple of jars</a></h3>
|
||||
<p>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.
|
||||
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.</p>
|
||||
<p>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.</p>
|
||||
<h3 id="the-hasjars-trait-and-the-jars-type"><a class="header" href="#the-hasjars-trait-and-the-jars-type">The HasJars trait and the Jars type</a></h3>
|
||||
<p>Each salsa database implements the <code>HasJars</code> trait,
|
||||
<h3 id="the-hasjars-trait-and-the-jars-type"><a class="header" href="#the-hasjars-trait-and-the-jars-type">The <code>HasJars</code> trait and the <code>Jars</code> type</a></h3>
|
||||
<p>Each Salsa database implements the <code>HasJars</code> trait,
|
||||
generated by the <code>salsa::db</code> procedural macro.
|
||||
The <code>HarJars</code> trait, among other things, defines a <code>Jars</code> associated type that maps to a tuple of the jars in the trait.</p>
|
||||
<p>For example, given a database like this...</p>
|
||||
|
@ -1720,12 +1711,17 @@ We can then do things like ask, "did this input change since revision R?&qu
|
|||
<li>using the ingredient index to find the route and get a <code>&dyn Ingredient<DB></code></li>
|
||||
<li>and then invoking the <code>maybe_changed_since</code> method on that trait object.</li>
|
||||
</ul>
|
||||
<h3 id="hasjarsdyn"><a class="header" href="#hasjarsdyn">HasJarsDyn</a></h3>
|
||||
<h3 id="hasjarsdyn"><a class="header" href="#hasjarsdyn"><code>HasJarsDyn</code></a></h3>
|
||||
<p>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.
|
||||
The user's code always interacts with a <code>dyn crate::Db</code> value, where <code>crate::Db</code> is the trait defined by the jar; the <code>crate::Db</code> trait extends <code>salsa::HasJar</code> which in turn extends <code>salsa::Database</code>.
|
||||
Ideally, we would have <code>salsa::Database</code> extend <code>salsa::HasJars</code>, which is the main trait that gives access to the jars data.
|
||||
But we don't want to do that because <code>HasJars</code> defines an associated type <code>Jars</code>, and that would mean that every reference to <code>dyn crate::Db</code> would have to specify the jars type using something like <code>dyn crate::Db<Jars = J></code>.
|
||||
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, <code>salsa::Database</code> in fact extends <em>another</em> trait, <code>HasJarsDyn</code>, that doesn't reveal the <code>Jars</code> or ingredient types directly, but just has various method that can be performed on an ingredient, given its <code>IngredientIndex</code>.
|
||||
Traits like <code>Ingredient<DB></code> require knowing the full <code>DB</code> type.
|
||||
If we had one function ingredient directly invoke a method on <code>Ingredient<DB></code>, that would imply that it has to be fully generic and only instantiated at the final crate, when the full database type is available.</p>
|
||||
<p>We solve this via the <code>HasJarsDyn</code> trait. The <code>HasJarsDyn</code> trait exports method that combine the "find ingredient, invoking method" steps into one method:</p>
|
||||
<p>We solve this via the <code>HasJarsDyn</code> trait. The <code>HasJarsDyn</code> trait exports a method that combines the "find ingredient, invoking method" steps into one method:</p>
|
||||
<p>[Perhaps this code snippet should only preview the HasJarsDyn method that is being referred to]</p>
|
||||
<pre><code class="language-rust ignore">/// Dyn friendly subset of HasJars
|
||||
pub trait HasJarsDyn {
|
||||
fn runtime(&self) -> &Runtime;
|
||||
|
@ -1798,7 +1794,7 @@ The implementation of this method is defined by the <code>#[salsa::db]</code> ma
|
|||
)
|
||||
}
|
||||
</code></pre>
|
||||
<p>This implementation for <code>create_jar</code> is geneated by the <code>#[salsa::jar]</code> macro, and simply walks over the representative type for each salsa item and ask <em>it</em> to create its ingredients</p>
|
||||
<p>This implementation for <code>create_jar</code> is geneated by the <code>#[salsa::jar]</code> macro, and simply walks over the representative type for each salsa item and asks <em>it</em> to create its ingredients</p>
|
||||
<pre><code class="language-rust ignore"> quote! {
|
||||
impl<'salsa_db> salsa::jar::Jar<'salsa_db> for #jar_struct {
|
||||
type DynDb = dyn #jar_trait + 'salsa_db;
|
||||
|
@ -1816,8 +1812,8 @@ The implementation of this method is defined by the <code>#[salsa::db]</code> ma
|
|||
}
|
||||
</code></pre>
|
||||
<p>The code to create the ingredients for any particular item is generated by their associated macros (e.g., <code>#[salsa::tracked]</code>, <code>#[salsa::input]</code>), but it always follows a particular structure.
|
||||
To create an ingredient, we first invoke <code>Routes::push</code> which creates the routes to that ingredient and assigns it an <code>IngredientIndex</code>.
|
||||
We can then invoke (e.g.) <code>FunctionIngredient::new</code> to create the structure.
|
||||
To create an ingredient, we first invoke <code>Routes::push</code>, which creates the routes to that ingredient and assigns it an <code>IngredientIndex</code>.
|
||||
We can then invoke a function such as <code>FunctionIngredient::new</code> to create the structure.
|
||||
The <em>routes</em> to an ingredient are defined as closures that, given the <code>DB::Jars</code>, can find the data for a particular ingredient.</p>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="database-and-runtime"><a class="header" href="#database-and-runtime">Database and runtime</a></h1>
|
||||
<p>A salsa database struct is declared by the user with the <code>#[salsa::db]</code> annotation.
|
||||
|
|
|
@ -145,10 +145,10 @@
|
|||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="the-red-green-algorithm"><a class="header" href="#the-red-green-algorithm">The "red-green" algorithm</a></h1>
|
||||
<p>This page explains the basic salsa incremental algorithm.
|
||||
The algorithm is called the "red-green" algorithm, which is where the name salsa comes from.</p>
|
||||
<p>This page explains the basic Salsa incremental algorithm.
|
||||
The algorithm is called the "red-green" algorithm, which is where the name Salsa comes from.</p>
|
||||
<h3 id="database-revisions"><a class="header" href="#database-revisions">Database revisions</a></h3>
|
||||
<p>The salsa database always tracks a single <strong>revision</strong>. Each time you set an input, the revision is incremented. So we start in revision <code>R1</code>, but when a <code>set</code> method is called, we will go to <code>R2</code>, then <code>R3</code>, and so on. For each input, we also track the revision in which it was last changed.</p>
|
||||
<p>The Salsa database always tracks a single <strong>revision</strong>. Each time you set an input, the revision is incremented. So we start in revision <code>R1</code>, but when a <code>set</code> method is called, we will go to <code>R2</code>, then <code>R3</code>, and so on. For each input, we also track the revision in which it was last changed.</p>
|
||||
<h3 id="basic-rule-when-inputs-change-re-execute"><a class="header" href="#basic-rule-when-inputs-change-re-execute">Basic rule: when inputs change, re-execute!</a></h3>
|
||||
<p>When you invoke a tracked function, in addition to storing the value that was returned, we also track what <em>other</em> tracked functions it depends on, and the revisions when their value last changed. When you invoke the function again, if the database is in a new revision, then we check whether any of the inputs to this function have changed in that new revision. If not, we can just return our cached value. But if the inputs <em>have</em> changed (or may have changed), we will re-execute the function to find the most up-to-date answer.</p>
|
||||
<p>Here is a simple example, where the <code>parse_module</code> function invokes the <code>module_text</code> function:</p>
|
||||
|
@ -161,7 +161,7 @@ fn parse_module(db: &dyn Db, module: Module) -> Ast {
|
|||
Ast::parse_text(module_text)
|
||||
}
|
||||
|
||||
#[salsa::tracked(ref)]
|
||||
#[salsa::tracked(return_ref)]
|
||||
fn module_text(db: &dyn Db, module: Module) -> String {
|
||||
panic!("text for module `{module:?}` not set")
|
||||
}
|
||||
|
@ -206,7 +206,9 @@ fn type_check(db: &dyn Db, module: Module) {
|
|||
<li>We will then compare the resulting AST. If it's the same as last time, we can <em>backdate</em> the result, meaning that we say that, even though the inputs changed, the output didn't.</li>
|
||||
</ul>
|
||||
<h2 id="durability-an-optimization"><a class="header" href="#durability-an-optimization">Durability: an optimization</a></h2>
|
||||
<p>As an optimization, salsa includes the concept of <strong>durability</strong>. When you set the value of a tracked function, you can also set it with a given <em>durability</em>:</p>
|
||||
<p>As an optimization, Salsa includes the concept of <strong>durability</strong>, which is the notion of how often some piece of tracked data changes. </p>
|
||||
<p>For example, when compiling a Rust program, you might mark the inputs from crates.io as <em>high durability</em> inputs, since they are unlikely to change. The current workspace could be marked as <em>low durability</em>, since changes to it are happening all the time.</p>
|
||||
<p>When you set the value of a tracked function, you can also set it with a given <em>durability</em>:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -219,7 +221,6 @@ fn type_check(db: &dyn Db, module: Module) {
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>For each durability, we track the revision in which <em>some input</em> with that durability changed. If a tracked function depends (transitively) only on high durability inputs, and you change a low durability input, then we can very easily determine that the tracked function result is still valid, avoiding the need to traverse the input edges one by one.</p>
|
||||
<p>An example: if compiling a Rust program, you might mark the inputs from crates.io as <em>high durability</em> inputs, since they are unlikely to change. The current workspace could be marked as <em>low durability</em>.</p>
|
||||
|
||||
</main>
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -146,9 +146,10 @@
|
|||
<main>
|
||||
<h1 id="defining-the-parser-reporting-errors"><a class="header" href="#defining-the-parser-reporting-errors">Defining the parser: reporting errors</a></h1>
|
||||
<p>The last interesting case in the parser is how to handle a parse error.
|
||||
Because salsa functions are memoized and may not execute, they should not have side-effects,
|
||||
Because Salsa functions are memoized and may not execute, they should not have side-effects,
|
||||
so we don't just want to call <code>eprintln!</code>.
|
||||
If we did so, the error would only be reported the first time the function was called.</p>
|
||||
If we did so, the error would only be reported the first time the function was called, but not
|
||||
on subsequent calls in the situation where the simply returns its memoized value.</p>
|
||||
<p>Salsa defines a mechanism for managing this called an <strong>accumulator</strong>.
|
||||
In our case, we define an accumulator struct called <code>Diagnostics</code> in the <code>ir</code> module:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
@ -168,7 +169,7 @@ pub struct Diagnostic {
|
|||
<p>Accumulator structs are always newtype structs with a single field, in this case of type <code>Diagnostic</code>.
|
||||
Memoized functions can <em>push</em> <code>Diagnostic</code> values onto the accumulator.
|
||||
Later, you can invoke a method to find all the values that were pushed by the memoized functions
|
||||
or any function that it called
|
||||
or any functions that they called
|
||||
(e.g., we could get the set of <code>Diagnostic</code> values produced by the <code>parse_statements</code> function).</p>
|
||||
<p>The <code>Parser::report_error</code> method contains an example of pushing a diagnostic:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
|
|
@ -164,10 +164,9 @@ pub(crate) struct Database {
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>The <code>#[salsa::db(...)]</code> attribute takes a list of all the jars to include.
|
||||
The struct must have a field named <code>storage</code> whose types is <code>salsa::Storage<Self></code>, but it can also contain whatever other fields you want.
|
||||
The struct must have a field named <code>storage</code> whose type is <code>salsa::Storage<Self></code>, but it can also contain whatever other fields you want.
|
||||
The <code>storage</code> struct owns all the data for the jars listed in the <code>db</code> attribute.</p>
|
||||
<p>The <code>salsa::db</code> attribute autogenerates a bunch of impls for things like the <code>salsa::HasJar<crate::Jar></code> trait that we saw earlier.
|
||||
This means that</p>
|
||||
<p>The <code>salsa::db</code> attribute autogenerates a bunch of impls for things like the <code>salsa::HasJar<crate::Jar></code> trait that we saw earlier.</p>
|
||||
<h2 id="implementing-the-salsadatabase-trait"><a class="header" href="#implementing-the-salsadatabase-trait">Implementing the <code>salsa::Database</code> trait</a></h2>
|
||||
<p>In addition to the struct itself, we must add an impl of <code>salsa::Database</code>:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
@ -226,7 +225,7 @@ This means that</p>
|
|||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<h2 id="implementing-the-traits-for-each-jar"><a class="header" href="#implementing-the-traits-for-each-jar">Implementing the traits for each Jar</a></h2>
|
||||
<h2 id="implementing-the-traits-for-each-jar"><a class="header" href="#implementing-the-traits-for-each-jar">Implementing the traits for each jar</a></h2>
|
||||
<p>The <code>Database</code> struct also needs to implement the <a href="./jar.html#database-trait-for-the-jar">database traits for each jar</a>.
|
||||
In our case, though, we already wrote that impl as a <a href="./jar.html#implementing-the-database-trait-for-the-jar">blanket impl alongside the jar itself</a>,
|
||||
so no action is needed.
|
||||
|
|
|
@ -150,7 +150,7 @@ To do so, we will create a database, set the input source text, run the parser,
|
|||
Before we can do that, though, we have to address one question: how do we inspect the value of an interned type like <code>Expression</code>?</p>
|
||||
<h2 id="the-debugwithdb-trait"><a class="header" href="#the-debugwithdb-trait">The <code>DebugWithDb</code> trait</a></h2>
|
||||
<p>Because an interned type like <code>Expression</code> just stores an integer, the traditional <code>Debug</code> trait is not very useful.
|
||||
To properly print a <code>Expression</code>, you need to access the salsa database to find out what its value is.
|
||||
To properly print a <code>Expression</code>, you need to access the Salsa database to find out what its value is.
|
||||
To solve this, <code>salsa</code> provides a <code>DebugWithDb</code> trait that acts like the regular <code>Debug</code>, but takes a database as argument.
|
||||
For types that implement this trait, you can invoke the <code>debug</code> method.
|
||||
This returns a temporary that implements the ordinary <code>Debug</code> trait, allowing you to write something like</p>
|
||||
|
@ -161,7 +161,7 @@ This returns a temporary that implements the ordinary <code>Debug</code> trait,
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>and get back the output you expect.</p>
|
||||
<p>The <code>DebugWithDb</code> trait is automatically derived for all <code>#[input]</code>, <code>#[interned]</code> and <code>#[tracked]</code> structs.</p>
|
||||
<p>The <code>DebugWithDb</code> trait is automatically derived for all <code>#[input]</code>, <code>#[interned]</code>, and <code>#[tracked]</code> structs.</p>
|
||||
<h2 id="forwarding-to-the-ordinary-debug-trait"><a class="header" href="#forwarding-to-the-ordinary-debug-trait">Forwarding to the ordinary <code>Debug</code> trait</a></h2>
|
||||
<p>For consistency, it is sometimes useful to have a <code>DebugWithDb</code> implementation even for types, like <code>Op</code>, that are just ordinary enums. You can do that like so:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
|
|
@ -149,22 +149,22 @@
|
|||
In the <a href="./structure.html">basic structure</a>, we defined some "pseudo-Rust" structures like <code>Statement</code> and <code>Expression</code>;
|
||||
now we are going to define them for real.</p>
|
||||
<h2 id="salsa-structs"><a class="header" href="#salsa-structs">"Salsa structs"</a></h2>
|
||||
<p>In addition to regular Rust types, we will make use of various <strong>salsa structs</strong>.
|
||||
A salsa struct is a struct that has been annotated with one of the salsa annotations:</p>
|
||||
<p>In addition to regular Rust types, we will make use of various <strong>Salsa structs</strong>.
|
||||
A Salsa struct is a struct that has been annotated with one of the Salsa annotations:</p>
|
||||
<ul>
|
||||
<li><a href="#input-structs"><code>#[salsa::input]</code></a>, which designates the "base inputs" to your computation;</li>
|
||||
<li><a href="#tracked-structs"><code>#[salsa::tracked]</code></a>, which designate intermediate values created during your computation;</li>
|
||||
<li><a href="#interned-structs"><code>#[salsa::interned]</code></a>, which designate small values that are easy to compare for equality.</li>
|
||||
</ul>
|
||||
<p>All salsa structs store the actual values of their fields in the salsa database.
|
||||
<p>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.</p>
|
||||
<p>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.
|
||||
<p>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 <a href="./jar.html">jar</a>.
|
||||
By default, this is <code>crate::Jar</code>, but you can specify a different jar with the <code>jar=</code> attribute (e.g., <code>#[salsa::input(jar = MyJar)]</code>).
|
||||
You must also list the struct in the jar definition itself, or you will get errors.</p>
|
||||
<h2 id="input-structs"><a class="header" href="#input-structs">Input structs</a></h2>
|
||||
<p>The first thing we will define is our <strong>input</strong>.
|
||||
Every salsa program has some basic inputs that drive the rest of the computation.
|
||||
Every Salsa program has some basic inputs that drive the rest of the computation.
|
||||
The rest of the program must be some deterministic function of those base inputs,
|
||||
such that when those inputs change, we can try to efficiently recompute the new result of that function.</p>
|
||||
<p>Inputs are defined as Rust structs with a <code>#[salsa::input]</code> annotation:</p>
|
||||
|
@ -180,8 +180,8 @@ pub struct SourceProgram {
|
|||
</span></code></pre></pre>
|
||||
<p>In our compiler, we have just one simple input, the <code>SourceProgram</code>, which has a <code>text</code> field (the string).</p>
|
||||
<h3 id="the-data-lives-in-the-database"><a class="header" href="#the-data-lives-in-the-database">The data lives in the database</a></h3>
|
||||
<p>Although they are declared like other Rust structs, salsa structs are implemented quite differently.
|
||||
The values of their fields are stored in the salsa database, and the struct itself just contains a numeric identifier.
|
||||
<p>Although they are declared like other Rust structs, Salsa structs are implemented quite differently.
|
||||
The values of their fields are stored in the Salsa database, and the struct itself just contains a numeric identifier.
|
||||
This means that the struct instances are copy (no matter what fields they contain).
|
||||
Creating instances of the struct and accessing fields is done by invoking methods like <code>new</code> as well as getters and setters.</p>
|
||||
<p>More concretely, the <code>#[salsa::input]</code> annotation will generate a struct for <code>SourceProgram</code> like this:</p>
|
||||
|
@ -224,7 +224,7 @@ pub struct Program {
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>Like with an input, the fields of a tracked struct are also stored in the database.
|
||||
Unlike an input, those fields are immutable (they cannot be "set"), and salsa compares them across revisions to know when they have changed.
|
||||
Unlike an input, those fields are immutable (they cannot be "set"), and Salsa compares them across revisions to know when they have changed.
|
||||
In this case, if parsing the input produced the same <code>Program</code> result (e.g., because the only change to the input was some trailing whitespace, perhaps),
|
||||
then subsequent parts of the computation won't need to re-execute.
|
||||
(We'll revisit the role of tracked structs in reuse more in future parts of the IR.)</p>
|
||||
|
@ -239,9 +239,7 @@ then subsequent parts of the computation won't need to re-execute.
|
|||
</ul>
|
||||
<h2 id="representing-functions"><a class="header" href="#representing-functions">Representing functions</a></h2>
|
||||
<p>We will also use a tracked struct to represent each function:
|
||||
Next we will define a <strong>tracked struct</strong>.
|
||||
Whereas inputs represent the <em>start</em> of a computation, tracked structs represent intermediate values created during your computation.</p>
|
||||
<p>The <code>Function</code> struct is going to be created by the parser to represent each of the functions defined by the user:</p>
|
||||
The <code>Function</code> struct is going to be created by the parser to represent each of the functions defined by the user:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -260,9 +258,7 @@ pub struct Function {
|
|||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>Like with an input, the fields of a tracked struct are also stored in the database.
|
||||
Unlike an input, those fields are immutable (they cannot be "set"), and salsa compares them across revisions to know when they have changed.
|
||||
If we had created some <code>Function</code> instance <code>f</code>, for example, we might find that <code>the f.body</code> field changes
|
||||
<p>If we had created some <code>Function</code> instance <code>f</code>, for example, we might find that <code>the f.body</code> field changes
|
||||
because the user changed the definition of <code>f</code>.
|
||||
This would mean that we have to re-execute those parts of the code that depended on <code>f.body</code>
|
||||
(but not those parts of the code that depended on the body of <em>other</em> functions).</p>
|
||||
|
@ -278,7 +274,7 @@ This indicates that, across two revisions R1 and R2, if two functions are create
|
|||
Adding <code>#[id]</code> attributes is an optimization and never affects correctness.
|
||||
For more details, see the <a href="../reference/algorithm.html">algorithm</a> page of the reference.</p>
|
||||
<h2 id="interned-structs"><a class="header" href="#interned-structs">Interned structs</a></h2>
|
||||
<p>The final kind of salsa struct are <em>interned structs</em>.
|
||||
<p>The final kind of Salsa struct are <em>interned structs</em>.
|
||||
As with input and tracked structs, the data for an interned struct is stored in the database, and you just pass around a single integer.
|
||||
Unlike those structs, if you intern the same data twice, you get back the <strong>same integer</strong>.</p>
|
||||
<p>A classic use of interning is for small strings like function names and variables.
|
||||
|
@ -313,13 +309,13 @@ assert_eq!(f1, f2);
|
|||
</span></code></pre></pre>
|
||||
<h3 id="interned-ids-are-guaranteed-to-be-consistent-within-a-revision-but-not-across-revisions-but-you-dont-have-to-care"><a class="header" href="#interned-ids-are-guaranteed-to-be-consistent-within-a-revision-but-not-across-revisions-but-you-dont-have-to-care">Interned ids are guaranteed to be consistent within a revision, but not across revisions (but you don't have to care)</a></h3>
|
||||
<p>Interned ids are guaranteed not to change within a single revision, so you can intern things from all over your program and get back consistent results.
|
||||
When you change the inputs, however, salsa may opt to clear some of the interned values and choose different integers.
|
||||
When you change the inputs, however, Salsa may opt to clear some of the interned values and choose different integers.
|
||||
However, if this happens, it will also be sure to re-execute every function that interned that value, so all of them still see a consistent value,
|
||||
just a different one than they saw in a previous revision.</p>
|
||||
<p>In other words, within a salsa computation, you can assume that interning produces a single consistent integer, and you don't have to think about it.
|
||||
If however you export interned identifiers outside the computation, and then change the inputs, they may not longer be valid or may refer to different values.</p>
|
||||
<p>In other words, within a Salsa computation, you can assume that interning produces a single consistent integer, and you don't have to think about it.
|
||||
If, however, you export interned identifiers outside the computation, and then change the inputs, they may no longer be valid or may refer to different values.</p>
|
||||
<h3 id="expressions-and-statements"><a class="header" href="#expressions-and-statements">Expressions and statements</a></h3>
|
||||
<p>We'll won't use any special "salsa structs" for expressions and statements:</p>
|
||||
<p>We won't use any special "Salsa structs" for expressions and statements:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
|
@ -365,7 +361,7 @@ pub enum Op {
|
|||
<p>Since statements and expressions are not tracked, this implies that we are only attempting to get incremental re-use at the granularity of functions --
|
||||
whenever anything in a function body changes, we consider the entire function body dirty and re-execute anything that depended on it.
|
||||
It usually makes sense to draw some kind of "reasonably coarse" boundary like this.</p>
|
||||
<p>One downside of the way we have set things up: we inlined the position into each of the structs.</p>
|
||||
<p>One downside of the way we have set things up: we inlined the position into each of the structs [what exactly does this mean?].</p>
|
||||
|
||||
</main>
|
||||
|
||||
|
|
|
@ -145,10 +145,10 @@
|
|||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="jars-and-databases"><a class="header" href="#jars-and-databases">Jars and databases</a></h1>
|
||||
<p>Before we can define the interesting parts of our salsa program, we have to setup a bit of structure that defines the salsa <strong>database</strong>.
|
||||
The database is a struct that ultimately stores all of salsa's intermediate state, such as the memoized return values from <a href="../overview.html#tracked-functions">tracked functions</a>.</p>
|
||||
<p>Before we can define the interesting parts of our Salsa program, we have to setup a bit of structure that defines the Salsa <strong>database</strong>.
|
||||
The database is a struct that ultimately stores all of Salsa's intermediate state, such as the memoized return values from <a href="../overview.html#tracked-functions">tracked functions</a>.</p>
|
||||
<p>The database itself is defined in terms of intermediate structures, called <strong>jars</strong><sup class="footnote-reference"><a href="#jar">1</a></sup>, which themselves contain the data for each function.
|
||||
This setup allows salsa programs to be divided amongst many crates.
|
||||
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.</p>
|
||||
<div class="footnote-definition" id="jar"><sup class="footnote-definition-label">1</sup>
|
||||
|
@ -180,7 +180,7 @@ pub struct Jar(
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>Although it's not required, it's highly recommended to put the <code>jar</code> struct at the root of your crate, so that it can be referred to as <code>crate::Jar</code>.
|
||||
All of the other salsa annotations reference a jar struct, and they all default to the path <code>crate::Jar</code>.
|
||||
All of the other Salsa annotations reference a jar struct, and they all default to the path <code>crate::Jar</code>.
|
||||
If you put the jar somewhere else, you will have to override that default.</p>
|
||||
<h2 id="defining-the-database-trait"><a class="header" href="#defining-the-database-trait">Defining the database trait</a></h2>
|
||||
<p>The <code>#[salsa::jar]</code> annotation also includes a <code>db = Db</code> field.
|
||||
|
@ -198,7 +198,7 @@ This allows for separate compilation, where you have a database that contains th
|
|||
where <code>Jar</code> is the jar struct. If your jar depends on other jars, you can have multiple such supertraits (e.g., <code>salsa::DbWithJar<other_crate::Jar></code>).</p>
|
||||
<p>Typically the <code>Db</code> 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,
|
||||
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.</p>
|
||||
<h2 id="implementing-the-database-trait-for-the-jar"><a class="header" href="#implementing-the-database-trait-for-the-jar">Implementing the database trait for the jar</a></h2>
|
||||
<p>The <code>Db</code> trait must be implemented by the database struct.
|
||||
|
@ -214,11 +214,11 @@ and that's what we do here:</p>
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
|
||||
<p>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 do:</p>
|
||||
<p>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:</p>
|
||||
<ul>
|
||||
<li>In each of your crates:
|
||||
<ul>
|
||||
<li>Define a <code>#[salsa::jar(db = Db)]</code> struct, typically at <code>crate::Jar</code>, and list each of your various salsa-annotated things inside of it.</li>
|
||||
<li>Define a <code>#[salsa::jar(db = Db)]</code> struct, typically at <code>crate::Jar</code>, and list each of your various Salsa-annotated things inside of it.</li>
|
||||
<li>Define a <code>Db</code> trait, typically at <code>crate::Db</code>, that you will use in memoized functions and elsewhere to refer to the database struct.</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
|
@ -152,7 +152,7 @@ and create the <code>Statement</code>, <code>Function</code>, and <code>Expressi
|
|||
<p>To minimize dependencies, we are going to write a <a href="https://en.wikipedia.org/wiki/Recursive_descent_parser">recursive descent parser</a>.
|
||||
Another option would be to use a <a href="https://rustrepo.com/catalog/rust-parsing_newest_1">Rust parsing framework</a>.
|
||||
We won't cover the parsing itself in this tutorial -- you can read the code if you want to see how it works.
|
||||
We're going to focus only on the salsa-related aspects.</p>
|
||||
We're going to focus only on the Salsa-related aspects.</p>
|
||||
<h2 id="the-parse_statements-function"><a class="header" href="#the-parse_statements-function">The <code>parse_statements</code> function</a></h2>
|
||||
<p>The starting point for the parser is the <code>parse_statements</code> function:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">
|
||||
|
@ -198,16 +198,16 @@ pub fn parse_statements(db: &dyn crate::Db, source: SourceProgram) -> Pro
|
|||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
<p>This function is annotated as <code>#[salsa::tracked]</code>.
|
||||
That means that, when it is called, salsa will track what inputs it reads as well as what value it returns.
|
||||
That means that, when it is called, Salsa will track what inputs it reads as well as what value it returns.
|
||||
The return value is <em>memoized</em>,
|
||||
which means that if you call this function again without changing the inputs,
|
||||
salsa will just clone the result rather than re-execute it.</p>
|
||||
Salsa will just clone the result rather than re-execute it.</p>
|
||||
<h3 id="tracked-functions-are-the-unit-of-reuse"><a class="header" href="#tracked-functions-are-the-unit-of-reuse">Tracked functions are the unit of reuse</a></h3>
|
||||
<p>Tracked functions are the core part of how salsa enables incremental reuse.
|
||||
<p>Tracked functions are the core part of how Salsa enables incremental reuse.
|
||||
The goal of the framework is to avoid re-executing tracked functions and instead to clone their result.
|
||||
Salsa uses the <a href="../reference/algorithm.html">red-green algorithm</a> to decide when to re-execute a function.
|
||||
The short version is that a tracked function is re-executed if either (a) it directly reads an input, and that input has changed
|
||||
or (b) it directly invokes another tracked function, and that function's return value has changed.
|
||||
The short version is that a tracked function is re-executed if either (a) it directly reads an input, and that input has changed,
|
||||
or (b) it directly invokes another tracked function and that function's return value has changed.
|
||||
In the case of <code>parse_statements</code>, it directly reads <code>ProgramSource::text</code>, so if the text changes, then the parser will re-execute.</p>
|
||||
<p>By choosing which functions to mark as <code>#[tracked]</code>, you control how much reuse you get.
|
||||
In our case, we're opting to mark the outermost parsing function as tracked, but not the inner ones.
|
||||
|
@ -218,19 +218,19 @@ and because (b) since strings are just a big blob-o-bytes without any structure,
|
|||
Some systems do choose to do more granular reparsing, often by doing a "first pass" over the string to give it a bit of structure,
|
||||
e.g. to identify the functions,
|
||||
but deferring the parsing of the body of each function until later.
|
||||
Setting up a scheme like this is relatively easy in salsa, and uses the same principles that we will use later to avoid re-executing the type checker.</p>
|
||||
Setting up a scheme like this is relatively easy in Salsa and uses the same principles that we will use later to avoid re-executing the type checker.</p>
|
||||
<h3 id="parameters-to-a-tracked-function"><a class="header" href="#parameters-to-a-tracked-function">Parameters to a tracked function</a></h3>
|
||||
<p>The <strong>first</strong> parameter to a tracked function is <strong>always</strong> the database, <code>db: &dyn crate::Db</code>.
|
||||
It must be a <code>dyn</code> value of whatever database is associated with the jar.</p>
|
||||
<p>The <strong>second</strong> parameter to a tracked function is <strong>always</strong> some kind of salsa struct.
|
||||
<p>The <strong>second</strong> parameter to a tracked function is <strong>always</strong> some kind of Salsa struct.
|
||||
The first parameter to a memoized function is always the database,
|
||||
which should be a <code>dyn Trait</code> value for the database trait associated with the jar
|
||||
(the default jar is <code>crate::Jar</code>).</p>
|
||||
<p>Tracked functions may take other arguments as well, though our examples here do not.
|
||||
Functions that take additional arguments are less efficient and flexible.
|
||||
It's generally better to structure tracked functions as functions of a single salsa struct if possible.</p>
|
||||
It's generally better to structure tracked functions as functions of a single Salsa struct if possible.</p>
|
||||
<h3 id="the-return_ref-annotation"><a class="header" href="#the-return_ref-annotation">The <code>return_ref</code> annotation</a></h3>
|
||||
<p>You may have noticed that <code>parse_statements</code> is tagged with <code>#[salsa::tracked(return_ref)]</code>.
|
||||
<p>You may have noticed that <code>parse_statements</code> is tagged with <code>#[salsa::tracked(return_ref)]</code>.
|
||||
Ordinarily, when you call a tracked function, the result you get back is cloned out of the database.
|
||||
The <code>return_ref</code> attribute means that a reference into the database is returned instead.
|
||||
So, when called, <code>parse_statements</code> will return an <code>&Vec<Statement></code> rather than cloning the <code>Vec</code>.
|
||||
|
|
|
@ -145,8 +145,8 @@
|
|||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="basic-structure"><a class="header" href="#basic-structure">Basic structure</a></h1>
|
||||
<p>Before we do anything with salsa, let's talk about the basic structure of the calc compiler.
|
||||
Part of salsa's design is that you are able to write programs that feel 'pretty close' to what a natural Rust program looks like.</p>
|
||||
<p>Before we do anything with Salsa, let's talk about the basic structure of the calc compiler.
|
||||
Part of Salsa's design is that you are able to write programs that feel 'pretty close' to what a natural Rust program looks like.</p>
|
||||
<h2 id="example-program"><a class="header" href="#example-program">Example program</a></h2>
|
||||
<p>This is our example calc program:</p>
|
||||
<pre><code>x = 5
|
||||
|
@ -175,7 +175,7 @@ print z
|
|||
Print(Expression),
|
||||
}
|
||||
|
||||
/// Defines `fn <name>(<args>) = <body>`
|
||||
/// Defines `fn <name>(<args>) = <body>`
|
||||
struct Function {
|
||||
name: FunctionId,
|
||||
args: Vec<VariableId>,
|
||||
|
@ -218,7 +218,7 @@ type VariableId = /* interned string */;
|
|||
We're going to write the checker in a "context-less" style,
|
||||
which is a bit less intuitive but allows for more incremental re-use.
|
||||
The idea is to compute, for a given expression, which variables it references.
|
||||
Then there is a function "check" which ensures that those variables are a subset of those that are already defined.</p>
|
||||
Then there is a function <code>check</code> which ensures that those variables are a subset of those that are already defined.</p>
|
||||
<h2 id="interpreter"><a class="header" href="#interpreter">Interpreter</a></h2>
|
||||
<p>The interpreter will execute the program and print the result. We don't bother with much incremental re-use here,
|
||||
though it's certainly possible.</p>
|
||||
|
|
|
@ -149,7 +149,7 @@
|
|||
<ul>
|
||||
<li><a href="https://youtu.be/_muY4HjSqVw">How Salsa Works</a>, which gives a
|
||||
high-level introduction to the key concepts involved and shows how
|
||||
to use salsa;</li>
|
||||
to use Salsa;</li>
|
||||
<li><a href="https://www.youtube.com/watch?v=i_IhACacPRY">Salsa In More Depth</a>,
|
||||
which digs into the incremental algorithm and explains -- at a
|
||||
high-level -- how Salsa is implemented.</li>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue