├── .devcontainer
└── devcontainer.json
├── .dir-locals.el
├── .github
├── dependabot.yml
└── workflows
│ ├── book.yml
│ └── test.yml
├── .gitignore
├── Cargo.toml
├── FAQ.md
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── RELEASES.md
├── benches
├── compare.rs
└── incremental.rs
├── book
├── .gitignore
├── book.toml
├── mermaid-init.js
├── mermaid.css
├── mermaid.min.js
├── netlify.sh
└── src
│ ├── SUMMARY.md
│ ├── about_salsa.md
│ ├── common_patterns.md
│ ├── common_patterns
│ └── on_demand_inputs.md
│ ├── cycles.md
│ ├── cycles
│ └── fallback.md
│ ├── derived-query-maybe-changed-after.drawio.svg
│ ├── derived-query-read.drawio.svg
│ ├── how_salsa_works.md
│ ├── meta.md
│ ├── overview.md
│ ├── plumbing.md
│ ├── plumbing
│ ├── cycles.md
│ ├── database.md
│ ├── database_and_runtime.md
│ ├── db_lifetime.md
│ ├── derived_flowchart.md
│ ├── diagram.md
│ ├── fetch.md
│ ├── generated_code.md
│ ├── jars_and_ingredients.md
│ ├── maybe_changed_after.md
│ ├── query_groups.md
│ ├── query_ops.md
│ ├── salsa_crate.md
│ ├── terminology.md
│ ├── terminology
│ │ ├── LRU.md
│ │ ├── backdate.md
│ │ ├── changed_at.md
│ │ ├── dependency.md
│ │ ├── derived_query.md
│ │ ├── durability.md
│ │ ├── ingredient.md
│ │ ├── input_query.md
│ │ ├── memo.md
│ │ ├── query.md
│ │ ├── query_function.md
│ │ ├── revision.md
│ │ ├── salsa_item.md
│ │ ├── salsa_struct.md
│ │ ├── untracked.md
│ │ └── verified.md
│ └── tracked_structs.md
│ ├── reference.md
│ ├── reference
│ ├── algorithm.md
│ └── durability.md
│ ├── tuning.md
│ ├── tutorial.md
│ ├── tutorial
│ ├── accumulators.md
│ ├── checker.md
│ ├── db.md
│ ├── debug.md
│ ├── interpreter.md
│ ├── ir.md
│ ├── jar.md
│ ├── parser.md
│ └── structure.md
│ └── videos.md
├── components
├── salsa-macro-rules
│ ├── Cargo.toml
│ └── src
│ │ ├── lib.rs
│ │ ├── macro_if.rs
│ │ ├── maybe_backdate.rs
│ │ ├── maybe_clone.rs
│ │ ├── maybe_default.rs
│ │ ├── setup_accumulator_impl.rs
│ │ ├── setup_input_struct.rs
│ │ ├── setup_interned_struct.rs
│ │ ├── setup_method_body.rs
│ │ ├── setup_tracked_fn.rs
│ │ ├── setup_tracked_struct.rs
│ │ └── unexpected_cycle_recovery.rs
└── salsa-macros
│ ├── Cargo.toml
│ └── src
│ ├── accumulator.rs
│ ├── db.rs
│ ├── db_lifetime.rs
│ ├── debug.rs
│ ├── fn_util.rs
│ ├── hygiene.rs
│ ├── input.rs
│ ├── interned.rs
│ ├── lib.rs
│ ├── options.rs
│ ├── salsa_struct.rs
│ ├── tracked.rs
│ ├── tracked_fn.rs
│ ├── tracked_impl.rs
│ ├── tracked_struct.rs
│ ├── update.rs
│ └── xform.rs
├── examples
├── calc
│ ├── compile.rs
│ ├── db.rs
│ ├── ir.rs
│ ├── main.rs
│ ├── parser.rs
│ └── type_check.rs
└── lazy-input
│ ├── inputs
│ ├── a
│ ├── aa
│ ├── b
│ └── start
│ └── main.rs
├── justfile
├── src
├── accumulator.rs
├── active_query.rs
├── array.rs
├── attach.rs
├── cancelled.rs
├── cycle.rs
├── database.rs
├── database_impl.rs
├── durability.rs
├── event.rs
├── function.rs
├── function
│ ├── accumulated.rs
│ ├── backdate.rs
│ ├── delete.rs
│ ├── diff_outputs.rs
│ ├── execute.rs
│ ├── fetch.rs
│ ├── inputs.rs
│ ├── lru.rs
│ ├── maybe_changed_after.rs
│ ├── memo.rs
│ └── specify.rs
├── hash.rs
├── id.rs
├── ingredient.rs
├── input.rs
├── input
│ ├── input_field.rs
│ └── setter.rs
├── interned.rs
├── key.rs
├── lib.rs
├── nonce.rs
├── revision.rs
├── runtime.rs
├── runtime
│ └── dependency_graph.rs
├── salsa_struct.rs
├── storage.rs
├── table.rs
├── table
│ ├── memo.rs
│ ├── sync.rs
│ └── util.rs
├── tracked_struct.rs
├── tracked_struct
│ └── tracked_field.rs
├── update.rs
├── views.rs
├── zalsa.rs
└── zalsa_local.rs
└── tests
├── accumulate-chain.rs
├── accumulate-custom-clone.rs
├── accumulate-custom-debug.rs
├── accumulate-dag.rs
├── accumulate-execution-order.rs
├── accumulate-from-tracked-fn.rs
├── accumulate-no-duplicates.rs
├── accumulate-reuse-workaround.rs
├── accumulate-reuse.rs
├── accumulate.rs
├── common
└── mod.rs
├── compile-fail
├── accumulator_incompatibles.rs
├── accumulator_incompatibles.stderr
├── get-on-private-interned-field.rs
├── get-on-private-interned-field.stderr
├── get-on-private-tracked-field.rs
├── get-on-private-tracked-field.stderr
├── get-set-on-private-input-field.rs
├── get-set-on-private-input-field.stderr
├── input_struct_incompatibles.rs
├── input_struct_incompatibles.stderr
├── interned_struct_incompatibles.rs
├── interned_struct_incompatibles.stderr
├── lru_can_not_be_used_with_specify.rs
├── lru_can_not_be_used_with_specify.stderr
├── panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs
├── panic-when-reading-fields-of-tracked-structs-from-older-revisions.stderr
├── salsa_fields_incompatibles.rs
├── salsa_fields_incompatibles.stderr
├── singleton_only_for_input.rs
├── singleton_only_for_input.stderr
├── span-input-setter.rs
├── span-input-setter.stderr
├── span-tracked-getter.rs
├── span-tracked-getter.stderr
├── specify-does-not-work-if-the-key-is-a-salsa-input.rs
├── specify-does-not-work-if-the-key-is-a-salsa-input.stderr
├── specify-does-not-work-if-the-key-is-a-salsa-interned.rs
├── specify-does-not-work-if-the-key-is-a-salsa-interned.stderr
├── tracked_fn_incompatibles.rs
├── tracked_fn_incompatibles.stderr
├── tracked_impl_incompatibles.rs
├── tracked_impl_incompatibles.stderr
├── tracked_method_incompatibles.rs
├── tracked_method_incompatibles.stderr
├── tracked_method_on_untracked_impl.rs
├── tracked_method_on_untracked_impl.stderr
├── tracked_struct_incompatibles.rs
└── tracked_struct_incompatibles.stderr
├── compile_fail.rs
├── cycles.rs
├── debug.rs
├── deletion-cascade.rs
├── deletion-drops.rs
├── deletion.rs
├── elided-lifetime-in-tracked-fn.rs
├── expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs
├── expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs
├── hello_world.rs
├── input_default.rs
├── input_field_durability.rs
├── input_setter_preserves_durability.rs
├── interned-struct-with-lifetime.rs
├── is_send_sync.rs
├── lru.rs
├── mutate_in_place.rs
├── override_new_get_set.rs
├── panic-when-creating-tracked-struct-outside-of-tracked-fn.rs
├── parallel
├── main.rs
├── parallel_cancellation.rs
├── parallel_cycle_all_recover.rs
├── parallel_cycle_mid_recover.rs
├── parallel_cycle_none_recover.rs
├── parallel_cycle_one_recover.rs
├── setup.rs
└── signal.rs
├── preverify-struct-with-leaked-data-2.rs
├── preverify-struct-with-leaked-data.rs
├── singleton.rs
├── specify-only-works-if-the-key-is-created-in-the-current-query.rs
├── synthetic_write.rs
├── tracked-struct-id-field-bad-eq.rs
├── tracked-struct-id-field-bad-hash.rs
├── tracked-struct-unchanged-in-new-rev.rs
├── tracked-struct-value-field-bad-eq.rs
├── tracked-struct-value-field-not-eq.rs
├── tracked_fn_constant.rs
├── tracked_fn_high_durability_dependency.rs
├── tracked_fn_no_eq.rs
├── tracked_fn_on_input.rs
├── tracked_fn_on_input_with_high_durability.rs
├── tracked_fn_on_interned.rs
├── tracked_fn_on_tracked.rs
├── tracked_fn_on_tracked_specify.rs
├── tracked_fn_read_own_entity.rs
├── tracked_fn_read_own_specify.rs
├── tracked_fn_return_ref.rs
├── tracked_method.rs
├── tracked_method_inherent_return_ref.rs
├── tracked_method_on_tracked_struct.rs
├── tracked_method_trait_return_ref.rs
├── tracked_struct_db1_lt.rs
├── tracked_struct_durability.rs
├── tracked_with_intern.rs
├── tracked_with_struct_db.rs
└── warnings
├── main.rs
├── needless_borrow.rs
├── needless_lifetimes.rs
└── unused_variable_db.rs
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2 | // README at: https://github.com/devcontainers/templates/tree/main/src/rust
3 | {
4 | "name": "Rust",
5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6 | "image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye",
7 | "features": {
8 | "ghcr.io/devcontainers-contrib/features/ripgrep:1": {}
9 | }
10 |
11 | // Use 'mounts' to make the cargo cache persistent in a Docker Volume.
12 | // "mounts": [
13 | // {
14 | // "source": "devcontainer-cargo-cache-${devcontainerId}",
15 | // "target": "/usr/local/cargo",
16 | // "type": "volume"
17 | // }
18 | // ]
19 |
20 | // Features to add to the dev container. More info: https://containers.dev/features.
21 | // "features": {},
22 |
23 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
24 | // "forwardPorts": [],
25 |
26 | // Use 'postCreateCommand' to run commands after the container is created.
27 | // "postCreateCommand": "rustc --version",
28 |
29 | // Configure tool-specific properties.
30 | // "customizations": {},
31 |
32 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
33 | // "remoteUser": "root"
34 | }
35 |
--------------------------------------------------------------------------------
/.dir-locals.el:
--------------------------------------------------------------------------------
1 | ((rust-mode (rust-format-on-save . t)))
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for more information:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 | # https://containers.dev/guide/dependabot
6 |
7 | version: 2
8 | updates:
9 | - package-ecosystem: "devcontainers"
10 | directory: "/"
11 | schedule:
12 | interval: weekly
13 |
--------------------------------------------------------------------------------
/.github/workflows/book.yml:
--------------------------------------------------------------------------------
1 | name: Book
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | paths:
9 | - "book/**"
10 | - ".github/workflows/book.yml"
11 | merge_group:
12 |
13 | jobs:
14 | book:
15 | name: Book
16 | runs-on: ubuntu-latest
17 | env:
18 | MDBOOK_VERSION: "0.4.40"
19 | MDBOOK_LINKCHECK_VERSION: "0.7.7"
20 | MDBOOK_MERMAID_VERSION: "0.13.0"
21 | steps:
22 | - uses: actions/checkout@v4
23 | - name: Install mdbook
24 | run: |
25 | curl -L https://github.com/rust-lang/mdBook/releases/download/v$MDBOOK_VERSION/mdbook-v$MDBOOK_VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xz -C ~/.cargo/bin
26 | curl -L https://github.com/badboy/mdbook-mermaid/releases/download/v$MDBOOK_MERMAID_VERSION/mdbook-mermaid-v$MDBOOK_MERMAID_VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xz -C ~/.cargo/bin
27 | curl -L https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/download/v$MDBOOK_LINKCHECK_VERSION/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -O
28 | unzip mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -d ~/.cargo/bin
29 | chmod +x ~/.cargo/bin/mdbook-linkcheck
30 | - name: Build
31 | run: mdbook build
32 | working-directory: book
33 | - uses: actions/upload-artifact@v4
34 | with:
35 | name: book
36 | path: book/book/html
37 |
38 | deploy:
39 | name: Deploy
40 | runs-on: ubuntu-latest
41 | needs: book
42 | if: github.event_name == 'push' && github.ref == 'refs/heads/master'
43 | concurrency:
44 | group: github-pages
45 | cancel-in-progress: true
46 | permissions:
47 | contents: read
48 | pages: write
49 | id-token: write
50 | steps:
51 | - uses: actions/download-artifact@v4
52 | with:
53 | name: book
54 | - uses: actions/configure-pages@v5
55 | - uses: actions/upload-pages-artifact@v3
56 | with:
57 | path: .
58 | - uses: actions/deploy-pages@v4
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | **/*.rs.bk
3 | Cargo.lock
4 | TAGS
5 | nikom
6 | .idea
7 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "salsa"
3 | version = "0.18.0"
4 | authors = ["Salsa developers"]
5 | edition = "2021"
6 | license = "Apache-2.0 OR MIT"
7 | repository = "https://github.com/salsa-rs/salsa"
8 | description = "A generic framework for on-demand, incrementalized computation (experimental)"
9 |
10 | [dependencies]
11 | arc-swap = "1"
12 | crossbeam = "0.8"
13 | dashmap = "6"
14 | hashlink = "0.9"
15 | indexmap = "2"
16 | append-only-vec = "0.1.5"
17 | tracing = "0.1"
18 | parking_lot = "0.12"
19 | rustc-hash = "2"
20 | salsa-macro-rules = { version = "0.1.0", path = "components/salsa-macro-rules" }
21 | salsa-macros = { path = "components/salsa-macros" }
22 | smallvec = "1"
23 | lazy_static = "1"
24 |
25 | [dev-dependencies]
26 | annotate-snippets = "0.11.4"
27 | derive-new = "0.6.0"
28 | codspeed-criterion-compat = { version = "2.6.0", default-features = false }
29 | expect-test = "1.4.0"
30 | eyre = "0.6.8"
31 | notify-debouncer-mini = "0.4.1"
32 | ordered-float = "4.2.1"
33 | rustversion = "1.0"
34 | test-log = { version ="0.2.11", features = ["trace"] }
35 | trybuild = "1.0"
36 |
37 | [[bench]]
38 | name = "compare"
39 | harness = false
40 |
41 |
42 | [[bench]]
43 | name = "incremental"
44 | harness = false
45 |
46 | [workspace]
47 | members = ["components/salsa-macro-rules", "components/salsa-macros"]
48 |
--------------------------------------------------------------------------------
/FAQ.md:
--------------------------------------------------------------------------------
1 | # Frequently asked questions
2 |
3 | ## Why is it called salsa?
4 |
5 | I like salsa! Don't you?! Well, ok, there's a bit more to it. The
6 | underlying algorithm for figuring out which bits of code need to be
7 | re-executed after any given change is based on the algorithm used in
8 | rustc. Michael Woerister and I first described the rustc algorithm in
9 | terms of two colors, red and green, and hence we called it the
10 | "red-green algorithm". This made me think of the New Mexico State
11 | Question --- ["Red or green?"][nm] --- which refers to chile
12 | (salsa). Although this version no longer uses colors (we borrowed
13 | revision counters from Glimmer, instead), I still like the name.
14 |
15 | [nm]: https://www.sos.state.nm.us/about-new-mexico/state-question/
16 |
17 | ## What is the relationship between salsa and an Entity-Component System (ECS)?
18 |
19 | You may have noticed that Salsa "feels" a lot like an ECS in some
20 | ways. That's true -- Salsa's queries are a bit like *components* (and
21 | the keys to the queries are a bit like *entities*). But there is one
22 | big difference: **ECS is -- at its heart -- a mutable system**. You
23 | can get or set a component of some entity whenever you like. In
24 | contrast, salsa's queries **define "derived values" via pure
25 | computations**.
26 |
27 | Partly as a consequence, ECS doesn't handle incremental updates for
28 | you. When you update some component of some entity, you have to ensure
29 | that other entities' components are updated appropriately.
30 |
31 | Finally, ECS offers interesting metadata and "aspect-like" facilities,
32 | such as iterating over all entities that share certain components.
33 | Salsa has no analogue to that.
34 |
35 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any
2 | person obtaining a copy of this software and associated
3 | documentation files (the "Software"), to deal in the
4 | Software without restriction, including without
5 | limitation the rights to use, copy, modify, merge,
6 | publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software
8 | is furnished to do so, subject to the following
9 | conditions:
10 |
11 | The above copyright notice and this permission notice
12 | shall be included in all copies or substantial portions
13 | of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # salsa
2 |
3 | [](https://github.com/salsa-rs/salsa/actions?query=workflow%3ATest)
4 | [](https://github.com/salsa-rs/salsa/actions?query=workflow%3ABook)
5 | [](https://docs.rs/salsa)
6 | [](https://crates.io/crates/salsa)
7 |
8 | *A generic framework for on-demand, incrementalized computation.*
9 |
10 |
11 |
12 | ## Obligatory warning
13 |
14 | Very much a WORK IN PROGRESS at this point. Ready for experimental use
15 | but expect frequent breaking changes.
16 |
17 | ## Credits
18 |
19 | This system is heavily inspired by [adapton](http://adapton.org/), [glimmer](https://github.com/glimmerjs/glimmer-vm), and rustc's query
20 | system. So credit goes to Eduard-Mihai Burtescu, Matthew Hammer,
21 | Yehuda Katz, and Michael Woerister.
22 |
23 | ## Key idea
24 |
25 | The key idea of `salsa` is that you define your program as a set of
26 | **queries**. Every query is used like function `K -> V` that maps from
27 | some key of type `K` to a value of type `V`. Queries come in two basic
28 | varieties:
29 |
30 | - **Inputs**: the base inputs to your system. You can change these
31 | whenever you like.
32 | - **Functions**: pure functions (no side effects) that transform your
33 | inputs into other values. The results of queries are memoized to
34 | avoid recomputing them a lot. When you make changes to the inputs,
35 | we'll figure out (fairly intelligently) when we can re-use these
36 | memoized values and when we have to recompute them.
37 |
38 | ## Want to learn more?
39 |
40 | To learn more about Salsa, try one of the following:
41 |
42 | - read the [heavily commented `hello_world` example](https://github.com/salsa-rs/salsa/blob/master/examples/hello_world/main.rs);
43 | - check out the [Salsa book](https://salsa-rs.github.io/salsa);
44 | - [中文版](https://rust-chinese-translation.github.io/salsa-book)
45 | - watch one of our [videos](https://salsa-rs.github.io/salsa/videos.html).
46 |
47 | ## Getting in touch
48 |
49 | The bulk of the discussion happens in the [issues](https://github.com/salsa-rs/salsa/issues)
50 | and [pull requests](https://github.com/salsa-rs/salsa/pulls),
51 | but we have a [zulip chat](https://salsa.zulipchat.com/) as well.
52 |
53 |
--------------------------------------------------------------------------------
/RELEASES.md:
--------------------------------------------------------------------------------
1 | # 0.13.0
2 |
3 | - **Breaking change:** adopt the new `Durability` API proposed in [RFC #6]
4 | - this replaces and generalizes the existing concepts of constants
5 | - **Breaking change:** remove "volatile" queries
6 | - instead, create a normal query which invokes the
7 | `report_untracked_read` method on the salsa runtime
8 | - introduce "slots", an optimization to salsa's internal workings
9 | - document `#[salsa::requires]` attribute, which permits private dependencies
10 | - Adopt `AtomicU64` for `runtimeId` (#182)
11 | - use `ptr::eq` and `ptr::hash` for readability
12 | - upgrade parking lot, rand dependencies
13 |
14 | [RFC #6]: https://github.com/salsa-rs/salsa-rfcs/pull/6
15 |
--------------------------------------------------------------------------------
/benches/incremental.rs:
--------------------------------------------------------------------------------
1 | use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Criterion};
2 | use salsa::Setter;
3 |
4 | #[salsa::input]
5 | struct Input {
6 | field: usize,
7 | }
8 |
9 | #[salsa::tracked]
10 | struct Tracked<'db> {
11 | number: usize,
12 | }
13 |
14 | #[salsa::tracked(return_ref)]
15 | fn index<'db>(db: &'db dyn salsa::Database, input: Input) -> Vec> {
16 | (0..input.field(db)).map(|i| Tracked::new(db, i)).collect()
17 | }
18 |
19 | #[salsa::tracked]
20 | fn root(db: &dyn salsa::Database, input: Input) -> usize {
21 | let index = index(db, input);
22 | index.len()
23 | }
24 |
25 | fn many_tracked_structs(criterion: &mut Criterion) {
26 | criterion.bench_function("many_tracked_structs", |b| {
27 | b.iter_batched_ref(
28 | || {
29 | let db = salsa::DatabaseImpl::new();
30 |
31 | let input = Input::new(&db, 1_000);
32 | let input2 = Input::new(&db, 1);
33 |
34 | // prewarm cache
35 | let _ = root(&db, input);
36 | let _ = root(&db, input2);
37 |
38 | (db, input, input2)
39 | },
40 | |(db, input, input2)| {
41 | // Make a change, but fetch the result for the other input
42 | input2.set_field(db).to(2);
43 |
44 | let result = root(db, *input);
45 |
46 | assert_eq!(result, 1_000);
47 | },
48 | BatchSize::LargeInput,
49 | );
50 | });
51 | }
52 |
53 | criterion_group!(benches, many_tracked_structs);
54 | criterion_main!(benches);
55 |
--------------------------------------------------------------------------------
/book/.gitignore:
--------------------------------------------------------------------------------
1 | book
2 |
--------------------------------------------------------------------------------
/book/book.toml:
--------------------------------------------------------------------------------
1 | [book]
2 | authors = ["Salsa Contributors"]
3 | multilingual = false
4 | src = "src"
5 | title = "Salsa"
6 |
7 | [build]
8 | create-missing = false
9 |
10 | [preprocess.links]
11 |
12 | [output.html]
13 | additional-css =["mermaid.css"]
14 | additional-js =["mermaid.min.js", "mermaid-init.js"]
15 |
16 | [output.linkcheck]
17 | # follow-web-links = true --- this is commented out b/c of false errors
18 | traverse-parent-directories = false
19 | exclude = ['bilibili\.com']
20 | [preprocessor]
21 | [preprocessor.mermaid]
22 | command = "mdbook-mermaid"
23 |
--------------------------------------------------------------------------------
/book/mermaid-init.js:
--------------------------------------------------------------------------------
1 | mermaid.initialize({startOnLoad:true});
2 |
--------------------------------------------------------------------------------
/book/netlify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Script meant to be run from netlify
4 |
5 | set -x
6 |
7 | MDBOOK_VERSION='0.4.12'
8 | MDBOOK_LINKCHECK_VERSION='0.7.4'
9 | MDBOOK_MERMAID_VERSION='0.8.3'
10 |
11 | curl -L https://github.com/rust-lang/mdBook/releases/download/v$MDBOOK_VERSION/mdbook-v$MDBOOK_VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xz -C ~/.cargo/bin
12 | curl -L https://github.com/badboy/mdbook-mermaid/releases/download/v$MDBOOK_MERMAID_VERSION/mdbook-mermaid-v$MDBOOK_MERMAID_VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xz -C ~/.cargo/bin
13 | curl -L https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/download/v$MDBOOK_LINKCHECK_VERSION/mdbook-linkcheck.v$MDBOOK_LINKCHECK_VERSION.x86_64-unknown-linux-gnu.zip -O
14 | unzip mdbook-linkcheck.v$MDBOOK_LINKCHECK_VERSION.x86_64-unknown-linux-gnu.zip -d ~/.cargo/bin
15 | chmod +x ~/.cargo/bin/mdbook-linkcheck
16 |
17 | mdbook build
18 | mkdir versions
19 | mv book/html/* versions
20 |
--------------------------------------------------------------------------------
/book/src/about_salsa.md:
--------------------------------------------------------------------------------
1 | # About salsa
2 |
3 | Salsa is a Rust framework for writing incremental, on-demand programs
4 | -- these are programs that want to adapt to changes in their inputs,
5 | continuously producing a new output that is up-to-date. Salsa is based
6 | on the the incremental recompilation techniques that we built for
7 | rustc, and many (but not all) of its users are building compilers or
8 | other similar tooling.
9 |
10 | If you'd like to learn more about Salsa, check out:
11 |
12 | - The [overview](./overview.md), for a brief summary.
13 | - The [tutorial](./tutorial.md), for a detailed look.
14 | - You can also watch some of our [videos](./videos.md), though the content there is rather out of date.
15 |
16 | If you'd like to chat about Salsa, or you think you might like to
17 | contribute, please jump on to our Zulip instance at
18 | [salsa.zulipchat.com](https://salsa.zulipchat.com/).
19 |
--------------------------------------------------------------------------------
/book/src/common_patterns.md:
--------------------------------------------------------------------------------
1 | # Common patterns
2 |
3 | This section documents patterns for using Salsa.
4 |
--------------------------------------------------------------------------------
/book/src/common_patterns/on_demand_inputs.md:
--------------------------------------------------------------------------------
1 | # On-Demand (Lazy) Inputs
2 |
3 | Salsa inputs work best if you can easily provide all of the inputs upfront.
4 | However sometimes the set of inputs is not known beforehand.
5 |
6 | A typical example is reading files from disk.
7 | While it is possible to eagerly scan a particular directory and create an in-memory file tree as salsa input structs, a more straight-forward approach is to read the files lazily.
8 | That is, when a query requests the text of a file for the first time:
9 |
10 | 1. Read the file from disk and cache it.
11 | 2. Setup a file-system watcher for this path.
12 | 3. Update the cached file when the watcher sends a change notification.
13 |
14 | This is possible to achieve in salsa, by caching the inputs in your database structs and adding a method to the database trait to retrieve them out of this cache.
15 |
16 | A complete, runnable file-watching example can be found in [the lazy-input example](https://github.com/salsa-rs/salsa/tree/master/examples/lazy-input).
17 |
18 | The setup looks roughly like this:
19 |
20 | ```rust,ignore
21 | {{#include ../../../examples/lazy-input/main.rs:db}}
22 | ```
23 |
24 | - We declare a method on the `Db` trait that gives us a `File` input on-demand (it only requires a `&dyn Db` not a `&mut dyn Db`).
25 | - There should only be one input struct per file, so we implement that method using a cache (`DashMap` is like a `RwLock`).
26 |
27 | The driving code that's doing the top-level queries is then in charge of updating the file contents when a file-change notification arrives.
28 | It does this by updating the Salsa input in the same way that you would update any other input.
29 |
30 | Here we implement a simple driving loop, that recompiles the code whenever a file changes.
31 | You can use the logs to check that only the queries that could have changed are re-evaluated.
32 |
33 | ```rust,ignore
34 | {{#include ../../../examples/lazy-input/main.rs:main}}
35 | ```
36 |
--------------------------------------------------------------------------------
/book/src/cycles.md:
--------------------------------------------------------------------------------
1 | # Cycle handling
2 |
3 | By default, when Salsa detects a cycle in the computation graph, Salsa will panic with a [`salsa::Cycle`] as the panic value. The [`salsa::Cycle`] structure that describes the cycle, which can be useful for diagnosing what went wrong.
4 |
5 | [`salsa::cycle`]: https://github.com/salsa-rs/salsa/blob/0f9971ad94d5d137f1192fde2b02ccf1d2aca28c/src/lib.rs#L654-L672
6 |
--------------------------------------------------------------------------------
/book/src/cycles/fallback.md:
--------------------------------------------------------------------------------
1 | # Recovering via fallback
2 |
3 | Panicking when a cycle occurs is ok for situations where you believe a cycle is impossible. But sometimes cycles can result from illegal user input and cannot be statically prevented. In these cases, you might prefer to gracefully recover from a cycle rather than panicking the entire query. Salsa supports that with the idea of *cycle recovery*.
4 |
5 | To use cycle recovery, you annotate potential participants in the cycle with the `recovery_fn` argument to `#[salsa::tracked]`, e.g. `#[salsa::tracked(recovery_fn=my_recovery_fn)]`. When a cycle occurs, if any participant P has recovery information, then no panic occurs. Instead, the execution of P is aborted and P will execute the recovery function to generate its result. Participants in the cycle that do not have recovery information continue executing as normal, using this recovery result.
6 |
7 | The recovery function has a similar signature to a query function. It is given a reference to your database along with a `salsa::Cycle` describing the cycle that occurred and the arguments to the tracked function that caused the cycle; it returns the result of the query. Example:
8 |
9 | ```rust
10 | fn my_recover_fn(
11 | db: &dyn MyDatabase,
12 | cycle: &salsa::Cycle,
13 | arg1: T1,
14 | ...
15 | argN: TN,
16 | ) -> MyResultValue
17 | ```
18 |
19 | See [the tests](https://github.com/salsa-rs/salsa/blob/cd339fc1c9a6ea0ffb1d09bd3bffb5633f776ef3/tests/cycles.rs#L132-L141) for an example.
20 |
21 | **Important:** Although the recovery function is given a `db` handle, you should be careful to avoid creating a cycle from within recovery or invoking queries that may be participating in the current cycle. Attempting to do so can result in inconsistent results.
22 |
--------------------------------------------------------------------------------
/book/src/how_salsa_works.md:
--------------------------------------------------------------------------------
1 | # How Salsa works
2 |
3 | ## Video available
4 |
5 | To get the most complete introduction to Salsa's inner workings, check
6 | out [the "How Salsa Works" video](https://youtu.be/_muY4HjSqVw). If
7 | you'd like a deeper dive, [the "Salsa in more depth"
8 | video](https://www.youtube.com/watch?v=i_IhACacPRY) digs into the
9 | details of the incremental algorithm.
10 |
11 | > If you're in China, watch videos on ["How Salsa Works"](https://www.bilibili.com/video/BV1Df4y1A7t3/), ["Salsa In More Depth"](https://www.bilibili.com/video/BV1AM4y1G7E4/).
12 |
13 | ## Key idea
14 |
15 | The key idea of `salsa` is that you define your program as a set of
16 | **queries**. Every query is used like a function `K -> V` that maps from
17 | some key of type `K` to a value of type `V`. Queries come in two basic
18 | varieties:
19 |
20 | - **Inputs**: the base inputs to your system. You can change these
21 | whenever you like.
22 | - **Functions**: pure functions (no side effects) that transform your
23 | inputs into other values. The results of queries are memoized to
24 | avoid recomputing them a lot. When you make changes to the inputs,
25 | we'll figure out (fairly intelligently) when we can re-use these
26 | memoized values and when we have to recompute them.
27 |
28 | ## How to use Salsa in three easy steps
29 |
30 | Using Salsa is as easy as 1, 2, 3...
31 |
32 | 1. Define one or more **query groups** that contain the inputs
33 | and queries you will need. We'll start with one such group, but
34 | later on you can use more than one to break up your system into
35 | components (or spread your code across crates).
36 | 2. Define the **query functions** where appropriate.
37 | 3. Define the **database**, which contains the storage for all
38 | the inputs/queries you will be using. The query struct will contain
39 | the storage for all of the inputs/queries and may also contain
40 | anything else that your code needs (e.g., configuration data).
41 |
42 | To see an example of this in action, check out [the `hello_world`
43 | example][hello_world], which has a number of comments explaining how
44 | things work.
45 |
46 | [hello_world]: https://github.com/salsa-rs/salsa/blob/master/examples/hello_world/main.rs
47 |
48 | ## Digging into the plumbing
49 |
50 | Check out the [plumbing](plumbing.md) chapter to see a deeper explanation of the
51 | code that Salsa generates and how it connects to the Salsa library.
52 |
--------------------------------------------------------------------------------
/book/src/meta.md:
--------------------------------------------------------------------------------
1 | # Meta: about the book itself
2 |
3 | ## Linking policy
4 |
5 | We try to avoid links that easily become fragile.
6 |
7 | **Do:**
8 |
9 | * Link to `docs.rs` types to document the public API, but modify the link to use `latest` as the version.
10 | * Link to modules in the source code.
11 | * Create ["named anchors"] and embed source code directly.
12 |
13 | ["named anchors"]: https://rust-lang.github.io/mdBook/format/mdbook.html?highlight=ANCHOR#including-portions-of-a-file
14 |
15 | **Don't:**
16 |
17 | * Link to direct lines on github, even within a specific commit, unless you are trying to reference a historical piece of code ("how things were at the time").
--------------------------------------------------------------------------------
/book/src/plumbing.md:
--------------------------------------------------------------------------------
1 | # Plumbing
2 |
3 | This chapter documents the code that salsa generates and its "inner workings".
4 | We refer to this as the "plumbing".
5 |
6 | ## Overview
7 |
8 | The plumbing section is broken up into chapters:
9 |
10 | - The [jars and ingredients](./plumbing/jars_and_ingredients.md) covers how each salsa item (like a tracked function) specifies what data it needs and runtime, and how links between items work.
11 | - The [database and runtime](./plumbing/database_and_runtime.md) covers the data structures that are used at runtime to coordinate workers, trigger cancellation, track which functions are active and what dependencies they have accrued, and so forth.
12 | - The [query operations](./plumbing/query_ops.md) chapter describes how the major operations on function ingredients work. This text was written for an older version of salsa but the logic is the same:
13 | - The [maybe changed after](./plumbing/maybe_changed_after.md) operation determines when a memoized value for a tracked function is out of date.
14 | - The [fetch](./plumbing/fetch.md) operation computes the most recent value.
15 | - The [derived queries flowchart](./plumbing/derived_flowchart.md) depicts the logic in flowchart form.
16 | - The [cycle handling](./plumbing/cycles.md) handling chapter describes what happens when cycles occur.
17 | - The [terminology](./plumbing/terminology.md) section describes various words that appear throughout.
18 |
--------------------------------------------------------------------------------
/book/src/plumbing/derived_flowchart.md:
--------------------------------------------------------------------------------
1 | # Derived queries flowchart
2 |
3 | Derived queries are by far the most complex. This flowchart documents the flow of the [maybe changed after] and [fetch] operations. This flowchart can be edited on [draw.io]:
4 |
5 | [draw.io]: https://draw.io
6 | [fetch]: ./fetch.md
7 | [maybe changed after]: ./maybe_changed_after.md
8 |
9 |
10 |
15 |
--------------------------------------------------------------------------------
/book/src/plumbing/generated_code.md:
--------------------------------------------------------------------------------
1 | # Generated code
2 |
3 | This page walks through the ["Hello, World!"] example and explains the code that
4 | it generates. Please take it with a grain of salt: while we make an effort to
5 | keep this documentation up to date, this sort of thing can fall out of date
6 | easily. See the page history below for major updates.
7 |
8 | ["Hello, World!"]: https://github.com/salsa-rs/salsa/blob/master/examples/hello_world/main.rs
9 |
10 | If you'd like to see for yourself, you can set the environment variable
11 | `SALSA_DUMP` to 1 while the procedural macro runs, and it will dump the full
12 | output to stdout. I recommend piping the output through rustfmt.
13 |
14 | ## Sources
15 |
16 | The main parts of the source that we are focused on are as follows.
17 |
18 | ### Query group
19 |
20 | ```rust,ignore
21 | {{#include ../../../examples/hello_world/main.rs:trait}}
22 | ```
23 |
24 | ### Database
25 |
26 | ```rust,ignore
27 | {{#include ../../../examples/hello_world/main.rs:database}}
28 | ```
29 |
--------------------------------------------------------------------------------
/book/src/plumbing/query_ops.md:
--------------------------------------------------------------------------------
1 | # Query operations
2 |
3 | Each of the query storage struct implements the `QueryStorageOps` trait found in the [`plumbing`] module:
4 |
5 | ```rust,no_run,noplayground
6 | {{#include ../../../src/plumbing.rs:QueryStorageOps}}
7 | ```
8 |
9 | which defines the basic operations that all queries support. The most important are these two:
10 |
11 | * [maybe changed after](./maybe_changed_after.md): Returns true if the value of the query (for the given key) may have changed since the given revision.
12 | * [Fetch](./fetch.md): Returns the up-to-date value for the given K (or an error in the case of an "unrecovered" cycle).
13 |
14 | [`plumbing`]: https://github.com/salsa-rs/salsa/blob/master/src/plumbing.rs
15 |
--------------------------------------------------------------------------------
/book/src/plumbing/salsa_crate.md:
--------------------------------------------------------------------------------
1 | # Runtime
2 |
3 | This section documents the contents of the salsa crate. The salsa crate contains code that interacts with the [generated code] to create the complete "salsa experience".
4 |
5 | [generated code]: ./generated_code.md
6 |
7 | ## Major types
8 |
9 | The crate has a few major types.
10 |
11 | ### The [`salsa::Storage`] struct
12 |
13 | The [`salsa::Storage`] struct is what users embed into their database. It consists of two main parts:
14 |
15 | * The "query store", which is the [generated storage struct](./database.md#the-database-storage-struct).
16 | * The [`salsa::Runtime`].
17 |
18 | ### The [`salsa::Runtime`] struct
19 |
20 | The [`salsa::Runtime`] struct stores the data that is used to track which queries are being executed and to coordinate between them. The `Runtime` is embedded within the [`salsa::Storage`] struct.
21 |
22 | **Important**. The `Runtime` does **not** store the actual data from the queries; they live alongside it in the [`salsa::Storage`] struct. This ensures that the type of `Runtime` is not generic which is needed to ensure dyn safety.
23 |
24 | #### Threading
25 |
26 | There is one [`salsa::Runtime`] for each active thread, and each of them has a unique [`RuntimeId`]. The `Runtime` state itself is divided into;
27 |
28 | * `SharedState`, accessible from all runtimes;
29 | * `LocalState`, accessible only from this runtime.
30 |
31 | [`salsa::Runtime`]: https://docs.rs/salsa/latest/salsa/struct.Runtime.html
32 | [`salsa::Storage`]: https://docs.rs/salsa/latest/salsa/struct.Storage.html
33 | [`RuntimeId`]: https://docs.rs/salsa/0.16.1/salsa/struct.RuntimeId.html
34 |
35 | ### Query storage implementations and support code
36 |
37 | For each kind of query (input, derived, interned, etc) there is a corresponding "storage struct" that contains the code to implement it. For example, derived queries are implemented by the `DerivedStorage` struct found in the [`salsa::derived`] module.
38 |
39 | [`salsa::derived`]: https://github.com/salsa-rs/salsa/blob/master/src/derived.rs
40 |
41 | Storage structs like `DerivedStorage` are generic over a query type `Q`, which corresponds to the [query structs] in the generated code. The query structs implement the `Query` trait which gives basic info such as the key and value type of the query and its ability to recover from cycles. In some cases, the `Q` type is expected to implement additional traits: derived queries, for example, implement `QueryFunction`, which defines the code that will execute when the query is called.
42 |
43 | [query structs]: ./query_groups.md#for-each-query-a-query-struct
44 |
45 | The storage structs, in turn, implement key traits from the plumbing module. The most notable is the `QueryStorageOps`, which defines the [basic operations that can be done on a query](./query_ops.md).
46 |
--------------------------------------------------------------------------------
/book/src/plumbing/terminology.md:
--------------------------------------------------------------------------------
1 | # Terminology
2 |
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/LRU.md:
--------------------------------------------------------------------------------
1 | # LRU
2 |
3 | The [`set_lru_capacity`](https://docs.rs/salsa/0.16.1/salsa/struct.QueryTableMut.html#method.set_lru_capacity) method can be used to fix the maximum capacity for a query at a specific number of values. If more values are added after that point, then salsa will drop the values from older [memos] to conserve memory (we always retain the [dependency] information for those memos, however, so that we can still compute whether values may have changed, even if we don't know what that value is).
4 |
5 | [memos]: ./memo.md
6 | [dependency]: ./dependency.md
7 |
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/backdate.md:
--------------------------------------------------------------------------------
1 | # Backdate
2 |
3 | *Backdating* is when we mark a value that was computed in revision R as having last changed in some earlier revision. This is done when we have an older [memo] M and we can compare the two values to see that, while the [dependencies] to M may have changed, the result of the [query function] did not.
4 |
5 | [memo]: ./memo.md
6 | [dependencies]: ./dependency.md
7 | [query function]: ./query_function.md
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/changed_at.md:
--------------------------------------------------------------------------------
1 | # Changed at
2 |
3 | The *changed at* revision for a [memo] is the [revision] in which that memo's value last changed. Typically, this is the same as the revision in which the [query function] was last executed, but it may be an earlier revision if the memo was [backdated].
4 |
5 | [query function]: ./query_function.md
6 | [backdated]: ./backdate.md
7 | [revision]: ./revision.md
8 | [memo]: ./memo.md
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/dependency.md:
--------------------------------------------------------------------------------
1 | # Dependency
2 |
3 | A *dependency* of a [query] Q is some other query Q1 that was invoked as part of computing the value for Q (typically, invoking by Q's [query function]).
4 |
5 | [query]: ./query.md
6 | [query function]: ./query_function.md
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/derived_query.md:
--------------------------------------------------------------------------------
1 | # Derived query
2 |
3 | A *derived query* is a [query] whose value is defined by the result of a user-provided [query function]. That function is executed to get the result of the query. Unlike [input queries], the result of a derived queries can always be recomputed whenever needed simply by re-executing the function.
4 |
5 | [query]: ./query.md
6 | [query function]: ./query_function.md
7 | [input queries]: ./input_query.md
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/durability.md:
--------------------------------------------------------------------------------
1 | # Durability
2 |
3 | *Durability* is an optimization that we use to avoid checking the [dependencies] of a [query] individually.
4 |
5 | [dependencies]: ./dependency.md
6 | [query]: ./query.md
7 |
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/ingredient.md:
--------------------------------------------------------------------------------
1 | # Ingredient
2 |
3 | An *ingredient* is an individual piece of storage used to create a [salsa item](./salsa_item.md)
4 | See the [jars and ingredients](../jars_and_ingredients.md) chapter for more details.
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/input_query.md:
--------------------------------------------------------------------------------
1 | # Input query
2 |
3 | An *input query* is a [query] whose value is explicitly set by the user. When that value is set, a [durability] can also be provided.
4 |
5 | [query]: ./query.md
6 | [durability]: ./durability.md
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/memo.md:
--------------------------------------------------------------------------------
1 | # Memo
2 |
3 | A *memo* stores information about the last time that a [query function] for some [query] Q was executed:
4 |
5 | * Typically, it contains the value that was returned from that function, so that we don't have to execute it again.
6 | * However, this is not always true: some queries don't cache their result values, and values can also be dropped as a result of [LRU] collection. In those cases, the memo just stores [dependency] information, which can still be useful to determine if other queries that have Q as a [dependency] may have changed.
7 | * The revision in which the memo last [verified].
8 | * The [changed at] revision in which the memo's value last changed. (Note that it may be [backdated].)
9 | * The minimum durability of the memo's [dependencies].
10 | * The complete set of [dependencies], if available, or a marker that the memo has an [untracked dependency].
11 |
12 | [revision]: ./revision.md
13 | [backdated]: ./backdate.md
14 | [dependencies]: ./dependency.md
15 | [dependency]: ./dependency.md
16 | [untracked dependency]: ./untracked.md
17 | [verified]: ./verified.md
18 | [query]: ./query.md
19 | [query function]: ./query_function.md
20 | [changed at]: ./changed_at.md
21 | [LRU]: ./LRU.md
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/query.md:
--------------------------------------------------------------------------------
1 | # Query
2 |
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/query_function.md:
--------------------------------------------------------------------------------
1 | # Query function
2 |
3 | The *query function* is the user-provided function that we execute to compute the value of a [derived query]. Salsa assumed that all query functions are a 'pure' function of their [dependencies] unless the user reports an [untracked read]. Salsa always assumes that functions have no important side-effects (i.e., that they don't send messages over the network whose results you wish to observe) and thus that it doesn't have to re-execute functions unless it needs their return value.
4 |
5 | [derived query]: ./derived_query.md
6 | [dependencies]: ./dependency.md
7 | [untracked read]: ./untracked.md
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/revision.md:
--------------------------------------------------------------------------------
1 | # Revision
2 |
3 | A *revision* is a monotonically increasing integer that we use to track the "version" of the database. Each time the value of an [input query] is modified, we create a new revision.
4 |
5 | [input query]: ./input_query.md
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/salsa_item.md:
--------------------------------------------------------------------------------
1 | # Salsa item
2 |
3 | A salsa item is something that is decorated with a `#[salsa::foo]` macro, like a tracked function or struct.
4 | See the [jars and ingredients](../jars_and_ingredients.md) chapter for more details.
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/salsa_struct.md:
--------------------------------------------------------------------------------
1 | # Salsa struct
2 |
3 | A salsa struct is a struct decorated with one of the salsa macros:
4 |
5 | * `#[salsa::tracked]`
6 | * `#[salsa::input]`
7 | * `#[salsa::interned]`
8 |
9 | See the [salsa overview](../../overview.md) for more details.
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/untracked.md:
--------------------------------------------------------------------------------
1 | # Untracked dependency
2 |
3 | An *untracked dependency* is an indication that the result of a [derived query] depends on something not visible to the salsa database. Untracked dependencies are created by invoking [`report_untracked_read`](https://docs.rs/salsa/0.16.1/salsa/struct.Runtime.html#method.report_untracked_read) or [`report_synthetic_read`](https://docs.rs/salsa/0.16.1/salsa/struct.Runtime.html#method.report_synthetic_read). When an untracked dependency is present, [derived queries] are always re-executed if the durability check fails (see the description of the [fetch operation] for more details).
4 |
5 | [derived query]: ./derived_query.md
6 | [derived queries]: ./derived_query.md
7 | [fetch operation]: ../fetch.md#derived-queries
8 |
--------------------------------------------------------------------------------
/book/src/plumbing/terminology/verified.md:
--------------------------------------------------------------------------------
1 | # Verified
2 |
3 | A [memo] is *verified* in a revision R if we have checked that its value is still up-to-date (i.e., if we were to reexecute the [query function], we are guaranteed to get the same result). Each memo tracks the revision in which it was last verified to avoid repeatedly checking whether dependencies have changed during the [fetch] and [maybe changed after] operations.
4 |
5 | [query function]: ./query_function.md
6 | [fetch]: ../fetch.md
7 | [maybe changed after]: ../maybe_changed_after.md
8 | [memo]: ./memo.md
--------------------------------------------------------------------------------
/book/src/plumbing/tracked_structs.md:
--------------------------------------------------------------------------------
1 | # Tracked structs
2 |
3 | Tracked structs are stored in a special way to reduce their costs.
4 |
5 | Tracked structs are created via a `new` operation.
6 |
7 | ## The tracked struct and tracked field ingredients
8 |
9 | For a single tracked struct we create multiple ingredients.
10 | The **tracked struct ingredient** is the ingredient created first.
11 | It offers methods to create new instances of the struct and therefore
12 | has unique access to the interner and hashtables used to create the struct id.
13 | It also shares access to a hashtable that stores the `ValueStruct` that
14 | contains the field data.
15 |
16 | For each field, we create a **tracked field ingredient** that moderates access
17 | to a particular field. All of these ingredients use that same shared hashtable
18 | to access the `ValueStruct` instance for a given id. The `ValueStruct`
19 | contains both the field values but also the revisions when they last changed value.
20 |
21 | ## Each tracked struct has a globally unique id
22 |
23 | This will begin by creating a _globally unique, 32-bit id_ for the tracked struct. It is created by interning a combination of
24 |
25 | - the currently executing query;
26 | - a u64 hash of the `#[id]` fields;
27 | - a _disambiguator_ that makes this hash unique within the current query. i.e., when a query starts executing, it creates an empty map, and the first time a tracked struct with a given hash is created, it gets disambiguator 0. The next one will be given 1, etc.
28 |
29 | ## Each tracked struct has a `ValueStruct` storing its data
30 |
31 | The struct and field ingredients share access to a hashmap that maps
32 | each field id to a value struct:
33 |
34 | ```rust,ignore
35 | {{#include ../../../src/tracked_struct.rs:ValueStruct}}
36 | ```
37 |
38 | The value struct stores the values of the fields but also the revisions when
39 | that field last changed. Each time the struct is recreated in a new revision,
40 | the old and new values for its fields are compared and a new revision is created.
41 |
42 | ## The macro generates the tracked struct `Configuration`
43 |
44 | The "configuration" for a tracked struct defines not only the types of the fields,
45 | but also various important operations such as extracting the hashable id fields
46 | and updating the "revisions" to track when a field last changed:
47 |
48 | ```rust,ignore
49 | {{#include ../../../src/tracked_struct.rs:Configuration}}
50 | ```
51 |
--------------------------------------------------------------------------------
/book/src/reference.md:
--------------------------------------------------------------------------------
1 | # Reference
2 |
--------------------------------------------------------------------------------
/book/src/reference/durability.md:
--------------------------------------------------------------------------------
1 | # Durability
2 |
3 | "Durability" is an optimization that can greatly improve the performance of your salsa programs.
4 | Durability specifies the probability that an input's value will change.
5 | The default is "low durability".
6 | But when you set the value of an input, you can manually specify a higher durability,
7 | typically `Durability::HIGH`.
8 | Salsa tracks when tracked functions only consume values of high durability
9 | and, if no high durability input has changed, it can skip traversing their
10 | dependencies.
11 |
12 | Typically "high durability" values are things like data read from the standard library
13 | or other inputs that aren't actively being edited by the end user.
--------------------------------------------------------------------------------
/book/src/tuning.md:
--------------------------------------------------------------------------------
1 | # Tuning Salsa
2 |
3 | ## LRU Cache
4 |
5 | You can specify an LRU cache size for any non-input query:
6 |
7 | ```rs
8 | let lru_capacity: usize = 128;
9 | base_db::ParseQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
10 | ```
11 |
12 | The default is `0`, which disables LRU-caching entirely.
13 |
14 | Note that there is no garbage collection for keys and
15 | results of old queries, so LRU caches are currently the
16 | only knob available for avoiding unbounded memory usage
17 | for long-running apps built on Salsa.
18 |
19 | ## Intern Queries
20 |
21 | Intern queries can make key lookup cheaper, save memory, and
22 | avoid the need for [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html).
23 |
24 | Interning is especially useful for queries that involve nested,
25 | tree-like data structures.
26 |
27 | See:
28 |
29 | - The [`compiler` example](https://github.com/salsa-rs/salsa/blob/master/examples/compiler/main.rs),
30 | which uses interning.
31 |
32 | ## Cancellation
33 |
34 | Queries that are no longer needed due to concurrent writes or changes in dependencies are cancelled
35 | by Salsa. Each access of an intermediate query is a potential cancellation point. Cancellation is
36 | implemented via panicking, and Salsa internals are intended to be panic-safe.
37 |
38 | If you have a query that contains a long loop which does not execute any intermediate queries,
39 | salsa won't be able to cancel it automatically. You may wish to check for cancellation yourself
40 | by invoking `db.unwind_if_cancelled()`.
41 |
42 | For more details on cancellation, see the tests for cancellation behavior in the Salsa repo.
43 |
--------------------------------------------------------------------------------
/book/src/tutorial.md:
--------------------------------------------------------------------------------
1 | # Tutorial: calc
2 |
3 | This tutorial walks through an end-to-end example of using Salsa.
4 | It does not assume you know anything about salsa,
5 | but reading the [overview](./overview.md) first is probably a good idea to get familiar with the basic concepts.
6 |
7 | Our goal is define a compiler/interpreter for a simple language called `calc`.
8 | The `calc` compiler takes programs like the following and then parses and executes them:
9 |
10 | ```
11 | fn area_rectangle(w, h) = w * h
12 | fn area_circle(r) = 3.14 * r * r
13 | print area_rectangle(3, 4)
14 | print area_circle(1)
15 | print 11 * 2
16 | ```
17 |
18 | When executed, this program prints `12`, `3.14`, and `22`.
19 |
20 | If the program contains errors (e.g., a reference to an undefined function), it prints those out too.
21 | And, of course, it will be reactive, so small changes to the input don't require recompiling (or rexecuting, necessarily) the entire thing.
22 |
--------------------------------------------------------------------------------
/book/src/tutorial/accumulators.md:
--------------------------------------------------------------------------------
1 | # Defining the parser: reporting errors
2 |
3 | The last interesting case in the parser is how to handle a parse error.
4 | Because Salsa functions are memoized and may not execute, they should not have side-effects,
5 | so we don't just want to call `eprintln!`.
6 | If we did so, the error would only be reported the first time the function was called, but not
7 | on subsequent calls in the situation where the simply returns its memoized value.
8 |
9 | Salsa defines a mechanism for managing this called an **accumulator**.
10 | In our case, we define an accumulator struct called `Diagnostics` in the `ir` module:
11 |
12 | ```rust
13 | {{#include ../../../examples/calc/ir.rs:diagnostic}}
14 | ```
15 |
16 | Accumulator structs are always newtype structs with a single field, in this case of type `Diagnostic`.
17 | Memoized functions can _push_ `Diagnostic` values onto the accumulator.
18 | Later, you can invoke a method to find all the values that were pushed by the memoized functions
19 | or any functions that they called
20 | (e.g., we could get the set of `Diagnostic` values produced by the `parse_statements` function).
21 |
22 | The `Parser::report_error` method contains an example of pushing a diagnostic:
23 |
24 | ```rust
25 | {{#include ../../../examples/calc/parser.rs:report_error}}
26 | ```
27 |
28 | To get the set of diagnostics produced by `parse_errors`, or any other memoized function,
29 | we invoke the associated `accumulated` function:
30 |
31 | ```rust
32 | let accumulated: Vec =
33 | parse_statements::accumulated::(db);
34 | // -----------
35 | // Use turbofish to specify
36 | // the diagnostics type.
37 | ```
38 |
39 | `accumulated` takes the database `db` as argument and returns a `Vec`.
40 |
--------------------------------------------------------------------------------
/book/src/tutorial/checker.md:
--------------------------------------------------------------------------------
1 | # Defining the checker
2 |
--------------------------------------------------------------------------------
/book/src/tutorial/db.md:
--------------------------------------------------------------------------------
1 | # Defining the database struct
2 |
3 | Now that we have defined a [jar](./jar.md), we need to create the **database struct**.
4 | The database struct is where all the jars come together.
5 | Typically it is only used by the "driver" of your application;
6 | the one which starts up the program, supplies the inputs, and relays the outputs.
7 |
8 | In `calc`, the database struct is in the [`db`] module, and it looks like this:
9 |
10 | [`db`]: https://github.com/salsa-rs/salsa/blob/master/examples/calc/db.rs
11 |
12 | ```rust
13 | {{#include ../../../examples/calc/db.rs:db_struct}}
14 | ```
15 |
16 | The `#[salsa::db(...)]` attribute takes a list of all the jars to include.
17 | The struct must have a field named `storage` whose type is `salsa::Storage`, but it can also contain whatever other fields you want.
18 | The `storage` struct owns all the data for the jars listed in the `db` attribute.
19 |
20 | The `salsa::db` attribute autogenerates a bunch of impls for things like the `salsa::HasJar` trait that we saw earlier.
21 |
22 | ## Implementing the `salsa::Database` trait
23 |
24 | In addition to the struct itself, we must add an impl of `salsa::Database`:
25 |
26 | ```rust
27 | {{#include ../../../examples/calc/db.rs:db_impl}}
28 | ```
29 |
30 | ## Implementing the `salsa::ParallelDatabase` trait
31 |
32 | If you want to permit accessing your database from multiple threads at once, then you also need to implement the `ParallelDatabase` trait:
33 |
34 | ```rust
35 | {{#include ../../../examples/calc/db.rs:par_db_impl}}
36 | ```
37 |
38 | ## Implementing the traits for each jar
39 |
40 | The `Database` struct also needs to implement the [database traits for each jar](./jar.md#database-trait-for-the-jar).
41 | In our case, though, we already wrote that impl as a [blanket impl alongside the jar itself](./jar.md#implementing-the-database-trait-for-the-jar),
42 | so no action is needed.
43 | This is the recommended strategy unless your trait has custom members that depend on fields of the `Database` itself
44 | (for example, sometimes the `Database` holds some kind of custom resource that you want to give access to).
45 |
--------------------------------------------------------------------------------
/book/src/tutorial/debug.md:
--------------------------------------------------------------------------------
1 | # Defining the parser: debug impls and testing
2 |
3 | As the final part of the parser, we need to write some tests.
4 | To do so, we will create a database, set the input source text, run the parser, and check the result.
5 | Before we can do that, though, we have to address one question: how do we inspect the value of an interned type like `Expression`?
6 |
7 | ## The `DebugWithDb` trait
8 |
9 | Because an interned type like `Expression` just stores an integer, the traditional `Debug` trait is not very useful.
10 | To properly print a `Expression`, you need to access the Salsa database to find out what its value is.
11 | To solve this, `salsa` provides a `DebugWithDb` trait that acts like the regular `Debug`, but takes a database as argument.
12 | For types that implement this trait, you can invoke the `debug` method.
13 | This returns a temporary that implements the ordinary `Debug` trait, allowing you to write something like
14 |
15 | ```rust
16 | eprintln!("Expression = {:?}", expr.debug(db));
17 | ```
18 |
19 | and get back the output you expect.
20 |
21 | The `DebugWithDb` trait is automatically derived for all `#[input]`, `#[interned]`, and `#[tracked]` structs.
22 |
23 | ## Forwarding to the ordinary `Debug` trait
24 |
25 | 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:
26 |
27 | ```rust
28 | {{#include ../../../examples/calc/ir.rs:op_debug_impl}}
29 | ```
30 |
31 | ## Writing the unit test
32 |
33 | Now that we have our `DebugWithDb` impls in place, we can write a simple unit test harness.
34 | The `parse_string` function below creates a database, sets the source text, and then invokes the parser:
35 |
36 | ```rust
37 | {{#include ../../../examples/calc/parser.rs:parse_string}}
38 | ```
39 |
40 | Combined with the [`expect-test`](https://crates.io/crates/expect-test) crate, we can then write unit tests like this one:
41 |
42 | ```rust
43 | {{#include ../../../examples/calc/parser.rs:parse_print}}
44 | ```
45 |
--------------------------------------------------------------------------------
/book/src/tutorial/interpreter.md:
--------------------------------------------------------------------------------
1 | # Defining the interpreter
2 |
--------------------------------------------------------------------------------
/book/src/tutorial/structure.md:
--------------------------------------------------------------------------------
1 | # Basic structure
2 |
3 | Before we do anything with Salsa, let's talk about the basic structure of the calc compiler.
4 | 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.
5 |
6 | ## Example program
7 |
8 | This is our example calc program:
9 |
10 | ```
11 | x = 5
12 | y = 10
13 | z = x + y * 3
14 | print z
15 | ```
16 |
17 | ## Parser
18 |
19 | The calc compiler takes as input a program, represented by a string:
20 |
21 | ```rust
22 | struct ProgramSource {
23 | text: String
24 | }
25 | ```
26 |
27 | The first thing it does it to parse that string into a series of statements that look something like the following pseudo-Rust:[^lexer]
28 |
29 | ```rust
30 | enum Statement {
31 | /// Defines `fn () = `
32 | Function(Function),
33 | /// Defines `print `
34 | Print(Expression),
35 | }
36 |
37 | /// Defines `fn () = `
38 | struct Function {
39 | name: FunctionId,
40 | args: Vec,
41 | body: Expression
42 | }
43 | ```
44 |
45 | where an expression is something like this (pseudo-Rust, because the `Expression` enum is recursive):
46 |
47 | ```rust
48 | enum Expression {
49 | Op(Expression, Op, Expression),
50 | Number(f64),
51 | Variable(VariableId),
52 | Call(FunctionId, Vec),
53 | }
54 |
55 | enum Op {
56 | Add,
57 | Subtract,
58 | Multiply,
59 | Divide,
60 | }
61 | ```
62 |
63 | Finally, for function/variable names, the `FunctionId` and `VariableId` types will be interned strings:
64 |
65 | ```rust
66 | type FunctionId = /* interned string */;
67 | type VariableId = /* interned string */;
68 | ```
69 |
70 | [^lexer]: Because calc is so simple, we don't have to bother separating out the lexer from the parser.
71 |
72 | ## Checker
73 |
74 | The "checker" has the job of ensuring that the user only references variables that have been defined.
75 | We're going to write the checker in a "context-less" style,
76 | which is a bit less intuitive but allows for more incremental re-use.
77 | The idea is to compute, for a given expression, which variables it references.
78 | Then there is a function `check` which ensures that those variables are a subset of those that are already defined.
79 |
80 | ## Interpreter
81 |
82 | The interpreter will execute the program and print the result. We don't bother with much incremental re-use here,
83 | though it's certainly possible.
84 |
--------------------------------------------------------------------------------
/book/src/videos.md:
--------------------------------------------------------------------------------
1 | # Videos
2 |
3 | There is currently one video available on the newest version of Salsa:
4 |
5 | - [Salsa Architecture Walkthrough](https://www.youtube.com/watch?v=vrnNvAAoQFk),
6 | which covers many aspects of the redesigned architecture.
7 |
8 | There are also two videos on the older version Salsa, but they are rather
9 | outdated:
10 |
11 | - [How Salsa Works](https://youtu.be/_muY4HjSqVw), which gives a high-level
12 | introduction to the key concepts involved and shows how to use Salsa;
13 | - [Salsa In More Depth](https://www.youtube.com/watch?v=i_IhACacPRY), which digs
14 | into the incremental algorithm and explains -- at a high-level -- how Salsa is
15 | implemented.
16 |
17 | > If you're in China, watch videos on
18 | > [How Salsa Works](https://www.bilibili.com/video/BV1Df4y1A7t3/),
19 | > [Salsa In More Depth](https://www.bilibili.com/video/BV1AM4y1G7E4/).
20 |
--------------------------------------------------------------------------------
/components/salsa-macro-rules/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "salsa-macro-rules"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 |
--------------------------------------------------------------------------------
/components/salsa-macro-rules/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! This crate defines various `macro_rules` macros
2 | //! used as part of Salsa's internal plumbing.
3 | //! These macros are re-exported under `salsa::plumbing``.
4 | //! The procedural macros emit calls to these
5 | //! `macro_rules` macros after doing error checking.
6 | //!
7 | //! Using `macro_rules` macro definitions is generally
8 | //! more ergonomic and also permits true hygiene for local variables
9 | //! (sadly not items).
10 | //!
11 | //! Currently the only way to have a macro that is re-exported
12 | //! from a submodule is to use multiple crates, hence the existence
13 | //! of this crate.
14 |
15 | mod macro_if;
16 | mod maybe_backdate;
17 | mod maybe_clone;
18 | mod maybe_default;
19 | mod setup_accumulator_impl;
20 | mod setup_input_struct;
21 | mod setup_interned_struct;
22 | mod setup_method_body;
23 | mod setup_tracked_fn;
24 | mod setup_tracked_struct;
25 | mod unexpected_cycle_recovery;
26 |
--------------------------------------------------------------------------------
/components/salsa-macro-rules/src/macro_if.rs:
--------------------------------------------------------------------------------
1 | #[macro_export]
2 | macro_rules! macro_if {
3 | (true => $($t:tt)*) => {
4 | $($t)*
5 | };
6 |
7 | (false => $($t:tt)*) => {
8 | };
9 |
10 | (if true { $($t:tt)* } else { $($f:tt)*}) => {
11 | $($t)*
12 | };
13 |
14 | (if false { $($t:tt)* } else { $($f:tt)*}) => {
15 | $($f)*
16 | };
17 |
18 | (if0 0 { $($t:tt)* } else { $($f:tt)*}) => {
19 | $($t)*
20 | };
21 |
22 | (if0 $n:literal { $($t:tt)* } else { $($f:tt)*}) => {
23 | $($f)*
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/components/salsa-macro-rules/src/maybe_backdate.rs:
--------------------------------------------------------------------------------
1 | /// Conditionally update field value and backdate revisions
2 | #[macro_export]
3 | macro_rules! maybe_backdate {
4 | (
5 | ($maybe_clone:ident, no_backdate, $maybe_default:ident),
6 | $field_ty:ty,
7 | $old_field_place:expr,
8 | $new_field_place:expr,
9 | $revision_place:expr,
10 | $current_revision:expr,
11 | $zalsa:ident,
12 |
13 | ) => {
14 | $zalsa::always_update(
15 | &mut $revision_place,
16 | $current_revision,
17 | &mut $old_field_place,
18 | $new_field_place,
19 | );
20 | };
21 |
22 | (
23 | ($maybe_clone:ident, backdate, $maybe_default:ident),
24 | $field_ty:ty,
25 | $old_field_place:expr,
26 | $new_field_place:expr,
27 | $revision_place:expr,
28 | $current_revision:expr,
29 | $zalsa:ident,
30 | ) => {
31 | if $zalsa::UpdateDispatch::<$field_ty>::maybe_update(
32 | std::ptr::addr_of_mut!($old_field_place),
33 | $new_field_place,
34 | ) {
35 | $revision_place = $current_revision;
36 | }
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/components/salsa-macro-rules/src/maybe_clone.rs:
--------------------------------------------------------------------------------
1 | /// Generate either `field_ref_expr` or a clone of that expr.
2 | ///
3 | /// Used when generating field getters.
4 | #[macro_export]
5 | macro_rules! maybe_clone {
6 | (
7 | (no_clone, $maybe_backdate:ident, $maybe_default:ident),
8 | $field_ty:ty,
9 | $field_ref_expr:expr,
10 | ) => {
11 | $field_ref_expr
12 | };
13 |
14 | (
15 | (clone, $maybe_backdate:ident, $maybe_default:ident),
16 | $field_ty:ty,
17 | $field_ref_expr:expr,
18 | ) => {
19 | std::clone::Clone::clone($field_ref_expr)
20 | };
21 | }
22 |
23 | #[macro_export]
24 | macro_rules! maybe_cloned_ty {
25 | (
26 | (no_clone, $maybe_backdate:ident, $maybe_default:ident),
27 | $db_lt:lifetime,
28 | $field_ty:ty
29 | ) => {
30 | & $db_lt $field_ty
31 | };
32 |
33 | (
34 | (clone, $maybe_backdate:ident, $maybe_default:ident),
35 | $db_lt:lifetime,
36 | $field_ty:ty
37 | ) => {
38 | $field_ty
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/components/salsa-macro-rules/src/maybe_default.rs:
--------------------------------------------------------------------------------
1 | /// Generate either `field_ref_expr` or `field_ty::default`
2 | ///
3 | /// Used when generating an input's builder.
4 | #[macro_export]
5 | macro_rules! maybe_default {
6 | (
7 | ($maybe_clone:ident, $maybe_backdate:ident, default),
8 | $field_ty:ty,
9 | $field_ref_expr:expr,
10 | ) => {
11 | <$field_ty>::default()
12 | };
13 |
14 | (
15 | ($maybe_clone:ident, $maybe_backdate:ident, required),
16 | $field_ty:ty,
17 | $field_ref_expr:expr,
18 | ) => {
19 | $field_ref_expr
20 | };
21 | }
22 |
23 | #[macro_export]
24 | macro_rules! maybe_default_tt {
25 | (($maybe_clone:ident, $maybe_backdate:ident, default) => $($t:tt)*) => {
26 | $($t)*
27 | };
28 |
29 | (($maybe_clone:ident, $maybe_backdate:ident, required) => $($t:tt)*) => {
30 |
31 | };
32 | }
33 |
--------------------------------------------------------------------------------
/components/salsa-macro-rules/src/setup_accumulator_impl.rs:
--------------------------------------------------------------------------------
1 | /// Macro for setting up a function that must intern its arguments.
2 | #[macro_export]
3 | macro_rules! setup_accumulator_impl {
4 | (
5 | // Name of the struct
6 | Struct: $Struct:ident,
7 |
8 | // Annoyingly macro-rules hygiene does not extend to items defined in the macro.
9 | // We have the procedural macro generate names for those items that are
10 | // not used elsewhere in the user's code.
11 | unused_names: [
12 | $zalsa:ident,
13 | $zalsa_struct:ident,
14 | $CACHE:ident,
15 | $ingredient:ident,
16 | ]
17 | ) => {
18 | const _: () = {
19 | use salsa::plumbing as $zalsa;
20 | use salsa::plumbing::accumulator as $zalsa_struct;
21 |
22 | static $CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Struct>> =
23 | $zalsa::IngredientCache::new();
24 |
25 | fn $ingredient(db: &dyn $zalsa::Database) -> &$zalsa_struct::IngredientImpl<$Struct> {
26 | $CACHE.get_or_create(db, || {
27 | db.zalsa().add_or_lookup_jar_by_type(&<$zalsa_struct::JarImpl<$Struct>>::default())
28 | })
29 | }
30 |
31 | impl $zalsa::Accumulator for $Struct {
32 | const DEBUG_NAME: &'static str = stringify!($Struct);
33 |
34 | fn accumulate(self, db: &Db)
35 | where
36 | Db: ?Sized + $zalsa::Database,
37 | {
38 | let db = db.as_dyn_database();
39 | $ingredient(db).push(db, self);
40 | }
41 | }
42 | };
43 | };
44 | }
45 |
--------------------------------------------------------------------------------
/components/salsa-macro-rules/src/setup_method_body.rs:
--------------------------------------------------------------------------------
1 | #[macro_export]
2 | macro_rules! setup_method_body {
3 | (
4 | salsa_tracked_attr: #[$salsa_tracked_attr:meta],
5 | self: $self:ident,
6 | self_ty: $self_ty:ty,
7 | db_lt: $($db_lt:lifetime)?,
8 | db: $db:ident,
9 | db_ty: ($($db_ty:tt)*),
10 | input_ids: [$($input_id:ident),*],
11 | input_tys: [$($input_ty:ty),*],
12 | output_ty: $output_ty:ty,
13 | inner_fn_name: $inner_fn_name:ident,
14 | inner_fn: $inner_fn:item,
15 |
16 | // Annoyingly macro-rules hygiene does not extend to items defined in the macro.
17 | // We have the procedural macro generate names for those items that are
18 | // not used elsewhere in the user's code.
19 | unused_names: [
20 | $InnerTrait:ident,
21 | ]
22 | ) => {
23 | {
24 | trait $InnerTrait<$($db_lt)?> {
25 | fn $inner_fn_name($self, db: $($db_ty)*, $($input_id: $input_ty),*) -> $output_ty;
26 | }
27 |
28 | impl<$($db_lt)?> $InnerTrait<$($db_lt)?> for $self_ty {
29 | $inner_fn
30 | }
31 |
32 | #[$salsa_tracked_attr]
33 | fn $inner_fn_name<$($db_lt)?>(db: $($db_ty)*, this: $self_ty, $($input_id: $input_ty),*) -> $output_ty {
34 | <$self_ty as $InnerTrait>::$inner_fn_name(this, db, $($input_id),*)
35 | }
36 |
37 | $inner_fn_name($db, $self, $($input_id),*)
38 | }
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/components/salsa-macro-rules/src/unexpected_cycle_recovery.rs:
--------------------------------------------------------------------------------
1 | // Macro that generates the body of the cycle recovery function
2 | // for the case where no cycle recovery is possible. This has to be
3 | // a macro because it can take a variadic number of arguments.
4 | #[macro_export]
5 | macro_rules! unexpected_cycle_recovery {
6 | ($db:ident, $cycle:ident, $($other_inputs:ident),*) => {
7 | {
8 | std::mem::drop($db);
9 | std::mem::drop(($($other_inputs),*));
10 | panic!("cannot recover from cycle `{:?}`", $cycle)
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/components/salsa-macros/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "salsa-macros"
3 | version = "0.18.0"
4 | authors = ["Salsa developers"]
5 | edition = "2021"
6 | license = "Apache-2.0 OR MIT"
7 | repository = "https://github.com/salsa-rs/salsa"
8 | description = "Procedural macros for the salsa crate"
9 |
10 | [lib]
11 | proc-macro = true
12 |
13 | [dependencies]
14 | heck = "0.5.0"
15 | proc-macro2 = "1.0"
16 | quote = "1.0"
17 | syn = { version = "2.0.64", features = ["full", "visit-mut"] }
18 | synstructure = "0.13.1"
19 |
--------------------------------------------------------------------------------
/components/salsa-macros/src/accumulator.rs:
--------------------------------------------------------------------------------
1 | use proc_macro2::TokenStream;
2 |
3 | use crate::{
4 | hygiene::Hygiene,
5 | options::{AllowedOptions, Options},
6 | };
7 |
8 | // #[salsa::accumulator(jar = Jar0)]
9 | // struct Accumulator(DataType);
10 |
11 | pub(crate) fn accumulator(
12 | args: proc_macro::TokenStream,
13 | input: proc_macro::TokenStream,
14 | ) -> proc_macro::TokenStream {
15 | let hygiene = Hygiene::from1(&input);
16 | let args = syn::parse_macro_input!(args as Options);
17 | let struct_item = syn::parse_macro_input!(input as syn::ItemStruct);
18 | let ident = struct_item.ident.clone();
19 | let m = StructMacro {
20 | hygiene,
21 | args,
22 | struct_item,
23 | };
24 | match m.try_expand() {
25 | Ok(v) => crate::debug::dump_tokens(ident, v).into(),
26 | Err(e) => e.to_compile_error().into(),
27 | }
28 | }
29 |
30 | struct Accumulator;
31 |
32 | impl AllowedOptions for Accumulator {
33 | const RETURN_REF: bool = false;
34 | const SPECIFY: bool = false;
35 | const NO_EQ: bool = false;
36 | const NO_DEBUG: bool = true;
37 | const NO_CLONE: bool = true;
38 | const SINGLETON: bool = false;
39 | const DATA: bool = false;
40 | const DB: bool = false;
41 | const RECOVERY_FN: bool = false;
42 | const LRU: bool = false;
43 | const CONSTRUCTOR_NAME: bool = false;
44 | }
45 |
46 | struct StructMacro {
47 | hygiene: Hygiene,
48 | args: Options,
49 | struct_item: syn::ItemStruct,
50 | }
51 |
52 | #[allow(non_snake_case)]
53 | impl StructMacro {
54 | fn try_expand(self) -> syn::Result {
55 | let ident = self.struct_item.ident.clone();
56 |
57 | let zalsa = self.hygiene.ident("zalsa");
58 | let zalsa_struct = self.hygiene.ident("zalsa_struct");
59 | let CACHE = self.hygiene.ident("CACHE");
60 | let ingredient = self.hygiene.ident("ingredient");
61 |
62 | let struct_item = self.struct_item;
63 |
64 | let mut derives = vec![];
65 | if self.args.no_debug.is_none() {
66 | derives.push(quote!(Debug));
67 | }
68 | if self.args.no_clone.is_none() {
69 | derives.push(quote!(Clone));
70 | }
71 |
72 | Ok(quote! {
73 | #[derive(#(#derives),*)]
74 | #struct_item
75 |
76 | salsa::plumbing::setup_accumulator_impl! {
77 | Struct: #ident,
78 | unused_names: [
79 | #zalsa,
80 | #zalsa_struct,
81 | #CACHE,
82 | #ingredient,
83 | ]
84 | }
85 | })
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/components/salsa-macros/src/db_lifetime.rs:
--------------------------------------------------------------------------------
1 | //! Helper functions for working with fns, structs, and other generic things
2 | //! that are allowed to have a `'db` lifetime.
3 |
4 | use proc_macro2::Span;
5 | use syn::spanned::Spanned;
6 |
7 | /// Normally we try to use whatever lifetime parameter the user gave us
8 | /// to represent `'db`; but if they didn't give us one, we need to use a default
9 | /// name. We choose `'db`.
10 | pub(crate) fn default_db_lifetime(span: Span) -> syn::Lifetime {
11 | syn::Lifetime {
12 | apostrophe: span,
13 | ident: syn::Ident::new("db", span),
14 | }
15 | }
16 |
17 | /// Require that either there are no generics or exactly one lifetime parameter.
18 | pub(crate) fn require_optional_db_lifetime(generics: &syn::Generics) -> syn::Result<()> {
19 | if generics.params.is_empty() {
20 | return Ok(());
21 | }
22 |
23 | require_db_lifetime(generics)?;
24 |
25 | Ok(())
26 | }
27 |
28 | /// Require that either there is exactly one lifetime parameter.
29 | pub(crate) fn require_db_lifetime(generics: &syn::Generics) -> syn::Result<()> {
30 | if generics.params.is_empty() {
31 | return Err(syn::Error::new_spanned(
32 | generics,
33 | "this definition must have a `'db` lifetime",
34 | ));
35 | }
36 |
37 | for (param, index) in generics.params.iter().zip(0..) {
38 | let error = match param {
39 | syn::GenericParam::Lifetime(_) => index > 0,
40 | syn::GenericParam::Type(_) | syn::GenericParam::Const(_) => true,
41 | };
42 |
43 | if error {
44 | return Err(syn::Error::new_spanned(
45 | param,
46 | "only a single lifetime parameter is accepted",
47 | ));
48 | }
49 | }
50 |
51 | Ok(())
52 | }
53 |
54 | /// Return the `'db` lifetime given by the user, or a default.
55 | /// The generics ought to have been checked with `require_db_lifetime` already.
56 | pub(crate) fn db_lifetime(generics: &syn::Generics) -> syn::Lifetime {
57 | if let Some(lt) = generics.lifetimes().next() {
58 | lt.lifetime.clone()
59 | } else {
60 | default_db_lifetime(generics.span())
61 | }
62 | }
63 |
64 | pub(crate) fn require_no_generics(generics: &syn::Generics) -> syn::Result<()> {
65 | if let Some(param) = generics.params.iter().next() {
66 | return Err(syn::Error::new_spanned(
67 | param,
68 | "generic parameters not allowed here",
69 | ));
70 | }
71 |
72 | Ok(())
73 | }
74 |
--------------------------------------------------------------------------------
/components/salsa-macros/src/debug.rs:
--------------------------------------------------------------------------------
1 | use std::io::Write;
2 | use std::process::{Command, Stdio};
3 | use std::sync::OnceLock;
4 |
5 | use proc_macro2::TokenStream;
6 |
7 | static SALSA_DEBUG_MACRO: OnceLock