diff --git a/book/src/tutorial/accumulators.md b/book/src/tutorial/accumulators.md index 0df25ed4e..92f8b9f80 100644 --- a/book/src/tutorial/accumulators.md +++ b/book/src/tutorial/accumulators.md @@ -7,7 +7,7 @@ If we did so, the error would only be reported the first time the function was c on subsequent calls in the situation where the simply returns its memoized value. Salsa defines a mechanism for managing this called an **accumulator**. -In our case, we define an accumulator struct called `Diagnostics` in the `ir` module: +In our case, we define an accumulator struct called `Diagnostic` in the `ir` module: ```rust {{#include ../../../examples/calc/ir.rs:diagnostic}} @@ -30,7 +30,7 @@ we invoke the associated `accumulated` function: ```rust let accumulated: Vec = - parse_statements::accumulated::(db); + parse_statements::accumulated::(db); // ----------- // Use turbofish to specify // the diagnostics type. diff --git a/book/src/tutorial/checker.md b/book/src/tutorial/checker.md index 71e316339..9c36516dd 100644 --- a/book/src/tutorial/checker.md +++ b/book/src/tutorial/checker.md @@ -1 +1,3 @@ # Defining the checker + +// TODO \ No newline at end of file diff --git a/book/src/tutorial/debug.md b/book/src/tutorial/debug.md index c3a5253ff..f5273a404 100644 --- a/book/src/tutorial/debug.md +++ b/book/src/tutorial/debug.md @@ -2,36 +2,119 @@ As the final part of the parser, we need to write some tests. To do so, we will create a database, set the input source text, run the parser, and check the result. -Before we can do that, though, we have to address one question: how do we inspect the value of an interned type like `Expression`? +Before we can do that, though, we have to address one question: how do we inspect the value of a `salsa::tracked` +type like `Program`? -## The `DebugWithDb` trait +## The `salsa::Database::attach` method -Because an interned type like `Expression` just stores an integer, the traditional `Debug` trait is not very useful. -To properly print a `Expression`, you need to access the Salsa database to find out what its value is. -To solve this, `salsa` provides a `DebugWithDb` trait that acts like the regular `Debug`, but takes a database as argument. -For types that implement this trait, you can invoke the `debug` method. -This returns a temporary that implements the ordinary `Debug` trait, allowing you to write something like +Because a tracked type like `Program` just stores an integer, the traditional `Debug` trait is not very useful. +To properly print a `Program`, you need to access the Salsa database to find out what its value is. +To solve this, `salsa` provides the `debug` option when declaring tracked structs: `#[salsa::tracked(debug)]` which +creates an implementation of `Debug` that can access the values of `Program` in +the salsa database: ```rust -eprintln!("Expression = {:?}", expr.debug(db)); +{{#include ../../../examples/calc/ir.rs:program}} ``` -and get back the output you expect. +Specifying the `debug` option allows us to use our types in formatted strings, +but it's not enough to get the full value. Simply writing this code: -The `DebugWithDb` trait is automatically derived for all `#[input]`, `#[interned]`, and `#[tracked]` structs. +```rust +let db: CalcDatabaseImpl = Default::default(); + +let surce_text = "print 1 + 2"; +let source_program = SourceProgram::new(db, source_text.to_string()); + +let statements = parse_statements(db, source_program); + +println!("{:#?}", statements); +``` + +gives us this output: + +``` +Program { + [salsa id]: Id(800), +} +``` -## Forwarding to the ordinary `Debug` trait +And that is because when `println!` calls `Debug::fmt` on our `statements` variable +of type `Program`, the `Debug::fmt` implementation has no access to the Salsa database +to inspect the values. -For consistency, it is sometimes useful to have a `DebugWithDb` implementation even for types, like `Op`, that are just ordinary enums. You can do that like so: +In order to allow `Debug::fmt` to access the database, we can call it inside a +function passed to `Database::attach` which sets a thread-local variable to the +`Database` value it was called on, allowing the debug implementation to access it +and inspect values: ```rust -{{#include ../../../examples/calc/ir.rs:op_debug_impl}} +let db: CalcDatabaseImpl = Default::default(); + +db.attach(|db| { + let surce_text = "print 1 + 2"; + let source_program = SourceProgram::new(db, source_text.to_string()); + + let statements = parse_statements(db, source_program); + + println!("{:#?}", statements); +}) +``` + +Now we should see something like this: + +``` +Program { + [salsa id]: Id(800), + statements: [ + Statement { + span: Span { + [salsa id]: Id(404), + start: 0, + end: 11, + }, + data: Print( + Expression { + span: Span { + [salsa id]: Id(403), + start: 6, + end: 11, + }, + data: Op( + Expression { + span: Span { + [salsa id]: Id(400), + start: 6, + end: 7, + }, + data: Number( + 1.0, + ), + }, + Add, + Expression { + span: Span { + [salsa id]: Id(402), + start: 10, + end: 11, + }, + data: Number( + 2.0, + ), + }, + ), + }, + ), + }, + ], +} ``` ## Writing the unit test -Now that we have our `DebugWithDb` impls in place, we can write a simple unit test harness. -The `parse_string` function below creates a database, sets the source text, and then invokes the parser: +Now that we know how to inspect all the values of Salsa structs, we can write a simple unit test harness. +The `parse_string` function below creates a database, sets the source text, and then invokes the parser +to get the statements and creates a formatted string with all the values: ```rust {{#include ../../../examples/calc/parser.rs:parse_string}} diff --git a/book/src/tutorial/interpreter.md b/book/src/tutorial/interpreter.md index 0142e288b..458785345 100644 --- a/book/src/tutorial/interpreter.md +++ b/book/src/tutorial/interpreter.md @@ -1 +1,3 @@ # Defining the interpreter + +// TODO \ No newline at end of file diff --git a/book/src/tutorial/ir.md b/book/src/tutorial/ir.md index 0c86fb06e..7fe0ead50 100644 --- a/book/src/tutorial/ir.md +++ b/book/src/tutorial/ir.md @@ -101,7 +101,7 @@ in other words, you cannot change the values of a `salsa::Input`. The `'db` lifetime also allows tracked structs to be implemented using a pointer (versus the numeric id found in `salsa::input` structs). -This doesn't really effect you as a user except that it allows accessing fields from tracked structs— +This doesn't really affect you as a user except that it allows accessing fields from tracked structs— a very common operation—to be optimized. ## Representing functions @@ -113,7 +113,7 @@ The `Function` struct is going to be created by the parser to represent each of {{#include ../../../examples/calc/ir.rs:functions}} ``` -If we had created some `Function` instance `f`, for example, we might find that `the f.body` field changes +If we had created some `Function` instance `f`, for example, we might find that the `f.body` field changes because the user changed the definition of `f`. This would mean that we have to re-execute those parts of the code that depended on `f.body` (but not those parts of the code that depended on the body of _other_ functions). @@ -177,3 +177,62 @@ whenever anything in a function body changes, we consider the entire function bo It usually makes sense to draw some kind of "reasonably coarse" boundary like this. One downside of the way we have set things up: we inlined the position into each of the structs. + +## The `returns` attribute for struct fields + +You may have noticed that some fields of salsa structs are annotated with +`returns(ref)`. There are 6 possible return annotations for struct fields. +Some of them require implementing standard library traits or `salsa` specific +traits. + +Given this salsa struct definition: + +```rust +/// Number wraps an i32 and implements Copy & Clone required for +/// `returns(copy)` and `returns(clone)`. +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +struct Number(i32); + +/// Deref is required for `returns(deref)`. +impl std::ops::Deref for Number { + type Target = i32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// SalsaAsDeref is required for `returns(as_deref)`. +impl salsa::SalsaAsDeref for Number { + type AsDeref<'a> = i32; + + fn as_deref(&self) -> i32 { + self.0 + } +} + +/// SalsaAsRef is required for `returns(as_ref)`. +impl salsa::SalsaAsRef for Number { + type AsRef<'a> = &'a i32; + + fn as_ref(&self) -> &i32 { + &self.0 + } +} + +/// Salsa struct. +#[salsa::input] +struct Input { + #[returns(clone)] // We can use the different returns annotations here. + number: Number, +} +``` + +We can use one of these annotations for the `Input::number` field: + +- `returns(clone)` (**the default**): Reading the field returns a clone: `Number`. Requires `Clone`. +- `returns(ref)`: Reading the field returns a ref: `&Number`. +- `returns(copy)`: Reading the field returns a Copy: `Number`. Requires `Copy`. +- `returns(deref)`: Reading the field returns the dereferenced value: `&i32`. Requires `Deref`. +- `returns(as_ref)`: Reading the field returns a ref to internal values: `&i32`. Requires `SalsaAsRef`. +- `returns(as_deref)`: Reading the field returns the dereferenced value: `i32`. Requires `SalsaAsDeref`. diff --git a/book/src/tutorial/parser.md b/book/src/tutorial/parser.md index 94488fd91..600c60c8d 100644 --- a/book/src/tutorial/parser.md +++ b/book/src/tutorial/parser.md @@ -41,7 +41,7 @@ This means that if the input changes, we will always re-parse the entire input a We'll see later that this _doesn't_ mean we will always re-run the type checker and other parts of the compiler. This trade-off makes sense because (a) parsing is very cheap, so the overhead of tracking and enabling finer-grained reuse doesn't pay off -and because (b) since strings are just a big blob-o-bytes without any structure, it's rather hard to identify which parts of the IR need to be reparsed. +and because (b) since strings are just a big blob-of-bytes without any structure, it's rather hard to identify which parts of the IR need to be reparsed. 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. @@ -57,12 +57,19 @@ Tracked functions may take other arguments as well, though our examples here do 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. -### The `returns(ref)` annotation -You may have noticed that `parse_statements` is tagged with `#[salsa::tracked(returns(ref))]`. -Ordinarily, when you call a tracked function, the result you get back is cloned out of the database. -The `returns(ref)` attribute means that a reference into the database is returned instead. -So, when called, `parse_statements` will return an `&Vec` rather than cloning the `Vec`. -This is useful as a performance optimization. -(You may recall the `returns(ref)` annotation from the [ir](./ir.md) section of the tutorial, -where it was placed on struct fields, with roughly the same meaning.) +## The `returns` attribute for functions + +You may have noticed that `parse_statements` is tagged with `#[salsa::tracked]`. +Ordinarily, when you call a tracked function, the result you get back +**is cloned** out of the database. You may recall the different `returns(_)` +annotations from the [IR](./ir.md#the-returns-attribute-for-struct-fields) chapter. + +They all apply to salsa functions the same way: + +- `salsa::tracked(returns(clone))` (**the default**): Returns a clone of the return type. Requires `Clone`. +- `salsa::tracked(returns(ref))`: Returns a ref to the return type. +- `salsa::tracked(returns(copy))`: Returns a copy. Requires `Copy`. +- `salsa::tracked(returns(deref))`: Returns the result of calling `Deref::deref`. Requires `Deref`. +- `salsa::tracked(returns(as_ref))`: Returns the result of `SalsaAsRef::as_ref`. Requires `SalsaAsRef`. +- `salsa::tracked(returns(as_deref))`: Returns the result of `SalsaAsDeref::as_deref`. Requires `SalsaAsDeref`.