salsa-0.26.2/.cargo_vcs_info.json0000644000000001361046102023000122500ustar { "git": { "sha1": "c9114d4bde31df4a17a0edf24d9356e131631b78" }, "path_in_vcs": "" }salsa-0.26.2/.devcontainer/devcontainer.json000064400000000000000000000022331046102023000171310ustar 00000000000000// For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/rust { "name": "Rust", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye", "features": { "ghcr.io/devcontainers-contrib/features/ripgrep:1": {} } // Use 'mounts' to make the cargo cache persistent in a Docker Volume. // "mounts": [ // { // "source": "devcontainer-cargo-cache-${devcontainerId}", // "target": "/usr/local/cargo", // "type": "volume" // } // ] // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "rustc --version", // Configure tool-specific properties. // "customizations": {}, // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" } salsa-0.26.2/.dir-locals.el000064400000000000000000000000501046102023000134420ustar 00000000000000((rust-mode (rust-format-on-save . t))) salsa-0.26.2/.github/dependabot.yml000064400000000000000000000007231046102023000152100ustar 00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for more information: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates # https://containers.dev/guide/dependabot version: 2 updates: - package-ecosystem: "devcontainers" directory: "/" schedule: interval: weekly salsa-0.26.2/.github/workflows/book.yml000064400000000000000000000036641046102023000161010ustar 00000000000000name: Book on: push: branches: - master pull_request: paths: - "book/**" - ".github/workflows/book.yml" jobs: book: name: Book if: github.repository == 'salsa-rs/salsa' runs-on: ubuntu-latest env: MDBOOK_VERSION: "0.4.40" MDBOOK_LINKCHECK_VERSION: "0.7.7" MDBOOK_MERMAID_VERSION: "0.13.0" steps: - uses: actions/checkout@v4 - name: Install mdbook run: | 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 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 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 unzip mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -d ~/.cargo/bin chmod +x ~/.cargo/bin/mdbook-linkcheck - name: Setup Pages id: pages uses: actions/configure-pages@v5 - name: Build run: mdbook build working-directory: book - name: Upload static files as artifact id: deployment uses: actions/upload-pages-artifact@v3 with: path: ./book/book/html deploy: name: Deploy runs-on: ubuntu-latest needs: book if: github.repository == 'salsa-rs/salsa' && github.event_name == 'push' && github.ref == 'refs/heads/master' concurrency: group: github-pages cancel-in-progress: true permissions: contents: read pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 salsa-0.26.2/.github/workflows/release.yml000064400000000000000000000030501046102023000165540ustar 00000000000000name: Release-plz permissions: pull-requests: write contents: write on: push: branches: - master jobs: # Release unpublished packages. release-plz-release: if: ${{ github.repository_owner == 'salsa-rs' }} name: Release-plz release runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Run release-plz uses: release-plz/action@v0.5 with: command: release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} # Create a PR with the new versions and changelog, preparing the next release. release-plz-pr: if: ${{ github.repository_owner == 'salsa-rs' }} name: Release-plz PR runs-on: ubuntu-latest permissions: contents: write pull-requests: write concurrency: group: release-plz-${{ github.ref }} cancel-in-progress: false steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Run release-plz uses: release-plz/action@v0.5 with: command: release-pr env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} salsa-0.26.2/.github/workflows/test.yml000064400000000000000000000125431046102023000161220ustar 00000000000000name: Test on: pull_request: merge_group: push: branches: - master concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: test: name: Test strategy: matrix: rust: - 1.85.0 - stable - beta experimental: - false include: - rust: nightly experimental: true continue-on-error: ${{ matrix.experimental }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@master id: rust-toolchain with: toolchain: ${{ matrix.rust }} components: rustfmt, clippy - uses: taiki-e/install-action@nextest - uses: actions/cache@v4 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.toml') }} restore-keys: | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}- ${{ runner.os }}-cargo- - name: Format run: cargo fmt -- --check - name: Clippy run: cargo clippy --workspace --all-targets -- -D warnings # TODO: Use something like cargo-hack for more robust feature configuration testing. - name: Clippy / all-features run: cargo clippy --workspace --all-targets --features persistence -- -D warnings - name: Test run: cargo nextest run --workspace --all-targets --features persistence --no-fail-fast - name: Test Manual Registration / no-default-features run: cargo nextest run --workspace --tests --no-fail-fast --no-default-features --features macros - name: Test docs run: cargo test --workspace --doc miri: name: Miri runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install Miri uses: dtolnay/rust-toolchain@miri id: rust-toolchain - uses: taiki-e/install-action@nextest - uses: actions/cache@v4 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-miri-${{ hashFiles('**/Cargo.toml') }} restore-keys: | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-miri- ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}- ${{ runner.os }}-cargo- - name: Setup Miri run: cargo miri setup - name: Test with Miri run: cargo miri nextest run --no-fail-fast --tests env: MIRIFLAGS: -Zmiri-disable-isolation - name: Run examples with Miri run: cargo miri run --example calc shuttle: name: Shuttle runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@master id: rust-toolchain with: toolchain: stable - uses: taiki-e/install-action@nextest - uses: actions/cache@v4 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.toml') }} restore-keys: | ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}- ${{ runner.os }}-cargo- - name: Test with Shuttle run: cargo nextest run --features shuttle --test parallel benchmarks: # https://github.com/CodSpeedHQ/action/issues/126 if: github.repository == 'salsa-rs/salsa' &&github.event_name != 'merge_group' name: Benchmarks runs-on: codspeed-macro steps: - name: Checkout uses: actions/checkout@v5 - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@master id: rust-toolchain with: toolchain: stable - name: "Setup codspeed" uses: taiki-e/install-action@v2 with: tool: cargo-codspeed - uses: actions/cache@v4 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-${{ runner.arch }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.toml') }} restore-keys: | ${{ runner.os }}-${{ runner.arch }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-benchmark ${{ runner.os }}-${{ runner.arch }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }} ${{ runner.os }}-${{ runner.arch }}-cargo- - name: "Build benchmarks" run: cargo codspeed build -m simulation -m memory - name: "Run benchmarks" uses: CodSpeedHQ/action@v4 with: mode: "simulation,memory" run: cargo codspeed run token: ${{ secrets.CODSPEED_TOKEN }} salsa-0.26.2/.gitignore000064400000000000000000000000571046102023000130100ustar 00000000000000/target **/*.rs.bk Cargo.lock TAGS nikom .idea salsa-0.26.2/CHANGELOG.md000064400000000000000000000656671046102023000126530ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.26.2](https://github.com/salsa-rs/salsa/compare/salsa-v0.26.1...salsa-v0.26.2) - 2026-05-03 ### Fixed - fix tracked impl db lifetime for as_deref return mode ([#1084](https://github.com/salsa-rs/salsa/pull/1084)) ### Other - Do not use `#[allow(non_local_definitions)]` ([#1090](https://github.com/salsa-rs/salsa/pull/1090)) - Exclude the book from published crates ([#1089](https://github.com/salsa-rs/salsa/pull/1089)) - Detect overlapping supertype variants via leaf type IDs ([#1080](https://github.com/salsa-rs/salsa/pull/1080)) - Bump hashbrown to 0.17 ([#1087](https://github.com/salsa-rs/salsa/pull/1087)) - Expose `Revision::max()` and make it const ([#1086](https://github.com/salsa-rs/salsa/pull/1086)) ## [0.26.1](https://github.com/salsa-rs/salsa/compare/salsa-v0.26.0...salsa-v0.26.1) - 2026-03-20 ### Other - Bump the edition to 2024 ([#1073](https://github.com/salsa-rs/salsa/pull/1073)) - Don't panic if a query branched on an untracked state ([#1075](https://github.com/salsa-rs/salsa/pull/1075)) ## [0.26.0](https://github.com/salsa-rs/salsa/compare/salsa-v0.25.2...salsa-v0.26.0) - 2026-02-02 ### Added - Allow opt-ing out of LRU at compile time ([#1051](https://github.com/salsa-rs/salsa/pull/1051)) - Allow tracked function cycle attributes to take closures ([#1048](https://github.com/salsa-rs/salsa/pull/1048)) ### Fixed - Do not alias fields of `tracked_struct` `Value`s when updating ([#741](https://github.com/salsa-rs/salsa/pull/741)) ### Other - Remove unnecessary `boxcar::Vec` ([#1072](https://github.com/salsa-rs/salsa/pull/1072)) - replace remaining instances of `cycle_fallback` with `cycle_result` in the docs ([#1071](https://github.com/salsa-rs/salsa/pull/1071)) - Fix out-of-order verification of cycle head dependencies ([#1061](https://github.com/salsa-rs/salsa/pull/1061)) - Update compile fail tests ([#1070](https://github.com/salsa-rs/salsa/pull/1070)) - Merge `FallbackImmediate` and `Fixpoint` code paths ([#1063](https://github.com/salsa-rs/salsa/pull/1063)) - Introduce a `CancellationToken` for cancelling specific computations ([#1007](https://github.com/salsa-rs/salsa/pull/1007)) - Remove unnecessary backdate field macros ([#1069](https://github.com/salsa-rs/salsa/pull/1069)) - Remove `ValueWithMetadata` ([#1057](https://github.com/salsa-rs/salsa/pull/1057)) - Fix stale tracked struct values in later iterations ([#1068](https://github.com/salsa-rs/salsa/pull/1068)) - Skip book deployment on forks ([#1066](https://github.com/salsa-rs/salsa/pull/1066)) - Remove `QueryOriginKind::FixpointInitial` ([#1062](https://github.com/salsa-rs/salsa/pull/1062)) - Document cycle_fallback in the book ([#1056](https://github.com/salsa-rs/salsa/pull/1056)) - Fix book build with newer mdbook versions ([#1055](https://github.com/salsa-rs/salsa/pull/1055)) - Implement `Lookup` and `HashEqLike` for `Cow` ([#1054](https://github.com/salsa-rs/salsa/pull/1054)) - Add `DidFinalizeCycle` event, add more tests for cyclic nested queries ([#1052](https://github.com/salsa-rs/salsa/pull/1052)) - Collect cycle heads transitively ([#1050](https://github.com/salsa-rs/salsa/pull/1050)) - Remove unused database forking ([#1049](https://github.com/salsa-rs/salsa/pull/1049)) - Reduce monomorphized code in maybe_changed_after ([#1047](https://github.com/salsa-rs/salsa/pull/1047)) - Reduce monomorphized code in `execute_maybe_iterate` ([#1046](https://github.com/salsa-rs/salsa/pull/1046)) - Add `salsa::Update` trait bounds to generics when deriving `salsa::Update` ([#1041](https://github.com/salsa-rs/salsa/pull/1041)) ## [0.25.2](https://github.com/salsa-rs/salsa/compare/salsa-v0.25.1...salsa-v0.25.2) - 2025-12-17 ### Other - Revert #958 ([#1039](https://github.com/salsa-rs/salsa/pull/1039)) ## [0.25.1](https://github.com/salsa-rs/salsa/compare/salsa-v0.25.0...salsa-v0.25.1) - 2025-12-16 ### Other - Fix a remainder that was forgotten in #1036 ([#1037](https://github.com/salsa-rs/salsa/pull/1037)) ## [0.25.0](https://github.com/salsa-rs/salsa/compare/salsa-v0.24.0...salsa-v0.25.0) - 2025-12-16 ### Other - Require interned structs' fields to be `Update` ([#1036](https://github.com/salsa-rs/salsa/pull/1036)) - Make `ordermap` an optional feature ([#1034](https://github.com/salsa-rs/salsa/pull/1034)) - implement `Update` for `OrderMap` and `OrderSet` ([#1033](https://github.com/salsa-rs/salsa/pull/1033)) - Fully qualify std Result type ([#1025](https://github.com/salsa-rs/salsa/pull/1025)) - pass `Cycle` to the cycle recovery function ([#1028](https://github.com/salsa-rs/salsa/pull/1028)) - Fix cycle head durability ([#1024](https://github.com/salsa-rs/salsa/pull/1024)) - Call `cycle_fn` for every iteration ([#1021](https://github.com/salsa-rs/salsa/pull/1021)) - Track cycle function dependencies as part of the cyclic query ([#1018](https://github.com/salsa-rs/salsa/pull/1018)) - Always increment iteration count ([#1017](https://github.com/salsa-rs/salsa/pull/1017)) - Update compile fail snapshots to match new rust stable output ([#1020](https://github.com/salsa-rs/salsa/pull/1020)) - Only use provisional values from the same revision ([#1019](https://github.com/salsa-rs/salsa/pull/1019)) - Explain the motivation for breaking API changes made in #1012 and #1015 ([#1016](https://github.com/salsa-rs/salsa/pull/1016)) - Expose the Input query Id with cycle_initial ([#1015](https://github.com/salsa-rs/salsa/pull/1015)) - Add `SyncTable::peek_claim` fast path for `function::Ingredient::wait_for` ([#1011](https://github.com/salsa-rs/salsa/pull/1011)) - Fix cache invalidation when cycle head becomes non-head ([#1014](https://github.com/salsa-rs/salsa/pull/1014)) - Expose the query ID and the last provisional value to the cycle recovery function ([#1012](https://github.com/salsa-rs/salsa/pull/1012)) - Fix hangs in multithreaded fixpoint iteration ([#1010](https://github.com/salsa-rs/salsa/pull/1010)) - Remove experimental parallel feature ([#1013](https://github.com/salsa-rs/salsa/pull/1013)) - Simplify `WaitGroup` implementation ([#958](https://github.com/salsa-rs/salsa/pull/958)) - Fix missing license files in published macros/macro-rules crates ([#1009](https://github.com/salsa-rs/salsa/pull/1009)) - Run fixpoint per strongly connected component ([#999](https://github.com/salsa-rs/salsa/pull/999)) - Add benchmark for a fixpoint iteration with nested cycles ([#1001](https://github.com/salsa-rs/salsa/pull/1001)) ## [0.24.0](https://github.com/salsa-rs/salsa/compare/salsa-v0.23.0...salsa-v0.24.0) - 2025-09-30 ### Fixed - Cleanup provisional cycle head memos when query panics ([#993](https://github.com/salsa-rs/salsa/pull/993)) - Runaway for unchanged queries participating in cycle ([#981](https://github.com/salsa-rs/salsa/pull/981)) - Delete not re-created tracked structs after fixpoint iteration ([#979](https://github.com/salsa-rs/salsa/pull/979)) - fix assertion during interned deserialization ([#978](https://github.com/salsa-rs/salsa/pull/978)) - Do not unnecessarily require `Debug` on fields for interned structs ([#951](https://github.com/salsa-rs/salsa/pull/951)) - Fix phantom data usage in salsa structs affecting auto traits ([#932](https://github.com/salsa-rs/salsa/pull/932)) ### Other - Replace unsafe unwrap with `expect` call ([#998](https://github.com/salsa-rs/salsa/pull/998)) - Push active query in execute ([#996](https://github.com/salsa-rs/salsa/pull/996)) - Update codspeed action ([#997](https://github.com/salsa-rs/salsa/pull/997)) - Add implementations for Lookup and HashEqLike for CompactString ([#988](https://github.com/salsa-rs/salsa/pull/988)) - Provide a method to attach a database even if it's different from the current attached one ([#992](https://github.com/salsa-rs/salsa/pull/992)) - Allow fallback to take longer than one iteration to converge ([#991](https://github.com/salsa-rs/salsa/pull/991)) - refactor `entries` API ([#987](https://github.com/salsa-rs/salsa/pull/987)) - Persistent caching fixes ([#982](https://github.com/salsa-rs/salsa/pull/982)) - outline cold path of `lookup_ingredient` ([#984](https://github.com/salsa-rs/salsa/pull/984)) - Update snapshot to fix nightly type rendering ([#983](https://github.com/salsa-rs/salsa/pull/983)) - avoid cycles during serialization ([#977](https://github.com/salsa-rs/salsa/pull/977)) - Flatten unserializable query dependencies ([#975](https://github.com/salsa-rs/salsa/pull/975)) - optimize `Id::hash` ([#974](https://github.com/salsa-rs/salsa/pull/974)) - Make `thin-vec/serde` dependency dependent on `persistence` feature ([#973](https://github.com/salsa-rs/salsa/pull/973)) - Remove tracked structs from query outputs ([#969](https://github.com/salsa-rs/salsa/pull/969)) - Remove jemalloc ([#972](https://github.com/salsa-rs/salsa/pull/972)) - Initial persistent caching prototype ([#967](https://github.com/salsa-rs/salsa/pull/967)) - Fix `maybe_changed_after` runnaway for fixpoint queries ([#961](https://github.com/salsa-rs/salsa/pull/961)) - add parallel maybe changed after test ([#963](https://github.com/salsa-rs/salsa/pull/963)) - Update tests for Rust 1.89 ([#966](https://github.com/salsa-rs/salsa/pull/966)) - remove allocation lock ([#962](https://github.com/salsa-rs/salsa/pull/962)) - consolidate memory usage information API ([#964](https://github.com/salsa-rs/salsa/pull/964)) - Add heap size support for salsa structs ([#943](https://github.com/salsa-rs/salsa/pull/943)) - Extract the cycle branches from `fetch` and `maybe_changed_after` ([#955](https://github.com/salsa-rs/salsa/pull/955)) - allow reuse of cached provisional memos within the same cycle iteration during `maybe_changed_after` ([#954](https://github.com/salsa-rs/salsa/pull/954)) - Expose API to manually trigger cancellation ([#959](https://github.com/salsa-rs/salsa/pull/959)) - Upgrade dependencies ([#956](https://github.com/salsa-rs/salsa/pull/956)) - Use `CycleHeadSet` in `maybe_update_after` ([#953](https://github.com/salsa-rs/salsa/pull/953)) - Gate accumulator feature behind a feature flag ([#946](https://github.com/salsa-rs/salsa/pull/946)) - optimize allocation fast-path ([#949](https://github.com/salsa-rs/salsa/pull/949)) - remove borrow checks from `ZalsaLocal` ([#939](https://github.com/salsa-rs/salsa/pull/939)) - Do manual trait casting ([#922](https://github.com/salsa-rs/salsa/pull/922)) - Retain backing allocation of `ActiveQuery::input_outputs` in `ActiveQuery::seed_iteration` ([#948](https://github.com/salsa-rs/salsa/pull/948)) - remove extra bounds checks from memo table hot-paths ([#938](https://github.com/salsa-rs/salsa/pull/938)) - Outline all tracing events ([#942](https://github.com/salsa-rs/salsa/pull/942)) - remove bounds and type checks from `IngredientCache` ([#937](https://github.com/salsa-rs/salsa/pull/937)) - Avoid dynamic dispatch to access memo tables ([#941](https://github.com/salsa-rs/salsa/pull/941)) - optimize page access ([#940](https://github.com/salsa-rs/salsa/pull/940)) - Use `inventory` for static ingredient registration ([#934](https://github.com/salsa-rs/salsa/pull/934)) - Fix `heap_size` option not being preserved in tracked impls ([#930](https://github.com/salsa-rs/salsa/pull/930)) - update papaya ([#928](https://github.com/salsa-rs/salsa/pull/928)) ## [0.23.0](https://github.com/salsa-rs/salsa/compare/salsa-v0.22.0...salsa-v0.23.0) - 2025-06-27 ### Added - `Update` derive field overwrite support ([#747](https://github.com/salsa-rs/salsa/pull/747)) ### Fixed - fix race in `MemoTableTypes` ([#912](https://github.com/salsa-rs/salsa/pull/912)) - multithreaded nested fixpoint iteration ([#882](https://github.com/salsa-rs/salsa/pull/882)) ### Other - Emit self ty for query debug name of assoc function queries ([#927](https://github.com/salsa-rs/salsa/pull/927)) - Replace ingredient cache with faster ingredient map ([#921](https://github.com/salsa-rs/salsa/pull/921)) - add option to track heap memory usage of memos ([#925](https://github.com/salsa-rs/salsa/pull/925)) - Hide generated structs of tracked functions from docs via `#[doc(hidden)]` ([#917](https://github.com/salsa-rs/salsa/pull/917)) - Add API to dump memory usage ([#916](https://github.com/salsa-rs/salsa/pull/916)) - Revert "Assert size for interned Value" & Mark `Slot` trait as unsafe ([#915](https://github.com/salsa-rs/salsa/pull/915)) - add an option to tune interned garbage collection ([#911](https://github.com/salsa-rs/salsa/pull/911)) - Use explicit discriminants for `QueryOriginKind` for better comparisons ([#913](https://github.com/salsa-rs/salsa/pull/913)) - update boxcar ([#910](https://github.com/salsa-rs/salsa/pull/910)) - use latest revision for dependencies on interned values ([#908](https://github.com/salsa-rs/salsa/pull/908)) - remove high-durability values from interned LRU ([#907](https://github.com/salsa-rs/salsa/pull/907)) - Preserve attributes on interned/tracked struct fields ([#905](https://github.com/salsa-rs/salsa/pull/905)) - Assert size for interned `Value` ([#901](https://github.com/salsa-rs/salsa/pull/901)) - reduce size of interned value metadata ([#903](https://github.com/salsa-rs/salsa/pull/903)) - panic with string message again for cycle panics ([#898](https://github.com/salsa-rs/salsa/pull/898)) - Use `Revision` and `Durability` directly in input `Value` ([#902](https://github.com/salsa-rs/salsa/pull/902)) - Fix flaky parallel_join test ([#900](https://github.com/salsa-rs/salsa/pull/900)) - Bump MSRV to 1.85 ([#899](https://github.com/salsa-rs/salsa/pull/899)) - Simple LRU garbage collection for interned values ([#839](https://github.com/salsa-rs/salsa/pull/839)) - Capture execution backtrace when throwing `UnexpectedCycle` ([#883](https://github.com/salsa-rs/salsa/pull/883)) - Store tracked struct ids as ThinVec on Revisions ([#892](https://github.com/salsa-rs/salsa/pull/892)) - Update dependencies, remove unused `heck` dependency ([#894](https://github.com/salsa-rs/salsa/pull/894)) - Set `validate_final` in `execute` after removing the last cycle head ([#890](https://github.com/salsa-rs/salsa/pull/890)) - Pack `QueryEdge` memory layout ([#886](https://github.com/salsa-rs/salsa/pull/886)) - Lazily allocate extra memo state ([#888](https://github.com/salsa-rs/salsa/pull/888)) - Pack `QueryOrigin` memory layout ([#885](https://github.com/salsa-rs/salsa/pull/885)) - Restrict memo size assertion to 64bit platforms ([#884](https://github.com/salsa-rs/salsa/pull/884)) - Don't report stale outputs if there is newer generation in new_outputs ([#879](https://github.com/salsa-rs/salsa/pull/879)) - Fix hang in nested fixpoint iteration ([#871](https://github.com/salsa-rs/salsa/pull/871)) - Add debug spans for `new_revision` and `evict_lru` ([#881](https://github.com/salsa-rs/salsa/pull/881)) - Add fetch span ([#875](https://github.com/salsa-rs/salsa/pull/875)) - shrink_to_fit `IdentityMap` before storing it ([#816](https://github.com/salsa-rs/salsa/pull/816)) - Allow lifetimes in arguments in tracked fns with >1 parameters ([#880](https://github.com/salsa-rs/salsa/pull/880)) - Replace loom with shuttle ([#876](https://github.com/salsa-rs/salsa/pull/876)) - Use generational identifiers for tracked structs ([#864](https://github.com/salsa-rs/salsa/pull/864)) ### Fixed - `#[doc(hidden)]` auto-generated tracked-fn structs ([#917](https://github.com/salsa-rs/salsa/pull/917)) ## [0.22.0](https://github.com/salsa-rs/salsa/compare/salsa-v0.21.1...salsa-v0.22.0) - 2025-05-23 ### Fixed - fix memo table growth condition ([#850](https://github.com/salsa-rs/salsa/pull/850)) - incorrect caching for queries participating in fixpoint ([#843](https://github.com/salsa-rs/salsa/pull/843)) - change detection for fixpoint queries ([#836](https://github.com/salsa-rs/salsa/pull/836)) ### Other - Allow creation of tracked associated functions (without `self`) ([#859](https://github.com/salsa-rs/salsa/pull/859)) - Short-circuit `block-on` if same thread ([#862](https://github.com/salsa-rs/salsa/pull/862)) - Skip release-plz jobs on forks ([#873](https://github.com/salsa-rs/salsa/pull/873)) - Unwind with specific type when encountering an unexpected cycle ([#856](https://github.com/salsa-rs/salsa/pull/856)) - Remove jar mentions from book ([#775](https://github.com/salsa-rs/salsa/pull/775)) - Implement an `!Update` bound escape hatch for tracked fn ([#867](https://github.com/salsa-rs/salsa/pull/867)) - Only enable `boxcar/loom` when `loom` feature is enabled ([#869](https://github.com/salsa-rs/salsa/pull/869)) - Remove default `PartialOrd` and `Ord` derives for salsa-structs ([#868](https://github.com/salsa-rs/salsa/pull/868)) - update boxcar ([#865](https://github.com/salsa-rs/salsa/pull/865)) - speed-up cycle-retry logic ([#861](https://github.com/salsa-rs/salsa/pull/861)) - Fix returns(deref | as_ref | as_deref) in tracked methods ([#857](https://github.com/salsa-rs/salsa/pull/857)) - Changed `return_ref` syntax to `returns(as_ref)` and `returns(cloned)` ([#772](https://github.com/salsa-rs/salsa/pull/772)) - Work around a rust-analyzer bug ([#855](https://github.com/salsa-rs/salsa/pull/855)) - Lazy finalization of cycle participants in `maybe_changed_after` ([#854](https://github.com/salsa-rs/salsa/pull/854)) - Do not re-verify already verified memoized value in cycle verification ([#851](https://github.com/salsa-rs/salsa/pull/851)) - Pass cycle heads as out parameter for `maybe_changed_after` ([#852](https://github.com/salsa-rs/salsa/pull/852)) - Move salsa event system into `Zalsa` ([#849](https://github.com/salsa-rs/salsa/pull/849)) - gate loom dependency under feature flag ([#844](https://github.com/salsa-rs/salsa/pull/844)) - Add loom support ([#842](https://github.com/salsa-rs/salsa/pull/842)) - Clean up some unsafety ([#830](https://github.com/salsa-rs/salsa/pull/830)) ## [0.21.1](https://github.com/salsa-rs/salsa/compare/salsa-v0.21.0...salsa-v0.21.1) - 2025-04-30 ### Added - Make `attach` pub ([#832](https://github.com/salsa-rs/salsa/pull/832)) ### Other - better debug name for interned query arguments ([#837](https://github.com/salsa-rs/salsa/pull/837)) - Avoid panic in `Backtrace::capture` if `query_stack` is already borrowed ([#835](https://github.com/salsa-rs/salsa/pull/835)) - Clean up `function::execute` ([#833](https://github.com/salsa-rs/salsa/pull/833)) - Change an `assert!` to `assert_eq!` ([#828](https://github.com/salsa-rs/salsa/pull/828)) ## [0.21.0](https://github.com/salsa-rs/salsa/compare/salsa-v0.20.0...salsa-v0.21.0) - 2025-04-29 ### Fixed - Access to tracked-struct that was freed during fixpoint ([#817](https://github.com/salsa-rs/salsa/pull/817)) - correct debug output for tracked fields ([#826](https://github.com/salsa-rs/salsa/pull/826)) - Fix incorrect `values_equal` signature ([#825](https://github.com/salsa-rs/salsa/pull/825)) - allow unused lifetimes in tracked_struct expansion ([#824](https://github.com/salsa-rs/salsa/pull/824)) ### Other - Implement a query stack `Backtrace` analog ([#827](https://github.com/salsa-rs/salsa/pull/827)) - Simplify ID conversions ([#822](https://github.com/salsa-rs/salsa/pull/822)) - Attempt to fix codspeed ([#823](https://github.com/salsa-rs/salsa/pull/823)) - Remove unnecessary `Array` abstraction ([#821](https://github.com/salsa-rs/salsa/pull/821)) - Add a compile-fail test for a `'static` `!Update` struct ([#820](https://github.com/salsa-rs/salsa/pull/820)) - squelch most clippy warnings in generated code ([#809](https://github.com/salsa-rs/salsa/pull/809)) - Include struct name in formatted input-field index ([#819](https://github.com/salsa-rs/salsa/pull/819)) - Force inline `fetch_hot` ([#818](https://github.com/salsa-rs/salsa/pull/818)) - Per ingredient sync table ([#650](https://github.com/salsa-rs/salsa/pull/650)) - Use `DatabaseKey` for interned events ([#813](https://github.com/salsa-rs/salsa/pull/813)) - [refactor] More `fetch_hot` simplification ([#793](https://github.com/salsa-rs/salsa/pull/793)) - Don't store the fields in the interned map ([#812](https://github.com/salsa-rs/salsa/pull/812)) - Fix ci not always running ([#810](https://github.com/salsa-rs/salsa/pull/810)) ## [0.20.0](https://github.com/salsa-rs/salsa/compare/salsa-v0.19.0...salsa-v0.20.0) - 2025-04-22 ### Added - Drop `Debug` requirements and flip implementation defaults ([#756](https://github.com/salsa-rs/salsa/pull/756)) ### Fixed - Dereferencing freed memos when verifying provisional memos ([#788](https://github.com/salsa-rs/salsa/pull/788)) - `#[doc(hidden)]` `plumbing` module ([#781](https://github.com/salsa-rs/salsa/pull/781)) - Use `changed_at` revision when updating fields ([#778](https://github.com/salsa-rs/salsa/pull/778)) ### Other - Reduce memory usage by deduplicating type information ([#803](https://github.com/salsa-rs/salsa/pull/803)) - Make interned's `last_interned_at` equal `Revision::MAX` if they are interned outside a query ([#804](https://github.com/salsa-rs/salsa/pull/804)) - Add a third cycle mode, equivalent to old Salsa cycle behavior ([#801](https://github.com/salsa-rs/salsa/pull/801)) - Update compact_str from 0.8 to 0.9 ([#794](https://github.com/salsa-rs/salsa/pull/794)) - Implement `Update` for `ThinVec` ([#807](https://github.com/salsa-rs/salsa/pull/807)) - Don't push an unnecessary active query for `deep_verify_memo` ([#806](https://github.com/salsa-rs/salsa/pull/806)) - Inline/Outline more cold and slow paths ([#805](https://github.com/salsa-rs/salsa/pull/805)) - `#[inline]` some things ([#799](https://github.com/salsa-rs/salsa/pull/799)) - Discard unnecessary atomic load ([#780](https://github.com/salsa-rs/salsa/pull/780)) - Print query stack when encountering unexpected cycle ([#796](https://github.com/salsa-rs/salsa/pull/796)) - Remove incorrect `parallel_scope` API ([#797](https://github.com/salsa-rs/salsa/pull/797)) - [refactor] Simplify `fetch_hot` ([#792](https://github.com/salsa-rs/salsa/pull/792)) - [refactor] Reuse the same stack for all cycles heads in `validate_same_iteration` ([#791](https://github.com/salsa-rs/salsa/pull/791)) - add WillIterateCycle event ([#790](https://github.com/salsa-rs/salsa/pull/790)) - [fix] Use `validate_maybe_provisional` instead of `validate_provisional` ([#789](https://github.com/salsa-rs/salsa/pull/789)) - Use `ThinVec` for `CycleHeads` ([#787](https://github.com/salsa-rs/salsa/pull/787)) - Keep edge condvar on stack instead of allocating it in an `Arc` ([#773](https://github.com/salsa-rs/salsa/pull/773)) - allow reuse of cached provisional memos within the same cycle iteration ([#786](https://github.com/salsa-rs/salsa/pull/786)) - Implement `Lookup`/`HashEqLike` for `Arc` ([#784](https://github.com/salsa-rs/salsa/pull/784)) - Normalize imports style ([#779](https://github.com/salsa-rs/salsa/pull/779)) - Clean up `par_map` a bit ([#742](https://github.com/salsa-rs/salsa/pull/742)) - Fix typo in comment ([#777](https://github.com/salsa-rs/salsa/pull/777)) - Document most safety blocks ([#776](https://github.com/salsa-rs/salsa/pull/776)) - Use html directory for mdbook artifact ([#774](https://github.com/salsa-rs/salsa/pull/774)) - Move `verified_final` from `Memo` into `QueryRevisions` ([#769](https://github.com/salsa-rs/salsa/pull/769)) - Use `ThinVec` for `MemoTable`, halving its size ([#770](https://github.com/salsa-rs/salsa/pull/770)) - Remove unnecessary query stack acess in `block_on` ([#771](https://github.com/salsa-rs/salsa/pull/771)) - Replace memo queue with append-only vector ([#767](https://github.com/salsa-rs/salsa/pull/767)) - update boxcar ([#696](https://github.com/salsa-rs/salsa/pull/696)) - Remove extra page indirection in `Table` ([#710](https://github.com/salsa-rs/salsa/pull/710)) - update release steps ([#705](https://github.com/salsa-rs/salsa/pull/705)) - Remove some unnecessary panicking paths in cycle execution ([#765](https://github.com/salsa-rs/salsa/pull/765)) - *(perf)* Pool `ActiveQuerys` in the query stack ([#629](https://github.com/salsa-rs/salsa/pull/629)) - Resolve unwind safety fixme ([#761](https://github.com/salsa-rs/salsa/pull/761)) - Enable Garbage Collection for Interned Values ([#602](https://github.com/salsa-rs/salsa/pull/602)) - bug [salsa-macros]: Improve debug name of tracked methods ([#755](https://github.com/salsa-rs/salsa/pull/755)) - Remove dead code ([#764](https://github.com/salsa-rs/salsa/pull/764)) - Reduce unnecessary conditional work in `deep_verify_memo` ([#759](https://github.com/salsa-rs/salsa/pull/759)) - Use a `Vec` for `CycleHeads` ([#760](https://github.com/salsa-rs/salsa/pull/760)) - Use nextest for miri test runs ([#758](https://github.com/salsa-rs/salsa/pull/758)) - Pin `half` version to prevent CI failure ([#757](https://github.com/salsa-rs/salsa/pull/757)) - rewrite cycle handling to support fixed-point iteration ([#603](https://github.com/salsa-rs/salsa/pull/603)) ## [0.19.0](https://github.com/salsa-rs/salsa/compare/salsa-v0.18.0...salsa-v0.19.0) - 2025-03-10 ### Fixed - fix typo - fix enums bug ### Other - Have salsa not depend on salsa-macros ([#750](https://github.com/salsa-rs/salsa/pull/750)) - Group versions of packages together for releases ([#751](https://github.com/salsa-rs/salsa/pull/751)) - use `portable-atomic` in `IngredientCache` to compile on `powerpc-unknown-linux-gnu` ([#749](https://github.com/salsa-rs/salsa/pull/749)) - Store view downcaster in function ingredients directly ([#720](https://github.com/salsa-rs/salsa/pull/720)) - Some small perf things ([#744](https://github.com/salsa-rs/salsa/pull/744)) - :replace instead of std::mem::replace ([#746](https://github.com/salsa-rs/salsa/pull/746)) - Cleanup `Cargo.toml`s ([#745](https://github.com/salsa-rs/salsa/pull/745)) - Drop clone requirement for accumulated values - implement `Update` trait for `IndexMap`, and `IndexSet` - more correct bounds on `Send` and `Sync` implementation `DeletedEntries` - replace `arc-swap` with manual `AtomicPtr` - Remove unnecessary `current_revision` call from `setup_interned_struct` - Merge pull request #731 from Veykril/veykril/push-nzkwqzxxkxou - Remove some dynamically dispatched `Database::event` calls - Lazy fetching - Add small supertype input benchmark - Replace a `DashMap` with `RwLock` as writing is rare for it - address review comments - Skip memo ingredient index mapping for non enum tracked functions - Trade off a bit of memory for more speed in `MemoIngredientIndices` - Introduce Salsa enums - Cancel duplicate test workflow runs - implement `Update` trait for `hashbrown::HashMap` - Move `unwind_if_revision_cancelled` from `ZalsaLocal` to `Zalsa` - Don't clone strings in benchmarks - Merge pull request #714 from Veykril/veykril/push-synxntlkqqsq - Merge pull request #711 from Veykril/veykril/push-stmmwmtprovt - Merge pull request #715 from Veykril/veykril/push-plwpsqknwulq - Enforce `unsafe_op_in_unsafe_fn` - Remove some `ZalsaDatabase::zalsa` calls - Remove outdated FIXME - Replace `IngredientCache` lock with atomic primitive - Reduce method delegation duplication - Automatically clear the cancellation flag when cancellation completes - Allow trigger LRU eviction without increasing the current revision - Simplify `Ingredient::reset_for_new_revision` setup - Require mut Zalsa access for setting the lru limit - Split off revision bumping from `zalsa_mut` access - Update `hashbrown` (0.15) and `hashlink` (0.10) salsa-0.26.2/Cargo.lock0000644000001277231046102023000102370ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "annotate-snippets" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" dependencies = [ "anstyle", "unicode-width", ] [[package]] name = "anstream" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", "windows-sys 0.61.2", ] [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "assoc" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdc70193dadb9d7287fa4b633f15f90c876915b31f6af17da307fc59c9859a8" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", "tap", "wyz", ] [[package]] name = "boxcar" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "castaway" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cc" version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "codspeed" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7ce4a32373a5c84f4fa785099b300b9b1796311abc122b9eb62c65fa615145d" dependencies = [ "anyhow", "cc", "colored", "getrandom", "glob", "libc", "nix", "serde", "serde_json", "statrs", ] [[package]] name = "codspeed-criterion-compat" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5557c4e023f427ba208b849193c51c2ebd40534828490cd3f5f7f7fc0284ce5" dependencies = [ "clap", "codspeed", "codspeed-criterion-compat-walltime", "colored", "regex", ] [[package]] name = "codspeed-criterion-compat-walltime" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43ac19f0a5c3542301e9d011d658f93c3f550698ec8ba7d7c072692e7924c401" dependencies = [ "anes", "cast", "ciborium", "clap", "codspeed", "criterion-plot", "is-terminal", "itertools", "num-traits", "once_cell", "oorandom", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", "windows-sys 0.59.0", ] [[package]] name = "compact_str" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", "itoa", "rustversion", "ryu", "static_assertions", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "dashmap" version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "dissimilar" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aeda16ab4059c5fd2a83f2b9c9e9c981327b18aa8e3b313f7e6563799d4f093e" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "env_filter" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", ] [[package]] name = "env_logger" version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "anstream", "anstyle", "env_filter", "log", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" dependencies = [ "serde", "serde_core", "typeid", ] [[package]] name = "expect-test" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0" dependencies = [ "dissimilar", "once_cell", ] [[package]] name = "eyre" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] [[package]] name = "filetime" version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", ] [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "fsevent-sys" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ "libc", ] [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "generator" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" dependencies = [ "cc", "cfg-if", "libc", "log", "rustversion", "windows-link", "windows-result", ] [[package]] name = "getrandom" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "half" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", "zerocopy", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash 0.1.5", ] [[package]] name = "hashbrown" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", ] [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ "hashbrown 0.15.5", ] [[package]] name = "hermit-abi" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "indenter" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.0", ] [[package]] name = "inotify" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ "bitflags 1.3.2", "inotify-sys", "libc", ] [[package]] name = "inotify-sys" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" dependencies = [ "libc", ] [[package]] name = "intrusive-collections" version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" dependencies = [ "memoffset", ] [[package]] name = "inventory" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" dependencies = [ "rustversion", ] [[package]] name = "is-terminal" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "kqueue" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", ] [[package]] name = "kqueue-sys" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" dependencies = [ "bitflags 1.3.2", "libc", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libredox" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "bitflags 2.11.1", "libc", "plain", "redox_syscall 0.7.4", ] [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "matchers" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ "regex-automata", ] [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "mio" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", "wasi", "windows-sys 0.48.0", ] [[package]] name = "nix" version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "notify" version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ "bitflags 2.11.1", "crossbeam-channel", "filetime", "fsevent-sys", "inotify", "kqueue", "libc", "log", "mio", "walkdir", "windows-sys 0.48.0", ] [[package]] name = "notify-debouncer-mini" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43" dependencies = [ "crossbeam-channel", "log", "notify", ] [[package]] name = "nu-ansi-term" version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "ordered-float" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" dependencies = [ "num-traits", ] [[package]] name = "ordermap" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7476a5b122ff1fce7208e7ee9dccd0a516e835f5b8b19b8f3c98a34cf757c1" dependencies = [ "indexmap", ] [[package]] name = "owo-colors" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall 0.5.18", "smallvec", "windows-link", ] [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "plain" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rand_pcg" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ "rand_core", ] [[package]] name = "rayon" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.11.1", ] [[package]] name = "redox_syscall" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ "bitflags 2.11.1", ] [[package]] name = "regex" version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustc-hash" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "salsa" version = "0.26.2" dependencies = [ "annotate-snippets", "boxcar", "codspeed-criterion-compat", "compact_str", "crossbeam-channel", "crossbeam-queue", "crossbeam-utils", "dashmap", "erased-serde", "expect-test", "eyre", "hashbrown 0.17.0", "hashlink", "indexmap", "intrusive-collections", "inventory", "notify-debouncer-mini", "ordered-float", "ordermap", "parking_lot", "portable-atomic", "rayon", "rustc-hash", "rustversion", "salsa-macro-rules", "salsa-macros", "serde", "serde_json", "shuttle", "smallvec", "test-log", "thin-vec", "tracing", "trybuild", "typeid", ] [[package]] name = "salsa-macro-rules" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58e354cbac6939b9b09cd9c11fb419a53e64b4a0f755d929f56a09f4cc752e41" [[package]] name = "salsa-macros" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3067861075c2b80608f84ad49fb88f2c7610b94cdf8b4201e79ddee87f8980c8" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", "serde", "serde_core", "zmij", ] [[package]] name = "serde_spanned" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "shuttle" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ab17edba38d63047f46780cf7360acf7467fec2c048928689a5c1dd1c2b4e31" dependencies = [ "assoc", "bitvec", "cfg-if", "generator", "hex", "owo-colors", "rand", "rand_core", "rand_pcg", "scoped-tls", "smallvec", "tracing", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "statrs" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" dependencies = [ "approx", "num-traits", ] [[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-triple" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "test-log" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f46bf474f0a4afebf92f076d54fd5e63423d9438b8c278a3d2ccb0f47f7cdb3" dependencies = [ "env_logger", "test-log-macros", "tracing-subscriber", ] [[package]] name = "test-log-core" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d4d41320b48bc4a211a9021678fcc0c99569b594ea31c93735b8e517102b4c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "test-log-macros" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9beb9249a81e430dffd42400a49019bcf548444f1968ff23080a625de0d4d320" dependencies = [ "syn", "test-log-core", ] [[package]] name = "thin-vec" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" dependencies = [ "serde", ] [[package]] name = "thread_local" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "toml" version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", "serde_core", "serde_spanned", "toml_datetime", "toml_parser", "toml_writer", "winnow", ] [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] [[package]] name = "toml_writer" version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tracing" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex-automata", "sharded-slab", "thread_local", "tracing", "tracing-core", "tracing-log", ] [[package]] name = "trybuild" version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47c635f0191bd3a2941013e5062667100969f8c4e9cd787c14f977265d73616e" dependencies = [ "glob", "serde", "serde_derive", "serde_json", "target-triple", "termcolor", "toml", ] [[package]] name = "typeid" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" [[package]] name = "wyz" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] [[package]] name = "zerocopy" version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" salsa-0.26.2/Cargo.toml0000644000000321321046102023000102470ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" rust-version = "1.85" name = "salsa" version = "0.26.2" authors = ["Salsa developers"] build = false exclude = ["book/"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A generic framework for on-demand, incrementalized computation (experimental)" readme = "README.md" license = "Apache-2.0 OR MIT" repository = "https://github.com/salsa-rs/salsa" [features] accumulator = ["salsa-macro-rules/accumulator"] default = [ "salsa_unstable", "rayon", "macros", "inventory", "accumulator", ] inventory = ["dep:inventory"] macros = ["dep:salsa-macros"] persistence = [ "dep:serde", "dep:erased-serde", "salsa-macros/persistence", "thin-vec/serde", ] salsa_unstable = [] shuttle = ["dep:shuttle"] [lib] name = "salsa" path = "src/lib.rs" [[example]] name = "calc" path = "examples/calc/main.rs" required-features = ["accumulator"] [[example]] name = "lazy-input" path = "examples/lazy-input/main.rs" required-features = ["accumulator"] [[test]] name = "accumulate" path = "tests/accumulate.rs" [[test]] name = "accumulate-chain" path = "tests/accumulate-chain.rs" [[test]] name = "accumulate-custom-debug" path = "tests/accumulate-custom-debug.rs" [[test]] name = "accumulate-dag" path = "tests/accumulate-dag.rs" [[test]] name = "accumulate-execution-order" path = "tests/accumulate-execution-order.rs" [[test]] name = "accumulate-from-tracked-fn" path = "tests/accumulate-from-tracked-fn.rs" [[test]] name = "accumulate-no-duplicates" path = "tests/accumulate-no-duplicates.rs" [[test]] name = "accumulate-reuse" path = "tests/accumulate-reuse.rs" [[test]] name = "accumulate-reuse-workaround" path = "tests/accumulate-reuse-workaround.rs" [[test]] name = "accumulated_backdate" path = "tests/accumulated_backdate.rs" [[test]] name = "backdate_untracked_db_field" path = "tests/backdate_untracked_db_field.rs" [[test]] name = "backtrace" path = "tests/backtrace.rs" [[test]] name = "cancellation_token" path = "tests/cancellation_token.rs" [[test]] name = "check_auto_traits" path = "tests/check_auto_traits.rs" [[test]] name = "compile_fail" path = "tests/compile_fail.rs" [[test]] name = "compile_pass" path = "tests/compile_pass.rs" [[test]] name = "cycle" path = "tests/cycle.rs" [[test]] name = "cycle_accumulate" path = "tests/cycle_accumulate.rs" [[test]] name = "cycle_dependency_order_different_entry_queries" path = "tests/cycle_dependency_order_different_entry_queries.rs" [[test]] name = "cycle_fallback_immediate" path = "tests/cycle_fallback_immediate.rs" [[test]] name = "cycle_initial_call_back_into_cycle" path = "tests/cycle_initial_call_back_into_cycle.rs" [[test]] name = "cycle_initial_call_query" path = "tests/cycle_initial_call_query.rs" [[test]] name = "cycle_input_different_cycle_head" path = "tests/cycle_input_different_cycle_head.rs" [[test]] name = "cycle_left_recursive_query" path = "tests/cycle_left_recursive_query.rs" [[test]] name = "cycle_maybe_changed_after" path = "tests/cycle_maybe_changed_after.rs" [[test]] name = "cycle_nested_converge_early" path = "tests/cycle_nested_converge_early.rs" [[test]] name = "cycle_nested_converges_never" path = "tests/cycle_nested_converges_never.rs" [[test]] name = "cycle_output" path = "tests/cycle_output.rs" [[test]] name = "cycle_recovery_call_back_into_cycle" path = "tests/cycle_recovery_call_back_into_cycle.rs" [[test]] name = "cycle_recovery_call_query" path = "tests/cycle_recovery_call_query.rs" [[test]] name = "cycle_recovery_dependencies" path = "tests/cycle_recovery_dependencies.rs" [[test]] name = "cycle_regression_455" path = "tests/cycle_regression_455.rs" [[test]] name = "cycle_result_dependencies" path = "tests/cycle_result_dependencies.rs" [[test]] name = "cycle_stale_cycle_heads" path = "tests/cycle_stale_cycle_heads.rs" [[test]] name = "cycle_tracked" path = "tests/cycle_tracked.rs" [[test]] name = "cycle_tracked_own_input" path = "tests/cycle_tracked_own_input.rs" [[test]] name = "dataflow" path = "tests/dataflow.rs" [[test]] name = "debug" path = "tests/debug.rs" [[test]] name = "debug_bounds" path = "tests/debug_bounds.rs" [[test]] name = "debug_db_contents" path = "tests/debug_db_contents.rs" [[test]] name = "deletion" path = "tests/deletion.rs" [[test]] name = "deletion-cascade" path = "tests/deletion-cascade.rs" [[test]] name = "deletion-drops" path = "tests/deletion-drops.rs" [[test]] name = "derive_update" path = "tests/derive_update.rs" [[test]] name = "durability" path = "tests/durability.rs" [[test]] name = "elided-lifetime-in-tracked-fn" path = "tests/elided-lifetime-in-tracked-fn.rs" [[test]] name = "expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y" path = "tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs" [[test]] name = "expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y" path = "tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs" [[test]] name = "hash_collision" path = "tests/hash_collision.rs" [[test]] name = "hello_world" path = "tests/hello_world.rs" [[test]] name = "input_default" path = "tests/input_default.rs" [[test]] name = "input_field_durability" path = "tests/input_field_durability.rs" [[test]] name = "input_setter_preserves_durability" path = "tests/input_setter_preserves_durability.rs" [[test]] name = "intern_access_in_different_revision" path = "tests/intern_access_in_different_revision.rs" [[test]] name = "interned-revisions" path = "tests/interned-revisions.rs" [[test]] name = "interned-structs" path = "tests/interned-structs.rs" [[test]] name = "interned-structs_self_ref" path = "tests/interned-structs_self_ref.rs" [[test]] name = "lru" path = "tests/lru.rs" [[test]] name = "manual_registration" path = "tests/manual_registration.rs" [[test]] name = "memory-usage" path = "tests/memory-usage.rs" [[test]] name = "mutate_in_place" path = "tests/mutate_in_place.rs" [[test]] name = "override_new_get_set" path = "tests/override_new_get_set.rs" [[test]] name = "panic-when-creating-tracked-struct-outside-of-tracked-fn" path = "tests/panic-when-creating-tracked-struct-outside-of-tracked-fn.rs" [[test]] name = "parallel" path = "tests/parallel/main.rs" [[test]] name = "persistence" path = "tests/persistence.rs" [[test]] name = "preverify-struct-with-leaked-data" path = "tests/preverify-struct-with-leaked-data.rs" [[test]] name = "preverify-struct-with-leaked-data-2" path = "tests/preverify-struct-with-leaked-data-2.rs" [[test]] name = "return_mode" path = "tests/return_mode.rs" [[test]] name = "singleton" path = "tests/singleton.rs" [[test]] name = "specify-only-works-if-the-key-is-created-in-the-current-query" path = "tests/specify-only-works-if-the-key-is-created-in-the-current-query.rs" [[test]] name = "supertype_overlap" path = "tests/supertype_overlap.rs" [[test]] name = "synthetic_write" path = "tests/synthetic_write.rs" [[test]] name = "tracked-struct-id-field-bad-eq" path = "tests/tracked-struct-id-field-bad-eq.rs" [[test]] name = "tracked-struct-id-field-bad-hash" path = "tests/tracked-struct-id-field-bad-hash.rs" [[test]] name = "tracked-struct-unchanged-in-new-rev" path = "tests/tracked-struct-unchanged-in-new-rev.rs" [[test]] name = "tracked-struct-value-field-bad-eq" path = "tests/tracked-struct-value-field-bad-eq.rs" [[test]] name = "tracked-struct-value-field-not-eq" path = "tests/tracked-struct-value-field-not-eq.rs" [[test]] name = "tracked_assoc_fn" path = "tests/tracked_assoc_fn.rs" [[test]] name = "tracked_fn_constant" path = "tests/tracked_fn_constant.rs" [[test]] name = "tracked_fn_high_durability_dependency" path = "tests/tracked_fn_high_durability_dependency.rs" [[test]] name = "tracked_fn_interned_lifetime" path = "tests/tracked_fn_interned_lifetime.rs" [[test]] name = "tracked_fn_multiple_args" path = "tests/tracked_fn_multiple_args.rs" [[test]] name = "tracked_fn_no_eq" path = "tests/tracked_fn_no_eq.rs" [[test]] name = "tracked_fn_on_input" path = "tests/tracked_fn_on_input.rs" [[test]] name = "tracked_fn_on_input_with_high_durability" path = "tests/tracked_fn_on_input_with_high_durability.rs" [[test]] name = "tracked_fn_on_interned" path = "tests/tracked_fn_on_interned.rs" [[test]] name = "tracked_fn_on_interned_enum" path = "tests/tracked_fn_on_interned_enum.rs" [[test]] name = "tracked_fn_on_tracked" path = "tests/tracked_fn_on_tracked.rs" [[test]] name = "tracked_fn_on_tracked_specify" path = "tests/tracked_fn_on_tracked_specify.rs" [[test]] name = "tracked_fn_orphan_escape_hatch" path = "tests/tracked_fn_orphan_escape_hatch.rs" [[test]] name = "tracked_fn_read_own_entity" path = "tests/tracked_fn_read_own_entity.rs" [[test]] name = "tracked_fn_read_own_specify" path = "tests/tracked_fn_read_own_specify.rs" [[test]] name = "tracked_fn_return_ref" path = "tests/tracked_fn_return_ref.rs" [[test]] name = "tracked_method" path = "tests/tracked_method.rs" [[test]] name = "tracked_method_inherent_return_deref" path = "tests/tracked_method_inherent_return_deref.rs" [[test]] name = "tracked_method_inherent_return_ref" path = "tests/tracked_method_inherent_return_ref.rs" [[test]] name = "tracked_method_on_tracked_struct" path = "tests/tracked_method_on_tracked_struct.rs" [[test]] name = "tracked_method_trait_return_ref" path = "tests/tracked_method_trait_return_ref.rs" [[test]] name = "tracked_method_with_self_ty" path = "tests/tracked_method_with_self_ty.rs" [[test]] name = "tracked_struct" path = "tests/tracked_struct.rs" [[test]] name = "tracked_struct_db1_lt" path = "tests/tracked_struct_db1_lt.rs" [[test]] name = "tracked_struct_disambiguates" path = "tests/tracked_struct_disambiguates.rs" [[test]] name = "tracked_struct_durability" path = "tests/tracked_struct_durability.rs" [[test]] name = "tracked_struct_manual_update" path = "tests/tracked_struct_manual_update.rs" [[test]] name = "tracked_struct_mixed_tracked_fields" path = "tests/tracked_struct_mixed_tracked_fields.rs" [[test]] name = "tracked_struct_recreate_new_revision" path = "tests/tracked_struct_recreate_new_revision.rs" [[test]] name = "tracked_struct_with_interned_query" path = "tests/tracked_struct_with_interned_query.rs" [[test]] name = "tracked_with_intern" path = "tests/tracked_with_intern.rs" [[test]] name = "tracked_with_struct_db" path = "tests/tracked_with_struct_db.rs" [[test]] name = "tracked_with_struct_ord" path = "tests/tracked_with_struct_ord.rs" [[test]] name = "warnings" path = "tests/warnings/main.rs" [[bench]] name = "accumulator" path = "benches/accumulator.rs" harness = false required-features = ["accumulator"] [[bench]] name = "compare" path = "benches/compare.rs" harness = false [[bench]] name = "dataflow" path = "benches/dataflow.rs" harness = false [[bench]] name = "incremental" path = "benches/incremental.rs" harness = false [dependencies.boxcar] version = "0.2.14" [dependencies.compact_str] version = "0.9" optional = true [dependencies.crossbeam-queue] version = "0.3.12" [dependencies.crossbeam-utils] version = "0.8.21" [dependencies.erased-serde] version = "0.4.6" optional = true [dependencies.hashbrown] version = "0.17" [dependencies.hashlink] version = "0.10" [dependencies.indexmap] version = "2" [dependencies.intrusive-collections] version = "0.9.7" [dependencies.inventory] version = "0.3.24" optional = true [dependencies.ordermap] version = "1" optional = true [dependencies.parking_lot] version = "0.12" [dependencies.portable-atomic] version = "1" [dependencies.rayon] version = "1.10.0" optional = true [dependencies.rustc-hash] version = "2" [dependencies.salsa-macro-rules] version = "0.26.2" [dependencies.salsa-macros] version = "0.26.2" optional = true [dependencies.serde] version = "1.0.219" features = ["derive"] optional = true [dependencies.shuttle] version = "0.8.1" optional = true [dependencies.smallvec] version = "1" features = ["const_new"] [dependencies.thin-vec] version = "0.2.14" [dependencies.tracing] version = "0.1" features = ["std"] default-features = false [dependencies.typeid] version = "1.0" [dev-dependencies.annotate-snippets] version = "0.11.5" [dev-dependencies.codspeed-criterion-compat] version = "4.4.1" default-features = false [dev-dependencies.crossbeam-channel] version = "0.5.15" [dev-dependencies.dashmap] version = "6" features = ["raw-api"] [dev-dependencies.expect-test] version = "1.5.1" [dev-dependencies.eyre] version = "0.6.12" [dev-dependencies.notify-debouncer-mini] version = "0.4.1" [dev-dependencies.ordered-float] version = "5.0.0" [dev-dependencies.rustversion] version = "1.0" [dev-dependencies.serde_json] version = "1.0.140" [dev-dependencies.test-log] version = "0.2.19" features = ["trace"] [dev-dependencies.trybuild] version = "1.0" [target."cfg(any())".dependencies.salsa-macros] version = "=0.26.2" salsa-0.26.2/Cargo.toml.orig000064400000000000000000000061701046102023000137110ustar 00000000000000[package] name = "salsa" version = "0.26.2" authors.workspace = true edition.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true description = "A generic framework for on-demand, incrementalized computation (experimental)" exclude = [ "book/", ] [dependencies] salsa-macro-rules = { version = "0.26.2", path = "components/salsa-macro-rules" } salsa-macros = { version = "0.26.2", path = "components/salsa-macros", optional = true } boxcar = "0.2.14" crossbeam-queue = "0.3.12" crossbeam-utils = "0.8.21" hashbrown = "0.17" hashlink = "0.10" indexmap = "2" intrusive-collections = "0.9.7" parking_lot = "0.12" portable-atomic = "1" rustc-hash = "2" smallvec = { version = "1", features = ["const_new"] } thin-vec = { version = "0.2.14" } tracing = { version = "0.1", default-features = false, features = ["std"] } typeid = "1.0" # Automatic ingredient registration. inventory = { version = "0.3.24", optional = true } # parallel map rayon = { version = "1.10.0", optional = true } # Stuff we want Update impls for by default compact_str = { version = "0.9", optional = true } shuttle = { version = "0.8.1", optional = true } # Persistent caching erased-serde = { version = "0.4.6", optional = true } serde = { version = "1.0.219", features = ["derive"], optional = true } ordermap = { version = "1", optional = true } [features] default = ["salsa_unstable", "rayon", "macros", "inventory", "accumulator"] inventory = ["dep:inventory"] persistence = [ "dep:serde", "dep:erased-serde", "salsa-macros/persistence", "thin-vec/serde", ] shuttle = ["dep:shuttle"] accumulator = ["salsa-macro-rules/accumulator"] macros = ["dep:salsa-macros"] # FIXME: remove `salsa_unstable` before 1.0. salsa_unstable = [] # This interlocks the `salsa-macros` and `salsa` versions together # preventing scenarios where they could diverge in a given project # which may ultimately result in odd issues due to the proc-macro # output mismatching with the declarative macro inputs [target.'cfg(any())'.dependencies] salsa-macros = { version = "=0.26.2", path = "components/salsa-macros" } [dev-dependencies] # examples crossbeam-channel = "0.5.15" dashmap = { version = "6", features = ["raw-api"] } eyre = "0.6.12" notify-debouncer-mini = "0.4.1" ordered-float = "5.0.0" # tests/benches annotate-snippets = "0.11.5" codspeed-criterion-compat = { version = "4.4.1", default-features = false } expect-test = "1.5.1" rustversion = "1.0" test-log = { version = "0.2.19", features = ["trace"] } trybuild = "1.0" serde_json = "1.0.140" [[bench]] name = "compare" harness = false [[bench]] name = "incremental" harness = false [[bench]] name = "accumulator" harness = false required-features = ["accumulator"] [[bench]] name = "dataflow" harness = false [[example]] name = "lazy-input" required-features = ["accumulator"] [[example]] name = "calc" required-features = ["accumulator"] [workspace] members = ["components/salsa-macro-rules", "components/salsa-macros"] [workspace.package] authors = ["Salsa developers"] edition = "2024" license = "Apache-2.0 OR MIT" repository = "https://github.com/salsa-rs/salsa" rust-version = "1.85" salsa-0.26.2/FAQ.md000064400000000000000000000030621046102023000117500ustar 00000000000000# Frequently asked questions ## Why is it called salsa? I like salsa! Don't you?! Well, ok, there's a bit more to it. The underlying algorithm for figuring out which bits of code need to be re-executed after any given change is based on the algorithm used in rustc. Michael Woerister and I first described the rustc algorithm in terms of two colors, red and green, and hence we called it the "red-green algorithm". This made me think of the New Mexico State Question --- ["Red or green?"][nm] --- which refers to chile (salsa). Although this version no longer uses colors (we borrowed revision counters from Glimmer, instead), I still like the name. [nm]: https://www.sos.state.nm.us/about-new-mexico/state-question/ ## What is the relationship between salsa and an Entity-Component System (ECS)? You may have noticed that Salsa "feels" a lot like an ECS in some ways. That's true -- Salsa's queries are a bit like *components* (and the keys to the queries are a bit like *entities*). But there is one big difference: **ECS is -- at its heart -- a mutable system**. You can get or set a component of some entity whenever you like. In contrast, salsa's queries **define "derived values" via pure computations**. Partly as a consequence, ECS doesn't handle incremental updates for you. When you update some component of some entity, you have to ensure that other entities' components are updated appropriately. Finally, ECS offers interesting metadata and "aspect-like" facilities, such as iterating over all entities that share certain components. Salsa has no analogue to that. salsa-0.26.2/LICENSE-APACHE000064400000000000000000000251371046102023000127520ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. salsa-0.26.2/LICENSE-MIT000064400000000000000000000017771046102023000124660ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. salsa-0.26.2/README.md000064400000000000000000000045641046102023000123060ustar 00000000000000# salsa [![Test](https://github.com/salsa-rs/salsa/workflows/Test/badge.svg)](https://github.com/salsa-rs/salsa/actions?query=workflow%3ATest) [![Book](https://github.com/salsa-rs/salsa/workflows/Book/badge.svg)](https://github.com/salsa-rs/salsa/actions?query=workflow%3ABook) [![Released API docs](https://docs.rs/salsa/badge.svg)](https://docs.rs/salsa) [![Crates.io](https://img.shields.io/crates/v/salsa.svg)](https://crates.io/crates/salsa) *A generic framework for on-demand, incrementalized computation.* Salsa Logo ## Obligatory warning Very much a WORK IN PROGRESS at this point. ## Credits This system is heavily inspired by [adapton](http://adapton.org/), [glimmer](https://github.com/glimmerjs/glimmer-vm), and rustc's query system. So credit goes to Eduard-Mihai Burtescu, Matthew Hammer, Yehuda Katz, and Michael Woerister. ## Key idea The key idea of `salsa` is that you define your program as a set of **queries**. Every query is used like function `K -> V` that maps from some key of type `K` to a value of type `V`. Queries come in two basic varieties: - **Inputs**: the base inputs to your system. You can change these whenever you like. - **Functions**: pure functions (no side effects) that transform your inputs into other values. The results of queries are memoized to avoid recomputing them a lot. When you make changes to the inputs, we'll figure out (fairly intelligently) when we can re-use these memoized values and when we have to recompute them. ## Want to learn more? To learn more about Salsa, try one of the following: - read the [heavily commented examples](https://github.com/salsa-rs/salsa/tree/master/examples); - check out the [Salsa book](https://salsa-rs.github.io/salsa); - [中文版](https://rust-chinese-translation.github.io/salsa-book) - watch one of our [videos](https://salsa-rs.github.io/salsa/videos.html). ## Getting in touch The bulk of the discussion happens in the [issues](https://github.com/salsa-rs/salsa/issues) and [pull requests](https://github.com/salsa-rs/salsa/pulls), but we have a [zulip chat](https://salsa.zulipchat.com/) as well. ## Contributing To create a release and publish to crates.io, update the `version` field in Cargo.toml. After pushed, GitHub Actions will publish the crates to crates.io automatically. salsa-0.26.2/RELEASES.md000064400000000000000000000012131046102023000125400ustar 00000000000000# 0.13.0 - **Breaking change:** adopt the new `Durability` API proposed in [RFC #6] - this replaces and generalizes the existing concepts of constants - **Breaking change:** remove "volatile" queries - instead, create a normal query which invokes the `report_untracked_read` method on the salsa runtime - introduce "slots", an optimization to salsa's internal workings - document `#[salsa::requires]` attribute, which permits private dependencies - Adopt `AtomicU64` for `runtimeId` (#182) - use `ptr::eq` and `ptr::hash` for readability - upgrade parking lot, rand dependencies [RFC #6]: https://github.com/salsa-rs/salsa-rfcs/pull/6 salsa-0.26.2/benches/accumulator.rs000064400000000000000000000036031046102023000153140ustar 00000000000000use std::hint::black_box; use codspeed_criterion_compat::{BatchSize, Criterion, criterion_group, criterion_main}; use salsa::Accumulator; #[salsa::input] struct Input { expressions: usize, } #[allow(dead_code)] #[salsa::accumulator] struct Diagnostic(String); #[salsa::interned] struct Expression<'db> { number: usize, } #[salsa::tracked] #[inline(never)] fn root(db: &dyn salsa::Database, input: Input) -> Vec { (0..input.expressions(db)) .map(|i| infer_expression(db, Expression::new(db, i))) .collect() } #[salsa::tracked] #[inline(never)] fn infer_expression<'db>(db: &'db dyn salsa::Database, expression: Expression<'db>) -> usize { let number = expression.number(db); if number % 10 == 0 { Diagnostic(format!("Number is {number}")).accumulate(db); } if number != 0 && number % 2 == 0 { let sub_expression = Expression::new(db, number / 2); let _ = infer_expression(db, sub_expression); } number } fn accumulator(criterion: &mut Criterion) { criterion.bench_function("accumulator", |b| { b.iter_batched_ref( || { let db = salsa::DatabaseImpl::new(); let input = Input::new(black_box(&db), black_box(10_000)); // Pre-warm let result = root(black_box(&db), black_box(input)); assert!(!black_box(result).is_empty()); (db, input) }, |(db, input)| { // Measure the cost of collecting accumulators ignoring the cost of running the // query itself. let diagnostics = root::accumulated::(black_box(db), *black_box(input)); assert_eq!(black_box(diagnostics).len(), 1000); }, BatchSize::SmallInput, ); }); } criterion_group!(benches, accumulator); criterion_main!(benches); salsa-0.26.2/benches/compare.rs000064400000000000000000000213001046102023000144150ustar 00000000000000use std::hint::black_box; use std::mem::transmute; use codspeed_criterion_compat::{ BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main, }; use salsa::Setter; #[salsa::input] pub struct Input { #[returns(ref)] pub text: String, } #[salsa::tracked] #[inline(never)] pub fn length(db: &dyn salsa::Database, input: Input) -> usize { input.text(db).len() } #[salsa::interned] pub struct InternedInput<'db> { #[returns(ref)] pub text: String, } #[derive(Clone, Copy, PartialEq, Eq, Hash, salsa::Supertype)] enum SupertypeInput<'db> { InternedInput(InternedInput<'db>), Input(Input), } #[salsa::tracked] #[inline(never)] pub fn interned_length<'db>(db: &'db dyn salsa::Database, input: InternedInput<'db>) -> usize { input.text(db).len() } #[salsa::tracked] #[inline(never)] pub fn either_length<'db>(db: &'db dyn salsa::Database, input: SupertypeInput<'db>) -> usize { match input { SupertypeInput::InternedInput(input) => interned_length(db, input), SupertypeInput::Input(input) => length(db, input), } } fn mutating_inputs(c: &mut Criterion) { let mut group: codspeed_criterion_compat::BenchmarkGroup< codspeed_criterion_compat::measurement::WallTime, > = c.benchmark_group("Mutating Inputs"); for n in &[10, 20, 30] { group.bench_function(BenchmarkId::new("mutating", n), |b| { b.iter_batched_ref( || { let db = salsa::DatabaseImpl::default(); let base_string = "hello, world!".to_owned(); let base_len = base_string.len(); let string = base_string.clone().repeat(*n); let new_len = string.len(); let input = Input::new(black_box(&db), black_box(base_string.clone())); let actual_len = length(&db, input); assert_eq!(black_box(actual_len), base_len); (db, input, string, new_len) }, |&mut (ref mut db, input, ref string, new_len)| { input.set_text(black_box(db)).to(black_box(string).clone()); let actual_len = length(db, input); assert_eq!(black_box(actual_len), new_len); }, BatchSize::SmallInput, ) }); } group.finish(); } fn inputs(c: &mut Criterion) { let mut group: codspeed_criterion_compat::BenchmarkGroup< codspeed_criterion_compat::measurement::WallTime, > = c.benchmark_group("Mutating Inputs"); group.bench_function(BenchmarkId::new("new", "InternedInput"), |b| { b.iter_batched_ref( || { let db = salsa::DatabaseImpl::default(); // Prepopulate ingredients. let input = InternedInput::new(black_box(&db), black_box("hello, world!".to_owned())); let interned_len = interned_length(black_box(&db), black_box(input)); assert_eq!(black_box(interned_len), 13); db }, |db| { let input = InternedInput::new(black_box(db), black_box("hello, world!".to_owned())); let interned_len = interned_length(black_box(db), black_box(input)); assert_eq!(black_box(interned_len), 13); }, BatchSize::SmallInput, ) }); group.bench_function(BenchmarkId::new("amortized", "InternedInput"), |b| { b.iter_batched_ref( || { let db = salsa::DatabaseImpl::default(); // we can't pass this along otherwise, and the lifetime is generally informational let input: InternedInput<'static> = unsafe { transmute(InternedInput::new(&db, "hello, world!".to_owned())) }; let interned_len = interned_length(black_box(&db), black_box(input)); assert_eq!(black_box(interned_len), 13); (db, input) }, |&mut (ref db, input)| { let interned_len = interned_length(black_box(db), black_box(input)); assert_eq!(black_box(interned_len), 13); }, BatchSize::SmallInput, ) }); group.bench_function(BenchmarkId::new("new", "Input"), |b| { b.iter_batched_ref( || { let db = salsa::DatabaseImpl::default(); // Prepopulate ingredients. let input = Input::new(black_box(&db), black_box("hello, world!".to_owned())); let len = length(black_box(&db), black_box(input)); assert_eq!(black_box(len), 13); db }, |db| { let input = Input::new(black_box(db), black_box("hello, world!".to_owned())); let len = length(black_box(db), black_box(input)); assert_eq!(black_box(len), 13); }, BatchSize::SmallInput, ) }); group.bench_function(BenchmarkId::new("amortized", "Input"), |b| { b.iter_batched_ref( || { let db = salsa::DatabaseImpl::default(); let input = Input::new(black_box(&db), black_box("hello, world!".to_owned())); let len = length(black_box(&db), black_box(input)); assert_eq!(black_box(len), 13); (db, input) }, |&mut (ref db, input)| { let len = length(black_box(db), black_box(input)); assert_eq!(black_box(len), 13); }, BatchSize::SmallInput, ) }); group.bench_function(BenchmarkId::new("new", "SupertypeInput"), |b| { b.iter_batched_ref( || { let db = salsa::DatabaseImpl::default(); // Prepopulate ingredients. let input = SupertypeInput::Input(Input::new( black_box(&db), black_box("hello, world!".to_owned()), )); let interned_input = SupertypeInput::InternedInput(InternedInput::new( black_box(&db), black_box("hello, world!".to_owned()), )); let len = either_length(black_box(&db), black_box(input)); assert_eq!(black_box(len), 13); let len = either_length(black_box(&db), black_box(interned_input)); assert_eq!(black_box(len), 13); db }, |db| { let input = SupertypeInput::Input(Input::new( black_box(db), black_box("hello, world!".to_owned()), )); let interned_input = SupertypeInput::InternedInput(InternedInput::new( black_box(db), black_box("hello, world!".to_owned()), )); let len = either_length(black_box(db), black_box(input)); assert_eq!(black_box(len), 13); let len = either_length(black_box(db), black_box(interned_input)); assert_eq!(black_box(len), 13); }, BatchSize::SmallInput, ) }); group.bench_function(BenchmarkId::new("amortized", "SupertypeInput"), |b| { b.iter_batched_ref( || { let db = salsa::DatabaseImpl::default(); let input = SupertypeInput::Input(Input::new( black_box(&db), black_box("hello, world!".to_owned()), )); let interned_input = SupertypeInput::InternedInput(InternedInput::new( black_box(&db), black_box("hello, world!".to_owned()), )); // we can't pass this along otherwise, and the lifetime is generally informational let interned_input: SupertypeInput<'static> = unsafe { transmute(interned_input) }; let len = either_length(black_box(&db), black_box(input)); assert_eq!(black_box(len), 13); let len = either_length(black_box(&db), black_box(interned_input)); assert_eq!(black_box(len), 13); (db, input, interned_input) }, |&mut (ref db, input, interned_input)| { let len = either_length(black_box(db), black_box(input)); assert_eq!(black_box(len), 13); let len = either_length(black_box(db), black_box(interned_input)); assert_eq!(black_box(len), 13); }, BatchSize::SmallInput, ) }); group.finish(); } criterion_group!(benches, mutating_inputs, inputs); criterion_main!(benches); salsa-0.26.2/benches/dataflow.rs000064400000000000000000000152431046102023000146010ustar 00000000000000//! Benchmark for fixpoint iteration cycle resolution. //! //! This benchmark simulates a (very simplified) version of a real dataflow analysis using fixpoint //! iteration. use std::collections::BTreeSet; use std::iter::IntoIterator; use codspeed_criterion_compat::{BatchSize, Criterion, criterion_group, criterion_main}; use salsa::{Database as Db, Setter}; /// A Use of a symbol. #[salsa::input] struct Use { reaching_definitions: Vec, } /// A Definition of a symbol, either of the form `base + increment` or `0 + increment`. #[salsa::input] struct Definition { base: Option, increment: usize, } #[derive(Eq, PartialEq, Clone, Debug, salsa::Update)] enum Type { Bottom, Values(Box<[usize]>), Top, } impl Type { fn join(tys: impl IntoIterator) -> Type { let mut result = Type::Bottom; for ty in tys.into_iter() { result = match (result, ty) { (result, Type::Bottom) => result, (_, Type::Top) => Type::Top, (Type::Top, _) => Type::Top, (Type::Bottom, ty) => ty, (Type::Values(a_ints), Type::Values(b_ints)) => { let mut set = BTreeSet::new(); set.extend(a_ints); set.extend(b_ints); Type::Values(set.into_iter().collect()) } } } result } } #[salsa::tracked(cycle_fn=use_cycle_recover, cycle_initial=use_cycle_initial)] fn infer_use(db: &dyn Db, u: Use) -> Type { let defs = u.reaching_definitions(db); match defs[..] { [] => Type::Bottom, [def] => infer_definition(db, def), _ => Type::join(defs.iter().map(|&def| infer_definition(db, def))), } } #[salsa::tracked(cycle_fn=def_cycle_recover, cycle_initial=def_cycle_initial)] fn infer_definition(db: &dyn Db, def: Definition) -> Type { let increment_ty = Type::Values(Box::from([def.increment(db)])); if let Some(base) = def.base(db) { let base_ty = infer_use(db, base); add(&base_ty, &increment_ty) } else { increment_ty } } fn def_cycle_initial(_db: &dyn Db, _id: salsa::Id, _def: Definition) -> Type { Type::Bottom } fn def_cycle_recover( _db: &dyn Db, cycle: &salsa::Cycle, _last_provisional_value: &Type, value: Type, _def: Definition, ) -> Type { cycle_recover(value, cycle.iteration()) } fn use_cycle_initial(_db: &dyn Db, _id: salsa::Id, _use: Use) -> Type { Type::Bottom } fn use_cycle_recover( _db: &dyn Db, cycle: &salsa::Cycle, _last_provisional_value: &Type, value: Type, _use: Use, ) -> Type { cycle_recover(value, cycle.iteration()) } fn cycle_recover(value: Type, count: u32) -> Type { match &value { Type::Bottom => value, Type::Values(_) => { if count > 4 { Type::Top } else { value } } Type::Top => value, } } fn add(a: &Type, b: &Type) -> Type { match (a, b) { (Type::Bottom, _) | (_, Type::Bottom) => Type::Bottom, (Type::Top, _) | (_, Type::Top) => Type::Top, (Type::Values(a_ints), Type::Values(b_ints)) => { let mut set = BTreeSet::new(); set.extend( a_ints .into_iter() .flat_map(|a| b_ints.into_iter().map(move |b| a + b)), ); Type::Values(set.into_iter().collect()) } } } fn dataflow(criterion: &mut Criterion) { criterion.bench_function("converge_diverge", |b| { b.iter_batched_ref( || { let mut db = salsa::DatabaseImpl::new(); let defx0 = Definition::new(&db, None, 0); let defy0 = Definition::new(&db, None, 0); let defx1 = Definition::new(&db, None, 0); let defy1 = Definition::new(&db, None, 0); let use_x = Use::new(&db, vec![defx0, defx1]); let use_y = Use::new(&db, vec![defy0, defy1]); defx1.set_base(&mut db).to(Some(use_y)); defy1.set_base(&mut db).to(Some(use_x)); // prewarm cache let _ = infer_use(&db, use_x); let _ = infer_use(&db, use_y); (db, defx1, use_x, use_y) }, |(db, defx1, use_x, use_y)| { // Set the increment on x to 0. defx1.set_increment(db).to(0); // Both symbols converge on 0. assert_eq!(infer_use(db, *use_x), Type::Values(Box::from([0]))); assert_eq!(infer_use(db, *use_y), Type::Values(Box::from([0]))); // Set the increment on x to 1. defx1.set_increment(db).to(1); // Now the loop diverges and we fall back to Top. assert_eq!(infer_use(db, *use_x), Type::Top); assert_eq!(infer_use(db, *use_y), Type::Top); }, BatchSize::LargeInput, ); }); } /// Emulates a data flow problem of the form: /// ```py /// self.x0 = self.x1 + self.x2 + self.x3 + self.x4 /// self.x1 = self.x0 + self.x2 + self.x3 + self.x4 /// self.x2 = self.x0 + self.x1 + self.x3 + self.x4 /// self.x3 = self.x0 + self.x1 + self.x2 + self.x4 /// self.x4 = 0 /// ``` fn nested(criterion: &mut Criterion) { criterion.bench_function("converge_diverge_nested", |b| { b.iter_batched_ref( || { let mut db = salsa::DatabaseImpl::new(); let def_x0 = Definition::new(&db, None, 0); let def_x1 = Definition::new(&db, None, 0); let def_x2 = Definition::new(&db, None, 0); let def_x3 = Definition::new(&db, None, 0); let def_x4 = Definition::new(&db, None, 0); let use_x0 = Use::new(&db, vec![def_x1, def_x2, def_x3, def_x4]); let use_x1 = Use::new(&db, vec![def_x0, def_x2, def_x3, def_x4]); let use_x2 = Use::new(&db, vec![def_x0, def_x1, def_x3, def_x4]); let use_x3 = Use::new(&db, vec![def_x0, def_x1, def_x3, def_x4]); def_x0.set_base(&mut db).to(Some(use_x0)); def_x1.set_base(&mut db).to(Some(use_x1)); def_x2.set_base(&mut db).to(Some(use_x2)); def_x3.set_base(&mut db).to(Some(use_x3)); (db, def_x0) }, |(db, def_x0)| { // All symbols converge on 0. assert_eq!(infer_definition(db, *def_x0), Type::Values(Box::from([0]))); }, BatchSize::LargeInput, ); }); } criterion_group!(benches, dataflow, nested); criterion_main!(benches); salsa-0.26.2/benches/incremental.rs000064400000000000000000000032611046102023000152760ustar 00000000000000use std::hint::black_box; use codspeed_criterion_compat::{BatchSize, Criterion, criterion_group, criterion_main}; use salsa::Setter; #[salsa::input] struct Input { field: usize, } #[salsa::tracked] struct Tracked<'db> { number: usize, } #[salsa::tracked(returns(ref))] #[inline(never)] fn index(db: &dyn salsa::Database, input: Input) -> Vec> { (0..input.field(db)).map(|i| Tracked::new(db, i)).collect() } #[salsa::tracked] #[inline(never)] fn root(db: &dyn salsa::Database, input: Input) -> usize { let index = index(db, input); index.len() } fn many_tracked_structs(criterion: &mut Criterion) { criterion.bench_function("many_tracked_structs", |b| { b.iter_batched_ref( || { let db = salsa::DatabaseImpl::new(); let input = Input::new(black_box(&db), black_box(1_000)); let input2 = Input::new(black_box(&db), black_box(1)); // prewarm cache let root1 = root(black_box(&db), black_box(input)); assert_eq!(black_box(root1), 1_000); let root2 = root(black_box(&db), black_box(input2)); assert_eq!(black_box(root2), 1); (db, input, input2) }, |(db, input, input2)| { // Make a change, but fetch the result for the other input input2.set_field(black_box(db)).to(black_box(2)); let result = root(black_box(db), *black_box(input)); assert_eq!(black_box(result), 1_000); }, BatchSize::LargeInput, ); }); } criterion_group!(benches, many_tracked_structs); criterion_main!(benches); salsa-0.26.2/examples/calc/compile.rs000064400000000000000000000004441046102023000155360ustar 00000000000000use crate::ir::SourceProgram; use crate::parser::parse_statements; use crate::type_check::type_check_program; #[salsa::tracked] pub fn compile(db: &dyn crate::Db, source_program: SourceProgram) { let program = parse_statements(db, source_program); type_check_program(db, program); } salsa-0.26.2/examples/calc/db.rs000064400000000000000000000033471046102023000145000ustar 00000000000000#[cfg(test)] use std::sync::{Arc, Mutex}; // ANCHOR: db_struct #[salsa::db] #[derive(Clone)] #[cfg_attr(not(test), derive(Default))] pub struct CalcDatabaseImpl { storage: salsa::Storage, // The logs are only used for testing and demonstrating reuse: #[cfg(test)] logs: Arc>>>, } #[cfg(test)] impl Default for CalcDatabaseImpl { fn default() -> Self { let logs = >>>>::default(); Self { storage: salsa::Storage::new(Some(Box::new({ let logs = logs.clone(); move |event| { eprintln!("Event: {event:?}"); // Log interesting events, if logging is enabled if let Some(logs) = &mut *logs.lock().unwrap() { // only log interesting events if let salsa::EventKind::WillExecute { .. } = event.kind { logs.push(format!("Event: {event:?}")); } } } }))), logs, } } } // ANCHOR_END: db_struct impl CalcDatabaseImpl { /// Enable logging of each salsa event. #[cfg(test)] pub fn enable_logging(&self) { let mut logs = self.logs.lock().unwrap(); if logs.is_none() { *logs = Some(vec![]); } } #[cfg(test)] #[allow(unused)] pub fn take_logs(&self) -> Vec { let mut logs = self.logs.lock().unwrap(); if let Some(logs) = &mut *logs { std::mem::take(logs) } else { vec![] } } } // ANCHOR: db_impl #[salsa::db] impl salsa::Database for CalcDatabaseImpl {} // ANCHOR_END: db_impl salsa-0.26.2/examples/calc/ir.rs000064400000000000000000000064031046102023000145210ustar 00000000000000#![allow(clippy::needless_borrow)] use ordered_float::OrderedFloat; // ANCHOR: input #[salsa::input(debug)] pub struct SourceProgram { #[returns(ref)] pub text: String, } // ANCHOR_END: input // ANCHOR: interned_ids #[salsa::interned(debug)] pub struct VariableId<'db> { #[returns(ref)] pub text: String, } #[salsa::interned(debug)] pub struct FunctionId<'db> { #[returns(ref)] pub text: String, } // ANCHOR_END: interned_ids // ANCHOR: program #[salsa::tracked(debug)] pub struct Program<'db> { #[tracked] #[returns(ref)] pub statements: Vec>, } // ANCHOR_END: program // ANCHOR: statements_and_expressions #[derive(Eq, PartialEq, Debug, Hash, salsa::Update)] pub struct Statement<'db> { pub span: Span<'db>, pub data: StatementData<'db>, } impl<'db> Statement<'db> { pub fn new(span: Span<'db>, data: StatementData<'db>) -> Self { Statement { span, data } } } #[derive(Eq, PartialEq, Debug, Hash, salsa::Update)] pub enum StatementData<'db> { /// Defines `fn () = ` Function(Function<'db>), /// Defines `print ` Print(Expression<'db>), } #[derive(Eq, PartialEq, Debug, Hash, salsa::Update)] pub struct Expression<'db> { pub span: Span<'db>, pub data: ExpressionData<'db>, } impl<'db> Expression<'db> { pub fn new(span: Span<'db>, data: ExpressionData<'db>) -> Self { Expression { span, data } } } #[derive(Eq, PartialEq, Debug, Hash, salsa::Update)] pub enum ExpressionData<'db> { Op(Box>, Op, Box>), Number(OrderedFloat), Variable(VariableId<'db>), Call(FunctionId<'db>, Vec>), } #[derive(Eq, PartialEq, Copy, Clone, Hash, Debug)] pub enum Op { Add, Subtract, Multiply, Divide, } // ANCHOR_END: statements_and_expressions // ANCHOR: functions #[salsa::tracked(debug)] pub struct Function<'db> { pub name: FunctionId<'db>, name_span: Span<'db>, #[tracked] #[returns(ref)] pub args: Vec>, #[tracked] #[returns(ref)] pub body: Expression<'db>, } // ANCHOR_END: functions #[salsa::tracked(debug)] pub struct Span<'db> { #[tracked] pub start: usize, #[tracked] pub end: usize, } // ANCHOR: diagnostic #[salsa::accumulator] #[derive(Debug)] #[allow(dead_code)] // Debug impl uses them pub struct Diagnostic { pub start: usize, pub end: usize, pub message: String, } // ANCHOR_END: diagnostic impl Diagnostic { pub fn new(start: usize, end: usize, message: String) -> Self { Diagnostic { start, end, message, } } #[cfg(test)] pub fn render(&self, db: &dyn crate::Db, src: SourceProgram) -> String { use annotate_snippets::*; let line_start = src.text(db)[..self.start].lines().count() + 1; Renderer::plain() .render( Level::Error.title(&self.message).snippet( Snippet::source(src.text(db)) .line_start(line_start) .origin("input") .fold(true) .annotation(Level::Error.span(self.start..self.end).label("here")), ), ) .to_string() } } salsa-0.26.2/examples/calc/main.rs000064400000000000000000000007001046102023000150250ustar 00000000000000use db::CalcDatabaseImpl; use ir::{Diagnostic, SourceProgram}; use salsa::Database as Db; mod compile; mod db; mod ir; mod parser; mod type_check; pub fn main() { let db: CalcDatabaseImpl = Default::default(); let source_program = SourceProgram::new(&db, String::new()); compile::compile(&db, source_program); let diagnostics = compile::compile::accumulated::(&db, source_program); eprintln!("{diagnostics:?}"); } salsa-0.26.2/examples/calc/parser.rs000064400000000000000000000757441046102023000154210ustar 00000000000000use ordered_float::OrderedFloat; use salsa::Accumulator; use crate::ir::{ Diagnostic, Expression, ExpressionData, Function, FunctionId, Op, Program, SourceProgram, Span, Statement, StatementData, VariableId, }; // ANCHOR: parse_statements #[salsa::tracked] pub fn parse_statements(db: &dyn crate::Db, source: SourceProgram) -> Program<'_> { // Get the source text from the database let source_text = source.text(db); // Create the parser let mut parser = Parser { db, source_text, position: 0, }; // Read in statements until we reach the end of the input let mut result = vec![]; loop { // Skip over any whitespace parser.skip_whitespace(); // If there are no more tokens, break if parser.peek().is_none() { break; } // Otherwise, there is more input, so parse a statement. if let Some(statement) = parser.parse_statement() { result.push(statement); } else { // If we failed, report an error at whatever position the parser // got stuck. We could recover here by skipping to the end of the line // or something like that. But we leave that as an exercise for the reader! parser.report_error(); break; } } Program::new(db, result) } // ANCHOR_END: parse_statements /// The parser tracks the current position in the input. /// /// There are parsing methods on the parser named `parse_foo`. Each such method tries to parse a /// `foo` at current position. Once they've recognized a `foo`, they return `Some(foo)` with the /// result, and they update the position. If there is a parse error /// (i.e., they don't recognize a `foo` at the current position), they return `None`, /// and they leave `position` at roughly the spot where parsing failed. You can use this to /// report errors and recover. /// /// There are some simpler method that read a single token (e.g., [`Parser::ch`] /// or [`Parser::word`]). These methods guarantee that, when they return `None`, the position /// is not changed apart from consuming whitespace. This allows them to be used to probe ahead /// and test the next token. struct Parser<'source, 'db> { db: &'db dyn crate::Db, source_text: &'source str, position: usize, } impl<'db> Parser<'_, 'db> { // Invoke `f` and, if it returns `None`, then restore the parsing position. fn probe(&mut self, f: impl FnOnce(&mut Self) -> Option) -> Option { let p = self.position; match f(self) { Some(v) => Some(v), _ => { self.position = p; None } } } // ANCHOR: report_error /// Report an error diagnostic at the current position. fn report_error(&self) { let next_position = match self.peek() { Some(ch) => self.position + ch.len_utf8(), None => self.position, }; Diagnostic { start: self.position, end: next_position, message: "unexpected character".to_string(), } .accumulate(self.db); } // ANCHOR_END: report_error fn peek(&self) -> Option { self.source_text[self.position..].chars().next() } // Returns a span ranging from `start_position` until the current position (exclusive) fn span_from(&self, start_position: usize) -> Span<'db> { Span::new(self.db, start_position, self.position) } fn consume(&mut self, ch: char) { debug_assert!(self.peek() == Some(ch)); self.position += ch.len_utf8(); } /// Skips whitespace and returns the new position. fn skip_whitespace(&mut self) -> usize { while let Some(ch) = self.peek() { if ch.is_whitespace() { self.consume(ch); } else { break; } } self.position } // ANCHOR: parse_statement fn parse_statement(&mut self) -> Option> { let start_position = self.skip_whitespace(); let word = self.word()?; if word == "fn" { let func = self.parse_function()?; Some(Statement::new( self.span_from(start_position), StatementData::Function(func), )) } else if word == "print" { let expr = self.parse_expression()?; Some(Statement::new( self.span_from(start_position), StatementData::Print(expr), )) } else { None } } // ANCHOR_END: parse_statement // ANCHOR: parse_function fn parse_function(&mut self) -> Option> { let start_position = self.skip_whitespace(); let name = self.word()?; let name_span = self.span_from(start_position); let name: FunctionId = FunctionId::new(self.db, name); // ^^^^^^^^^^^^^^^ // Create a new interned struct. self.ch('(')?; let args = self.parameters()?; self.ch(')')?; self.ch('=')?; let body = self.parse_expression()?; Some(Function::new(self.db, name, name_span, args, body)) // ^^^^^^^^^^^^^ // Create a new entity struct. } // ANCHOR_END: parse_function fn parse_expression(&mut self) -> Option> { self.parse_op_expression(Self::parse_expression1, Self::low_op) } fn low_op(&mut self) -> Option { if self.ch('+').is_some() { Some(Op::Add) } else if self.ch('-').is_some() { Some(Op::Subtract) } else { None } } /// Parses a high-precedence expression (times, div). /// /// On failure, skips arbitrary tokens. fn parse_expression1(&mut self) -> Option> { self.parse_op_expression(Self::parse_expression2, Self::high_op) } fn high_op(&mut self) -> Option { if self.ch('*').is_some() { Some(Op::Multiply) } else if self.ch('/').is_some() { Some(Op::Divide) } else { None } } fn parse_op_expression( &mut self, mut parse_expr: impl FnMut(&mut Self) -> Option>, mut op: impl FnMut(&mut Self) -> Option, ) -> Option> { let start_position = self.skip_whitespace(); let mut expr1 = parse_expr(self)?; while let Some(op) = op(self) { let expr2 = parse_expr(self)?; expr1 = Expression::new( self.span_from(start_position), ExpressionData::Op(Box::new(expr1), op, Box::new(expr2)), ); } Some(expr1) } /// Parses a "base expression" (no operators). /// /// On failure, skips arbitrary tokens. fn parse_expression2(&mut self) -> Option> { let start_position = self.skip_whitespace(); if let Some(w) = self.word() { if self.ch('(').is_some() { let f = FunctionId::new(self.db, w); let args = self.parse_expressions()?; self.ch(')')?; return Some(Expression::new( self.span_from(start_position), ExpressionData::Call(f, args), )); } let v = VariableId::new(self.db, w); Some(Expression::new( self.span_from(start_position), ExpressionData::Variable(v), )) } else if let Some(n) = self.number() { Some(Expression::new( self.span_from(start_position), ExpressionData::Number(OrderedFloat::from(n)), )) } else if self.ch('(').is_some() { let expr = self.parse_expression()?; self.ch(')')?; Some(expr) } else { None } } fn parse_expressions(&mut self) -> Option>> { let mut r = vec![]; loop { let expr = self.parse_expression()?; r.push(expr); if self.ch(',').is_none() { return Some(r); } } } /// Parses a list of variable identifiers, like `a, b, c`. /// No trailing commas because I am lazy. /// /// On failure, skips arbitrary tokens. fn parameters(&mut self) -> Option>> { let mut r = vec![]; loop { let name = self.word()?; let vid = VariableId::new(self.db, name); r.push(vid); if self.ch(',').is_none() { return Some(r); } } } /// Parses a single character. /// /// Even on failure, only skips whitespace. fn ch(&mut self, c: char) -> Option> { let start_position = self.skip_whitespace(); match self.peek() { Some(p) if c == p => { self.consume(c); Some(self.span_from(start_position)) } _ => None, } } /// Parses an identifier. /// /// Even on failure, only skips whitespace. fn word(&mut self) -> Option { self.skip_whitespace(); // In this loop, if we consume any characters, we always // return `Some`. let mut s = String::new(); let _position = self.position; while let Some(ch) = self.peek() { if ch.is_alphabetic() || ch == '_' || (!s.is_empty() && ch.is_numeric()) { s.push(ch); } else { break; } self.consume(ch); } if s.is_empty() { None } else { Some(s) } } /// Parses a number. /// /// Even on failure, only skips whitespace. fn number(&mut self) -> Option { let _start_position = self.skip_whitespace(); self.probe(|this| { // 👆 We need the call to `probe` here because we could consume // some characters like `3.1.2.3`, invoke `str::parse`, and then // still return `None`. let mut s = String::new(); while let Some(ch) = this.peek() { if ch.is_numeric() || ch == '.' { s.push(ch); } else { break; } this.consume(ch); } if s.is_empty() { None } else { str::parse(&s).ok() } }) } } // ANCHOR: parse_string /// Create a new database with the given source text and parse the result. /// Returns the statements and the diagnostics generated. #[cfg(test)] fn parse_string(source_text: &str) -> String { use salsa::Database; use crate::db::CalcDatabaseImpl; CalcDatabaseImpl::default().attach(|db| { // Create the source program let source_program = SourceProgram::new(db, source_text.to_string()); // Invoke the parser let statements = parse_statements(db, source_program); // Read out any diagnostics let accumulated = parse_statements::accumulated::(db, source_program); // Format the result as a string and return it format!("{:#?}", (statements, accumulated)) }) } // ANCHOR_END: parse_string // ANCHOR: parse_print #[test] fn parse_print() { let actual = parse_string("print 1 + 2"); let expected = expect_test::expect![[r#" ( 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, ), }, ), }, ), }, ], }, [], )"#]]; expected.assert_eq(&actual); } // ANCHOR_END: parse_print #[test] fn parse_example() { let actual = parse_string( " fn area_rectangle(w, h) = w * h fn area_circle(r) = 3.14 * r * r print area_rectangle(3, 4) print area_circle(1) print 11 * 2 ", ); let expected = expect_test::expect![[r#" ( Program { [salsa id]: Id(1400), statements: [ Statement { span: Span { [salsa id]: Id(409), start: 13, end: 57, }, data: Function( Function { [salsa id]: Id(1000), name: FunctionId { text: "area_rectangle", }, name_span: Span { [salsa id]: Id(400), start: 16, end: 30, }, args: [ VariableId { text: "w", }, VariableId { text: "h", }, ], body: Expression { span: Span { [salsa id]: Id(408), start: 39, end: 57, }, data: Op( Expression { span: Span { [salsa id]: Id(405), start: 39, end: 41, }, data: Variable( VariableId { text: "w", }, ), }, Multiply, Expression { span: Span { [salsa id]: Id(407), start: 43, end: 57, }, data: Variable( VariableId { text: "h", }, ), }, ), }, }, ), }, Statement { span: Span { [salsa id]: Id(415), start: 57, end: 102, }, data: Function( Function { [salsa id]: Id(1001), name: FunctionId { text: "area_circle", }, name_span: Span { [salsa id]: Id(40a), start: 60, end: 71, }, args: [ VariableId { text: "r", }, ], body: Expression { span: Span { [salsa id]: Id(414), start: 77, end: 102, }, data: Op( Expression { span: Span { [salsa id]: Id(411), start: 77, end: 86, }, data: Op( Expression { span: Span { [salsa id]: Id(40e), start: 77, end: 81, }, data: Number( 3.14, ), }, Multiply, Expression { span: Span { [salsa id]: Id(410), start: 84, end: 86, }, data: Variable( VariableId { text: "r", }, ), }, ), }, Multiply, Expression { span: Span { [salsa id]: Id(413), start: 88, end: 102, }, data: Variable( VariableId { text: "r", }, ), }, ), }, }, ), }, Statement { span: Span { [salsa id]: Id(41c), start: 102, end: 141, }, data: Print( Expression { span: Span { [salsa id]: Id(41b), start: 108, end: 128, }, data: Call( FunctionId { text: "area_rectangle", }, [ Expression { span: Span { [salsa id]: Id(417), start: 123, end: 124, }, data: Number( 3.0, ), }, Expression { span: Span { [salsa id]: Id(419), start: 126, end: 127, }, data: Number( 4.0, ), }, ], ), }, ), }, Statement { span: Span { [salsa id]: Id(421), start: 141, end: 174, }, data: Print( Expression { span: Span { [salsa id]: Id(420), start: 147, end: 161, }, data: Call( FunctionId { text: "area_circle", }, [ Expression { span: Span { [salsa id]: Id(41e), start: 159, end: 160, }, data: Number( 1.0, ), }, ], ), }, ), }, Statement { span: Span { [salsa id]: Id(426), start: 174, end: 195, }, data: Print( Expression { span: Span { [salsa id]: Id(425), start: 180, end: 186, }, data: Op( Expression { span: Span { [salsa id]: Id(422), start: 180, end: 182, }, data: Number( 11.0, ), }, Multiply, Expression { span: Span { [salsa id]: Id(424), start: 185, end: 186, }, data: Number( 2.0, ), }, ), }, ), }, ], }, [], )"#]]; expected.assert_eq(&actual); } #[test] fn parse_error() { let source_text: &str = "print 1 + + 2"; // 0123456789^ <-- this is the position 10, where the error is reported let actual = parse_string(source_text); let expected = expect_test::expect![[r#" ( Program { [salsa id]: Id(800), statements: [], }, [ Diagnostic { start: 10, end: 11, message: "unexpected character", }, ], )"#]]; expected.assert_eq(&actual); } #[test] fn parse_precedence() { // this parses as `(1 + (2 * 3)) + 4` let source_text: &str = "print 1 + 2 * 3 + 4"; let actual = parse_string(source_text); let expected = expect_test::expect![[r#" ( Program { [salsa id]: Id(800), statements: [ Statement { span: Span { [salsa id]: Id(40a), start: 0, end: 19, }, data: Print( Expression { span: Span { [salsa id]: Id(409), start: 6, end: 19, }, data: Op( Expression { span: Span { [salsa id]: Id(406), start: 6, end: 16, }, data: Op( Expression { span: Span { [salsa id]: Id(400), start: 6, end: 7, }, data: Number( 1.0, ), }, Add, Expression { span: Span { [salsa id]: Id(405), start: 10, end: 15, }, data: Op( Expression { span: Span { [salsa id]: Id(402), start: 10, end: 11, }, data: Number( 2.0, ), }, Multiply, Expression { span: Span { [salsa id]: Id(404), start: 14, end: 15, }, data: Number( 3.0, ), }, ), }, ), }, Add, Expression { span: Span { [salsa id]: Id(408), start: 18, end: 19, }, data: Number( 4.0, ), }, ), }, ), }, ], }, [], )"#]]; expected.assert_eq(&actual); } salsa-0.26.2/examples/calc/type_check.rs000064400000000000000000000165251046102023000162330ustar 00000000000000#[cfg(test)] use expect_test::expect; use salsa::Accumulator; #[cfg(test)] use test_log::test; use crate::ir::{ Diagnostic, Expression, Function, FunctionId, Program, Span, StatementData, VariableId, }; // ANCHOR: parse_statements #[salsa::tracked] pub fn type_check_program<'db>(db: &'db dyn crate::Db, program: Program<'db>) { for statement in program.statements(db) { match &statement.data { StatementData::Function(f) => type_check_function(db, *f, program), StatementData::Print(e) => CheckExpression::new(db, program, &[]).check(e), } } } #[salsa::tracked] pub fn type_check_function<'db>( db: &'db dyn crate::Db, function: Function<'db>, program: Program<'db>, ) { CheckExpression::new(db, program, function.args(db)).check(function.body(db)) } #[salsa::tracked] pub fn find_function<'db>( db: &'db dyn crate::Db, program: Program<'db>, name: FunctionId<'db>, ) -> Option> { program .statements(db) .iter() .flat_map(|s| match &s.data { StatementData::Function(f) if f.name(db) == name => Some(*f), _ => None, }) .next() } struct CheckExpression<'input, 'db> { db: &'db dyn crate::Db, program: Program<'db>, names_in_scope: &'input [VariableId<'db>], } impl<'input, 'db> CheckExpression<'input, 'db> { pub fn new( db: &'db dyn crate::Db, program: Program<'db>, names_in_scope: &'input [VariableId<'db>], ) -> Self { CheckExpression { db, program, names_in_scope, } } } impl<'db> CheckExpression<'_, 'db> { fn check(&self, expression: &Expression<'db>) { match &expression.data { crate::ir::ExpressionData::Op(left, _, right) => { self.check(left); self.check(right); } crate::ir::ExpressionData::Number(_) => {} crate::ir::ExpressionData::Variable(v) => { if !self.names_in_scope.contains(v) { self.report_error( expression.span, format!("the variable `{}` is not declared", v.text(self.db)), ); } } crate::ir::ExpressionData::Call(f, args) => { if self.find_function(*f).is_none() { self.report_error( expression.span, format!("the function `{}` is not declared", f.text(self.db)), ); } for arg in args { self.check(arg); } } } } fn find_function(&self, f: FunctionId<'db>) -> Option> { find_function(self.db, self.program, f) } fn report_error(&self, span: Span, message: String) { Diagnostic::new(span.start(self.db), span.end(self.db), message).accumulate(self.db); } } /// Create a new database with the given source text and parse the result. /// Returns the statements and the diagnostics generated. #[cfg(test)] fn check_string( source_text: &str, expected_diagnostics: expect_test::Expect, edits: &[(&str, expect_test::Expect)], ) { use salsa::{Database, Setter}; use crate::db::CalcDatabaseImpl; use crate::ir::SourceProgram; use crate::parser::parse_statements; // Create the database let mut db = CalcDatabaseImpl::default(); db.enable_logging(); // Create the source program let source_program = SourceProgram::new(&db, source_text.to_string()); // Invoke the parser let program = parse_statements(&db, source_program); // Read out any diagnostics db.attach(|db| { let rendered_diagnostics: String = type_check_program::accumulated::(db, program) .into_iter() .map(|d| d.render(db, source_program)) .collect::>() .join("\n"); expected_diagnostics.assert_eq(&rendered_diagnostics); }); // Apply edits and check diagnostics/logs after each one for (new_source_text, expected_diagnostics) in edits { source_program .set_text(&mut db) .to(new_source_text.to_string()); db.attach(|db| { let program = parse_statements(db, source_program); expected_diagnostics .assert_debug_eq(&type_check_program::accumulated::(db, program)); }); } } #[test] fn check_print() { check_string("print 1 + 2", expect![""], &[]); } #[test] fn check_bad_variable_in_program() { check_string( "print a + b", expect![[r#" error: the variable `a` is not declared --> input:2:7 | 2 | print a + b | ^^ here | error: the variable `b` is not declared --> input:2:11 | 2 | print a + b | ^ here |"#]], &[], ); } #[test] fn check_bad_function_in_program() { check_string( "print a(22)", expect![[r#" error: the function `a` is not declared --> input:2:7 | 2 | print a(22) | ^^^^^ here |"#]], &[], ); } #[test] fn check_bad_variable_in_function() { check_string( " fn add_one(a) = a + b print add_one(22) ", expect![[r#" error: the variable `b` is not declared --> input:4:33 | 4 | fn add_one(a) = a + b | _________________________________^ 5 | | print add_one(22) | |____________^ here |"#]], &[], ); } #[test] fn check_bad_function_in_function() { check_string( " fn add_one(a) = add_two(a) + b print add_one(22) ", expect![[r#" error: the function `add_two` is not declared --> input:4:29 | 4 | fn add_one(a) = add_two(a) + b | ^^^^^^^^^^ here | error: the variable `b` is not declared --> input:4:42 | 4 | fn add_one(a) = add_two(a) + b | __________________________________________^ 5 | | print add_one(22) | |____________^ here |"#]], &[], ); } #[test] fn fix_bad_variable_in_function() { check_string( " fn double(a) = a * b fn quadruple(a) = double(double(a)) print quadruple(2) ", expect![[r#" error: the variable `b` is not declared --> input:4:32 | 4 | fn double(a) = a * b | ________________________________^ 5 | | fn quadruple(a) = double(double(a)) | |____________^ here |"#]], &[( " fn double(a) = a * 2 fn quadruple(a) = double(double(a)) print quadruple(2) ", expect![[r#" [] "#]], )], ); } salsa-0.26.2/examples/lazy-input/inputs/a000064400000000000000000000000071046102023000164320ustar 000000000000002 ./aa salsa-0.26.2/examples/lazy-input/inputs/aa000064400000000000000000000000021046102023000165660ustar 000000000000008 salsa-0.26.2/examples/lazy-input/inputs/b000064400000000000000000000000021046102023000164260ustar 000000000000004 salsa-0.26.2/examples/lazy-input/inputs/start000064400000000000000000000000121046102023000173430ustar 000000000000001 ./a ./b salsa-0.26.2/examples/lazy-input/main.rs000064400000000000000000000161521046102023000162470ustar 00000000000000#![allow(unreachable_patterns)] // FIXME(rust-lang/rust#129031): regression in nightly use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::time::Duration; use crossbeam_channel::{Sender, unbounded}; use dashmap::DashMap; use dashmap::mapref::entry::Entry; use eyre::{Context, Report, Result, eyre}; use notify_debouncer_mini::notify::{RecommendedWatcher, RecursiveMode}; use notify_debouncer_mini::{DebounceEventResult, Debouncer, new_debouncer}; use salsa::{Accumulator, Setter, Storage}; // ANCHOR: main fn main() -> Result<()> { // Create the channel to receive file change events. let (tx, rx) = unbounded(); let mut db = LazyInputDatabase::new(tx); let initial_file_path = std::env::args_os() .nth(1) .ok_or_else(|| eyre!("Usage: ./lazy-input "))?; // Create the initial input using the input method so that changes to it // will be watched like the other files. let initial = db.input(initial_file_path.into())?; loop { // Compile the code starting at the provided input, this will read other // needed files using the on-demand mechanism. let sum = compile(&db, initial); let diagnostics = compile::accumulated::(&db, initial); if diagnostics.is_empty() { println!("Sum is: {sum}"); } else { for diagnostic in diagnostics { println!("{}", diagnostic.0); } } for log in db.logs.lock().unwrap().drain(..) { eprintln!("{log}"); } // Wait for file change events, the output can't change unless the // inputs change. for event in rx.recv()?.unwrap() { let path = event.path.canonicalize().wrap_err_with(|| { format!("Failed to canonicalize path {}", event.path.display()) })?; let file = match db.files.get(&path) { Some(file) => *file, None => continue, }; // `path` has changed, so read it and update the contents to match. // This creates a new revision and causes the incremental algorithm // to kick in, just like any other update to a salsa input. let contents = std::fs::read_to_string(path) .wrap_err_with(|| format!("Failed to read file {}", event.path.display()))?; file.set_contents(&mut db).to(contents); } } } // ANCHOR_END: main // ANCHOR: db #[salsa::input] struct File { path: PathBuf, #[returns(ref)] contents: String, } #[salsa::db] trait Db: salsa::Database { fn input(&self, path: PathBuf) -> Result; } #[salsa::db] #[derive(Clone)] struct LazyInputDatabase { storage: Storage, logs: Arc>>, files: DashMap, file_watcher: Arc>>, } impl LazyInputDatabase { fn new(tx: Sender) -> Self { let logs: Arc>> = Default::default(); Self { storage: Storage::new(Some(Box::new({ let logs = logs.clone(); move |event| { // don't log boring events if let salsa::EventKind::WillExecute { .. } = event.kind { logs.lock().unwrap().push(format!("{event:?}")); } } }))), logs, files: DashMap::new(), file_watcher: Arc::new(Mutex::new( new_debouncer(Duration::from_secs(1), tx).unwrap(), )), } } } #[salsa::db] impl salsa::Database for LazyInputDatabase {} #[salsa::db] impl Db for LazyInputDatabase { fn input(&self, path: PathBuf) -> Result { let path = path .canonicalize() .wrap_err_with(|| format!("Failed to read {}", path.display()))?; Ok(match self.files.entry(path.clone()) { // If the file already exists in our cache then just return it. Entry::Occupied(entry) => *entry.get(), // If we haven't read this file yet set up the watch, read the // contents, store it in the cache, and return it. Entry::Vacant(entry) => { // Set up the watch before reading the contents to try to avoid // race conditions. let watcher = &mut *self.file_watcher.lock().unwrap(); watcher .watcher() .watch(&path, RecursiveMode::NonRecursive) .unwrap(); let contents = std::fs::read_to_string(&path) .wrap_err_with(|| format!("Failed to read {}", path.display()))?; *entry.insert(File::new(self, path, contents)) } }) } } // ANCHOR_END: db #[salsa::accumulator] struct Diagnostic(String); impl Diagnostic { fn push_error(db: &dyn Db, file: File, error: Report) { Diagnostic(format!( "Error in file {}: {:?}\n", file.path(db) .file_name() .unwrap_or_else(|| "".as_ref()) .to_string_lossy(), error, )) .accumulate(db); } } #[salsa::tracked] struct ParsedFile<'db> { value: u32, #[returns(ref)] links: Vec>, } #[salsa::tracked] fn compile(db: &dyn Db, input: File) -> u32 { let parsed = parse(db, input); sum(db, parsed) } #[salsa::tracked] fn parse(db: &dyn Db, input: File) -> ParsedFile<'_> { let mut lines = input.contents(db).lines(); let value = match lines.next().map(|line| (line.parse::(), line)) { Some((Ok(num), _)) => num, Some((Err(e), line)) => { Diagnostic::push_error( db, input, Report::new(e).wrap_err(format!( "First line ({line}) could not be parsed as an integer" )), ); 0 } None => { Diagnostic::push_error(db, input, eyre!("File must contain an integer")); 0 } }; let links = lines .filter_map(|path| { let relative_path = match path.parse::() { Ok(path) => path, Err(err) => { Diagnostic::push_error( db, input, Report::new(err).wrap_err(format!("Failed to parse path: {path}")), ); return None; } }; let link_path = input.path(db).parent().unwrap().join(relative_path); match db.input(link_path) { Ok(file) => Some(parse(db, file)), Err(err) => { Diagnostic::push_error(db, input, err); None } } }) .collect(); ParsedFile::new(db, value, links) } #[salsa::tracked] fn sum<'db>(db: &'db dyn Db, input: ParsedFile<'db>) -> u32 { input.value(db) + input .links(db) .iter() .map(|&file| sum(db, file)) .sum::() } salsa-0.26.2/justfile000064400000000000000000000003041046102023000125630ustar 00000000000000test: cargo test --workspace --all-targets --no-fail-fast miri: cargo +nightly miri test --no-fail-fast shuttle: cargo nextest run --features shuttle --test parallel all: test miri salsa-0.26.2/release-plz.toml000064400000000000000000000002561046102023000141410ustar 00000000000000[[package]] name = "salsa" version_group = "salsa" [[package]] name = "salsa-macros" version_group = "salsa" [[package]] name = "salsa-macro-rules" version_group = "salsa" salsa-0.26.2/src/accumulator/accumulated.rs000064400000000000000000000021571046102023000167660ustar 00000000000000use std::any::Any; use std::fmt::Debug; use crate::accumulator::Accumulator; #[derive(Clone, Debug)] pub(crate) struct Accumulated { values: Vec, } pub(crate) trait AnyAccumulated: Any + Send + Sync { fn as_dyn_any(&self) -> &dyn Any; fn as_dyn_any_mut(&mut self) -> &mut dyn Any; } impl Accumulated { pub fn push(&mut self, value: A) { self.values.push(value); } pub fn extend_with_accumulated<'slf>(&'slf self, values: &mut Vec<&'slf A>) { values.extend(&self.values); } } impl Default for Accumulated { fn default() -> Self { Self { values: Default::default(), } } } impl AnyAccumulated for Accumulated where A: Accumulator, { fn as_dyn_any(&self) -> &dyn Any { self } fn as_dyn_any_mut(&mut self) -> &mut dyn Any { self } } impl dyn AnyAccumulated { pub fn accumulate(&mut self, value: A) { self.as_dyn_any_mut() .downcast_mut::>() .unwrap() .push(value); } } salsa-0.26.2/src/accumulator/accumulated_map.rs000064400000000000000000000077701046102023000176310ustar 00000000000000use std::ops; use rustc_hash::FxBuildHasher; use crate::IngredientIndex; use crate::accumulator::accumulated::Accumulated; use crate::accumulator::{Accumulator, AnyAccumulated}; use crate::sync::atomic::{AtomicBool, Ordering}; #[derive(Default)] pub struct AccumulatedMap { map: hashbrown::HashMap, FxBuildHasher>, } impl std::fmt::Debug for AccumulatedMap { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AccumulatedMap") .field("map", &self.map.keys()) .finish() } } impl AccumulatedMap { pub fn accumulate(&mut self, index: IngredientIndex, value: A) { self.map .entry(index) .or_insert_with(|| >>::default()) .accumulate(value); } pub fn extend_with_accumulated<'slf, A: Accumulator>( &'slf self, index: IngredientIndex, output: &mut Vec<&'slf A>, ) { let Some(a) = self.map.get(&index) else { return; }; a.as_dyn_any() .downcast_ref::>() .unwrap() .extend_with_accumulated(output); } pub fn is_empty(&self) -> bool { self.map.is_empty() } pub fn clear(&mut self) { self.map.clear() } pub fn allocation_size(&self) -> usize { self.map.allocation_size() } } /// Tracks whether any input read during a query's execution has any accumulated values. /// /// Knowning whether any input has accumulated values makes aggregating the accumulated values /// cheaper because we can skip over entire subtrees. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub enum InputAccumulatedValues { /// The query nor any of its inputs have any accumulated values. #[default] Empty, /// The query or any of its inputs have at least one accumulated value. Any, } impl InputAccumulatedValues { pub const fn is_any(self) -> bool { matches!(self, Self::Any) } pub const fn is_empty(self) -> bool { matches!(self, Self::Empty) } pub fn or_else(self, other: impl FnOnce() -> Self) -> Self { if self.is_any() { Self::Any } else { other() } } } impl ops::BitOr for InputAccumulatedValues { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { match (self, rhs) { (Self::Any, _) | (_, Self::Any) => Self::Any, (Self::Empty, Self::Empty) => Self::Empty, } } } impl ops::BitOrAssign for InputAccumulatedValues { fn bitor_assign(&mut self, rhs: Self) { *self = *self | rhs; } } #[derive(Debug, Default)] pub struct AtomicInputAccumulatedValues(AtomicBool); impl Clone for AtomicInputAccumulatedValues { fn clone(&self) -> Self { Self(AtomicBool::new(self.0.load(Ordering::Relaxed))) } } impl AtomicInputAccumulatedValues { pub(crate) fn new(accumulated_inputs: InputAccumulatedValues) -> Self { Self(AtomicBool::new(accumulated_inputs.is_any())) } pub(crate) fn store(&self, accumulated: InputAccumulatedValues) { self.0.store(accumulated.is_any(), Ordering::Release); } pub(crate) fn load(&self) -> InputAccumulatedValues { if self.0.load(Ordering::Acquire) { InputAccumulatedValues::Any } else { InputAccumulatedValues::Empty } } } #[cfg(test)] mod tests { use super::*; #[test] fn atomic_input_accumulated_values() { let val = AtomicInputAccumulatedValues::new(InputAccumulatedValues::Empty); assert_eq!(val.load(), InputAccumulatedValues::Empty); val.store(InputAccumulatedValues::Any); assert_eq!(val.load(), InputAccumulatedValues::Any); let val = AtomicInputAccumulatedValues::new(InputAccumulatedValues::Any); assert_eq!(val.load(), InputAccumulatedValues::Any); val.store(InputAccumulatedValues::Empty); assert_eq!(val.load(), InputAccumulatedValues::Empty); } } salsa-0.26.2/src/accumulator.rs000064400000000000000000000103601046102023000144720ustar 00000000000000//! Basic test of accumulator functionality. use std::any::{Any, TypeId}; use std::fmt; use std::marker::PhantomData; use std::panic::UnwindSafe; use accumulated::{Accumulated, AnyAccumulated}; use crate::function::VerifyResult; use crate::hash::{FxHashSet, FxIndexSet}; use crate::ingredient::{Ingredient, Jar}; use crate::plumbing::ZalsaLocal; use crate::sync::Arc; use crate::table::memo::MemoTableTypes; use crate::zalsa::{IngredientIndex, JarKind, Zalsa}; use crate::zalsa_local::QueryEdge; use crate::{Database, DatabaseKeyIndex, Id, Revision}; mod accumulated; pub(crate) mod accumulated_map; /// Trait implemented on the struct that user annotated with `#[salsa::accumulator]`. /// The `Self` type is therefore the types to be accumulated. pub trait Accumulator: Send + Sync + Any + Sized + UnwindSafe { const DEBUG_NAME: &'static str; /// Accumulate an instance of this in the database for later retrieval. fn accumulate(self, db: &Db) where Db: ?Sized + Database; } pub struct JarImpl { phantom: PhantomData, } impl Default for JarImpl { fn default() -> Self { Self { phantom: Default::default(), } } } impl Jar for JarImpl { fn create_ingredients( _zalsa: &mut Zalsa, first_index: IngredientIndex, ) -> Vec> { vec![Box::new(>::new(first_index))] } fn id_struct_type_id() -> TypeId { TypeId::of::() } } pub struct IngredientImpl { index: IngredientIndex, phantom: PhantomData>, } impl IngredientImpl { /// Find the accumulator ingredient for `A` in the database, if any. pub fn from_zalsa(zalsa: &Zalsa) -> Option<&Self> { let index = zalsa.lookup_jar_by_type::>(); let ingredient = zalsa.lookup_ingredient(index).assert_type::(); Some(ingredient) } pub fn new(index: IngredientIndex) -> Self { Self { index, phantom: PhantomData, } } pub fn push(&self, zalsa_local: &ZalsaLocal, value: A) { if let Err(()) = zalsa_local.accumulate(self.index, value) { panic!("cannot accumulate values outside of an active tracked function"); } } pub fn index(&self) -> IngredientIndex { self.index } } impl Ingredient for IngredientImpl { fn location(&self) -> &'static crate::ingredient::Location { &const { crate::ingredient::Location { file: file!(), line: line!(), } } } fn ingredient_index(&self) -> IngredientIndex { self.index } unsafe fn maybe_changed_after( &self, _zalsa: &crate::zalsa::Zalsa, _db: crate::database::RawDatabase<'_>, _input: Id, _revision: Revision, ) -> VerifyResult { panic!("nothing should ever depend on an accumulator directly") } fn collect_minimum_serialized_edges( &self, _zalsa: &Zalsa, _edge: QueryEdge, _serialized_edges: &mut FxIndexSet, _visited_edges: &mut FxHashSet, ) { panic!("nothing should ever depend on an accumulator directly") } fn debug_name(&self) -> &'static str { A::DEBUG_NAME } fn jar_kind(&self) -> JarKind { JarKind::Struct } fn memo_table_types(&self) -> &Arc { unreachable!("accumulator does not allocate pages") } fn memo_table_types_mut(&mut self) -> &mut Arc { unreachable!("accumulator does not allocate pages") } fn flatten_cycle_head_dependencies( &self, _zalsa: &Zalsa, _id: Id, _flattened_input_outputs: &mut FxIndexSet, _seen: &mut FxHashSet, ) { panic!("nothing should ever depend on an accumulator directly") } } impl std::fmt::Debug for IngredientImpl where A: Accumulator, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct(std::any::type_name::()) .field("index", &self.index) .finish() } } salsa-0.26.2/src/active_query.rs000064400000000000000000000425011046102023000146550ustar 00000000000000use std::{fmt, mem, ops}; use crate::Revision; #[cfg(feature = "accumulator")] use crate::accumulator::{ Accumulator, accumulated_map::{AccumulatedMap, AtomicInputAccumulatedValues, InputAccumulatedValues}, }; use crate::hash::FxIndexSet; use crate::key::DatabaseKeyIndex; use crate::runtime::Stamp; use crate::sync::atomic::AtomicBool; use crate::tracked_struct::{Disambiguator, DisambiguatorMap, IdentityHash, IdentityMap}; use crate::zalsa_local::{ QueryEdge, QueryEdgeKind, QueryOrigin, QueryRevisions, QueryRevisionsExtra, }; use crate::{ Id, cycle::{CycleHeads, IterationCount}, }; use crate::{durability::Durability, tracked_struct::Identity}; #[derive(Debug)] pub(crate) struct ActiveQuery { /// What query is executing pub(crate) database_key_index: DatabaseKeyIndex, /// Minimum durability of inputs observed so far. durability: Durability, /// Maximum revision of all inputs observed. If we observe an /// untracked read, this will be set to the most recent revision. changed_at: Revision, /// Inputs: Set of subqueries that were accessed thus far. /// Outputs: Tracks values written by this query. Could be... /// /// * invocations of `specify` /// * accumulators pushed to input_outputs: FxIndexSet, /// True if there was an untracked read. untracked_read: bool, /// When new tracked structs are created, their data is hashed, and the resulting /// hash is added to this map. If it is not present, then the disambiguator is 0. /// Otherwise it is 1 more than the current value (which is incremented). /// /// This table starts empty as the query begins and is gradually populated. /// Note that if a query executes in 2 different revisions but creates the same /// set of tracked structs, they will get the same disambiguator values. disambiguator_map: DisambiguatorMap, /// Map from tracked struct keys (which include the hash + disambiguator) to their /// final id. tracked_struct_ids: IdentityMap, /// Stores the values accumulated to the given ingredient. /// The type of accumulated value is erased but known to the ingredient. #[cfg(feature = "accumulator")] accumulated: AccumulatedMap, /// [`InputAccumulatedValues::Empty`] if any input read during the query's execution /// has any accumulated values. #[cfg(feature = "accumulator")] accumulated_inputs: InputAccumulatedValues, /// Provisional cycle results that this query depends on. cycle_heads: CycleHeads, /// If this query is a cycle head, iteration count of that cycle. iteration_count: IterationCount, } impl ActiveQuery { pub(super) fn seed_iteration( &mut self, durability: Durability, changed_at: Revision, edges: &[QueryEdge], untracked_read: bool, active_tracked_ids: &[(Identity, Id)], ) { assert!(self.input_outputs.is_empty()); // Copy over outputs for `diff_outputs`, don't copy inputs because cycle heads // flatten all input dependencies. self.input_outputs.extend( edges .iter() .filter(|edge| matches!(edge.kind(), QueryEdgeKind::Output(_))) .copied(), ); self.durability = self.durability.min(durability); self.changed_at = self.changed_at.max(changed_at); self.untracked_read |= untracked_read; self.disambiguator_map .seed(active_tracked_ids.iter().map(|(id, _)| id)); // Mark all tracked structs from the previous iteration as active. self.tracked_struct_ids .mark_all_active(active_tracked_ids.iter().copied()); self.disambiguator_map .seed(active_tracked_ids.iter().map(|(id, _)| id)); } pub(super) fn take_cycle_heads(&mut self) -> CycleHeads { std::mem::take(&mut self.cycle_heads) } pub(super) fn add_read( &mut self, input: DatabaseKeyIndex, durability: Durability, changed_at: Revision, cycle_heads: &CycleHeads, #[cfg(feature = "accumulator")] has_accumulated: bool, #[cfg(feature = "accumulator")] accumulated_inputs: &AtomicInputAccumulatedValues, ) { self.durability = self.durability.min(durability); self.changed_at = self.changed_at.max(changed_at); self.input_outputs.insert(QueryEdge::input(input)); self.cycle_heads.extend(cycle_heads); #[cfg(feature = "accumulator")] { self.accumulated_inputs = self.accumulated_inputs.or_else(|| match has_accumulated { true => InputAccumulatedValues::Any, false => accumulated_inputs.load(), }); } } pub(super) fn add_read_simple( &mut self, input: DatabaseKeyIndex, durability: Durability, revision: Revision, ) { self.durability = self.durability.min(durability); self.changed_at = self.changed_at.max(revision); self.input_outputs.insert(QueryEdge::input(input)); } pub(super) fn add_untracked_read(&mut self, changed_at: Revision) { self.untracked_read = true; self.durability = Durability::MIN; self.changed_at = changed_at; } #[cfg(feature = "accumulator")] pub(super) fn accumulate(&mut self, index: crate::IngredientIndex, value: impl Accumulator) { self.accumulated.accumulate(index, value); } /// Adds a key to our list of outputs. pub(super) fn add_output(&mut self, key: DatabaseKeyIndex) { self.input_outputs.insert(QueryEdge::output(key)); } /// True if the given key was output by this query. pub(super) fn disambiguate(&mut self, key: IdentityHash) -> Disambiguator { self.disambiguator_map.disambiguate(key) } pub(super) fn stamp(&self) -> Stamp { Stamp { durability: self.durability, changed_at: self.changed_at, } } pub(crate) fn tracked_struct_ids(&self) -> &IdentityMap { &self.tracked_struct_ids } pub(crate) fn tracked_struct_ids_mut(&mut self) -> &mut IdentityMap { &mut self.tracked_struct_ids } } impl ActiveQuery { fn new(database_key_index: DatabaseKeyIndex, iteration_count: IterationCount) -> Self { ActiveQuery { database_key_index, durability: Durability::MAX, changed_at: Revision::start(), input_outputs: FxIndexSet::default(), untracked_read: false, disambiguator_map: Default::default(), tracked_struct_ids: Default::default(), cycle_heads: Default::default(), iteration_count, #[cfg(feature = "accumulator")] accumulated: Default::default(), #[cfg(feature = "accumulator")] accumulated_inputs: Default::default(), } } fn top_into_revisions(&mut self) -> CompletedQuery { let &mut Self { database_key_index: _, durability, changed_at, ref mut input_outputs, untracked_read, ref mut disambiguator_map, ref mut tracked_struct_ids, ref mut cycle_heads, iteration_count, #[cfg(feature = "accumulator")] ref mut accumulated, #[cfg(feature = "accumulator")] accumulated_inputs, } = self; let origin = if untracked_read { QueryOrigin::derived_untracked(input_outputs.drain(..).collect()) } else { QueryOrigin::derived(input_outputs.drain(..).collect()) }; disambiguator_map.clear(); #[cfg(feature = "accumulator")] let accumulated_inputs = AtomicInputAccumulatedValues::new(accumulated_inputs); let verified_final = cycle_heads.is_empty(); let (active_tracked_structs, stale_tracked_structs) = tracked_struct_ids.drain(); let extra = QueryRevisionsExtra::new( #[cfg(feature = "accumulator")] mem::take(accumulated), active_tracked_structs, mem::take(cycle_heads), iteration_count, ); let revisions = QueryRevisions { changed_at, durability, origin, #[cfg(feature = "accumulator")] accumulated_inputs, verified_final: AtomicBool::new(verified_final), extra, }; CompletedQuery { revisions, stale_tracked_structs, } } fn clear(&mut self) { let Self { database_key_index: _, durability: _, changed_at: _, input_outputs, untracked_read: _, disambiguator_map, tracked_struct_ids, cycle_heads, iteration_count, #[cfg(feature = "accumulator")] accumulated, #[cfg(feature = "accumulator")] accumulated_inputs: _, } = self; input_outputs.clear(); disambiguator_map.clear(); tracked_struct_ids.clear(); *cycle_heads = Default::default(); *iteration_count = IterationCount::initial(); #[cfg(feature = "accumulator")] accumulated.clear(); } fn reset_for( &mut self, new_database_key_index: DatabaseKeyIndex, new_iteration_count: IterationCount, ) { let Self { database_key_index, durability, changed_at, input_outputs, untracked_read, disambiguator_map, tracked_struct_ids, cycle_heads, iteration_count, #[cfg(feature = "accumulator")] accumulated, #[cfg(feature = "accumulator")] accumulated_inputs, } = self; *database_key_index = new_database_key_index; *durability = Durability::MAX; *changed_at = Revision::start(); *untracked_read = false; *iteration_count = new_iteration_count; debug_assert!( input_outputs.is_empty(), "`ActiveQuery::clear` or `ActiveQuery::into_revisions` should've been called" ); debug_assert!( disambiguator_map.is_empty(), "`ActiveQuery::clear` or `ActiveQuery::into_revisions` should've been called" ); debug_assert!( tracked_struct_ids.is_empty(), "`ActiveQuery::clear` or `ActiveQuery::into_revisions` should've been called" ); debug_assert!( cycle_heads.is_empty(), "`ActiveQuery::clear` or `ActiveQuery::into_revisions` should've been called" ); #[cfg(feature = "accumulator")] { *accumulated_inputs = Default::default(); debug_assert!( accumulated.is_empty(), "`ActiveQuery::clear` or `ActiveQuery::into_revisions` should've been called" ); } } } #[derive(Default)] pub(crate) struct QueryStack { stack: Vec, len: usize, } impl std::fmt::Debug for QueryStack { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if f.alternate() { f.debug_list() .entries(self.stack.iter().map(|q| q.database_key_index)) .finish() } else { f.debug_struct("QueryStack") .field("stack", &self.stack) .field("len", &self.len) .finish() } } } impl ops::Deref for QueryStack { type Target = [ActiveQuery]; #[inline(always)] fn deref(&self) -> &Self::Target { &self.stack[..self.len] } } impl ops::DerefMut for QueryStack { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.stack[..self.len] } } impl QueryStack { pub(crate) fn push_new_query( &mut self, database_key_index: DatabaseKeyIndex, iteration_count: IterationCount, ) { if self.len < self.stack.len() { self.stack[self.len].reset_for(database_key_index, iteration_count); } else { self.stack .push(ActiveQuery::new(database_key_index, iteration_count)); } self.len += 1; } #[cfg(debug_assertions)] pub(crate) fn len(&self) -> usize { self.len } pub(crate) fn pop_into_revisions( &mut self, key: DatabaseKeyIndex, #[cfg(debug_assertions)] push_len: usize, ) -> CompletedQuery { #[cfg(debug_assertions)] assert_eq!(push_len, self.len(), "unbalanced push/pop"); debug_assert_ne!(self.len, 0, "too many pops"); self.len -= 1; debug_assert_eq!( self.stack[self.len].database_key_index, key, "unbalanced push/pop" ); self.stack[self.len].top_into_revisions() } pub(crate) fn pop(&mut self, key: DatabaseKeyIndex, #[cfg(debug_assertions)] push_len: usize) { #[cfg(debug_assertions)] assert_eq!(push_len, self.len(), "unbalanced push/pop"); debug_assert_ne!(self.len, 0, "too many pops"); self.len -= 1; debug_assert_eq!( self.stack[self.len].database_key_index, key, "unbalanced push/pop" ); self.stack[self.len].clear() } } /// The state of a completed query. pub(crate) struct CompletedQuery { /// Inputs and outputs accumulated during query execution. pub(crate) revisions: QueryRevisions, /// The keys of any tracked structs that were created in a previous execution of the /// query but not the current one, and should be marked as stale. pub(crate) stale_tracked_structs: Vec<(Identity, Id)>, } struct CapturedQuery { database_key_index: DatabaseKeyIndex, durability: Durability, changed_at: Revision, cycle_heads: CycleHeads, iteration_count: IterationCount, } impl fmt::Debug for CapturedQuery { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut debug_struct = f.debug_struct("CapturedQuery"); debug_struct .field("database_key_index", &self.database_key_index) .field("durability", &self.durability) .field("changed_at", &self.changed_at); if !self.cycle_heads.is_empty() { debug_struct .field("cycle_heads", &self.cycle_heads) .field("iteration_count", &self.iteration_count); } debug_struct.finish() } } pub struct Backtrace(Box<[CapturedQuery]>); impl Backtrace { pub fn capture() -> Option { crate::with_attached_database(|db| { db.zalsa_local().try_with_query_stack(|stack| { Backtrace( stack .iter() .rev() .map(|query| CapturedQuery { database_key_index: query.database_key_index, durability: query.durability, changed_at: query.changed_at, cycle_heads: query.cycle_heads.clone(), iteration_count: query.iteration_count, }) .collect(), ) }) })? } } impl fmt::Debug for Backtrace { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "Backtrace ")?; let mut dbg = fmt.debug_list(); for frame in &self.0 { dbg.entry(&frame); } dbg.finish() } } impl fmt::Display for Backtrace { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(fmt, "query stacktrace:")?; let full = fmt.alternate(); let indent = " "; for ( idx, &CapturedQuery { database_key_index, durability, changed_at, ref cycle_heads, iteration_count, }, ) in self.0.iter().enumerate() { write!(fmt, "{idx:>4}: {database_key_index:?}")?; if full { write!(fmt, " -> ({changed_at:?}, {durability:#?}")?; if !cycle_heads.is_empty() || !iteration_count.is_initial() { write!(fmt, ", iteration = {iteration_count}")?; } write!(fmt, ")")?; } writeln!(fmt)?; crate::attach::with_attached_database(|db| { let ingredient = db .zalsa() .lookup_ingredient(database_key_index.ingredient_index()); let loc = ingredient.location(); writeln!(fmt, "{indent}at {}:{}", loc.file, loc.line)?; if !cycle_heads.is_empty() { write!(fmt, "{indent}cycle heads: ")?; for (idx, head) in cycle_heads.iter().enumerate() { if idx != 0 { write!(fmt, ", ")?; } write!( fmt, "{:?} -> iteration = {}", head.database_key_index, head.iteration_count )?; } writeln!(fmt)?; } Ok(()) }) .transpose()?; } Ok(()) } } salsa-0.26.2/src/attach.rs000064400000000000000000000160371046102023000134260ustar 00000000000000use std::cell::Cell; use std::ptr::NonNull; use crate::Database; #[cfg(feature = "shuttle")] crate::sync::thread_local! { /// The thread-local state salsa requires for a given thread static ATTACHED: Attached = Attached::new(); } // shuttle's `thread_local` macro does not support const-initialization. #[cfg(not(feature = "shuttle"))] crate::sync::thread_local! { /// The thread-local state salsa requires for a given thread static ATTACHED: Attached = const { Attached::new() } } /// State that is specific to a single execution thread. /// /// Internally, this type uses ref-cells. /// /// **Note also that all mutations to the database handle (and hence /// to the local-state) must be undone during unwinding.** struct Attached { /// Pointer to the currently attached database. database: Cell>>, } impl Attached { const fn new() -> Self { Self { database: Cell::new(None), } } #[inline] fn attach(&self, db: &Db, op: impl FnOnce() -> R) -> R where Db: ?Sized + Database, { struct DbGuard<'s> { /// The database that *we* attached on scope entry. /// /// `None` if one was already attached by a parent scope. state: Option<&'s Attached>, } impl<'s> DbGuard<'s> { #[inline] fn new(attached: &'s Attached, db: &dyn Database) -> Self { match attached.database.get() { // A database is already attached, make sure it's the same as the new one. Some(current_db) => { let new_db = NonNull::from(db); if !std::ptr::addr_eq(current_db.as_ptr(), new_db.as_ptr()) { panic!( "Cannot change database mid-query. current: {current_db:?}, new: {new_db:?}" ); } Self { state: None } } // No database is attached, attach the new one. None => { attached.database.set(Some(NonNull::from(db))); Self { state: Some(attached), } } } } } impl Drop for DbGuard<'_> { #[inline] fn drop(&mut self) { // Reset database to null if we did anything in `DbGuard::new`. if let Some(attached) = self.state { if let Some(prev) = attached.database.replace(None) { // SAFETY: `prev` is a valid pointer to a database. unsafe { prev.as_ref().zalsa_local().uncancel() }; } } } } let _guard = DbGuard::new(self, db.as_dyn_database()); op() } #[inline] fn attach_allow_change(&self, db: &Db, op: impl FnOnce() -> R) -> R where Db: ?Sized + Database, { struct DbGuard<'s> { /// The database that *we* attached on scope entry. /// /// `None` if one was already attached by a parent scope. state: Option<&'s Attached>, /// The previously attached database that we replaced, if any. /// /// We need to make sure to rollback and activate it again when we exit the scope. prev: Option>, } impl<'s> DbGuard<'s> { #[inline] fn new(attached: &'s Attached, db: &dyn Database) -> Self { let db = NonNull::from(db); match attached.database.replace(Some(db)) { // A database was already attached by a parent scope. Some(prev) => { if std::ptr::eq(db.as_ptr(), prev.as_ptr()) { // and it was the same as ours, so we did not change anything. Self { state: None, prev: None, } } else { // and it was the a different one from ours, record the state changes. Self { state: Some(attached), prev: Some(prev), } } } // No database is attached, attach the new one. None => { attached.database.set(Some(db)); Self { state: Some(attached), prev: None, } } } } } impl Drop for DbGuard<'_> { #[inline] fn drop(&mut self) { // Reset database to null if we did anything in `DbGuard::new`. if let Some(attached) = self.state { if let Some(prev) = attached.database.replace(self.prev) { // SAFETY: `prev` is a valid pointer to a database. unsafe { prev.as_ref().zalsa_local().uncancel() }; } } } } let _guard = DbGuard::new(self, db.as_dyn_database()); op() } /// Access the "attached" database. Returns `None` if no database is attached. /// Databases are attached with `attach_database`. #[inline] fn with(&self, op: impl FnOnce(&dyn Database) -> R) -> Option { let db = self.database.get()?; // SAFETY: We always attach the database in for the entire duration of a function, // so it cannot become "unattached" while this function is running. Some(op(unsafe { db.as_ref() })) } } /// Attach the database to the current thread and execute `op`. /// Panics if a different database has already been attached. #[inline] pub fn attach(db: &Db, op: impl FnOnce() -> R) -> R where Db: ?Sized + Database, { ATTACHED.with( #[inline] |a| a.attach(db, op), ) } /// Attach the database to the current thread and execute `op`. /// Allows a different database than currently attached. The original database /// will be restored on return. /// /// **Note:** Switching databases can cause bugs. If you do not intend to switch /// databases, prefer [`attach`] which will panic if you accidentally do. #[inline] pub fn attach_allow_change(db: &Db, op: impl FnOnce() -> R) -> R where Db: ?Sized + Database, { ATTACHED.with( #[inline] |a| a.attach_allow_change(db, op), ) } /// Access the "attached" database. Returns `None` if no database is attached. /// Databases are attached with `attach_database`. #[inline] pub fn with_attached_database(op: impl FnOnce(&dyn Database) -> R) -> Option { ATTACHED.with( #[inline] |a| a.with(op), ) } salsa-0.26.2/src/cancelled.rs000064400000000000000000000032421046102023000140660ustar 00000000000000use std::fmt; use std::panic::{self, UnwindSafe}; /// A panic payload indicating that execution of a salsa query was cancelled. /// /// This can occur for a few reasons: /// * /// * /// * #[derive(Debug)] #[non_exhaustive] pub enum Cancelled { /// The query was operating but the local database execution has been cancelled. Local, /// The query was operating on revision R, but there is a pending write to move to revision R+1. PendingWrite, /// The query was blocked on another thread, and that thread panicked. PropagatedPanic, } impl Cancelled { #[cold] pub(crate) fn throw(self) -> ! { // We use resume and not panic here to avoid running the panic // hook (that is, to avoid collecting and printing backtrace). panic::resume_unwind(Box::new(self)); } /// Runs `f`, and catches any salsa cancellation. pub fn catch(f: F) -> Result where F: FnOnce() -> T + UnwindSafe, { match panic::catch_unwind(f) { Ok(t) => Ok(t), Err(payload) => match payload.downcast() { Ok(cancelled) => Err(*cancelled), Err(payload) => panic::resume_unwind(payload), }, } } } impl std::fmt::Display for Cancelled { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let why = match self { Cancelled::Local => "local cancellation request", Cancelled::PendingWrite => "pending write", Cancelled::PropagatedPanic => "propagated panic", }; f.write_str("cancelled because of ")?; f.write_str(why) } } impl std::error::Error for Cancelled {} salsa-0.26.2/src/cycle.rs000064400000000000000000000422021046102023000132520ustar 00000000000000//! Cycle handling //! //! Salsa's default cycle handling is quite simple: if we encounter a cycle (that is, if we attempt //! to execute a query that is already on the active query stack), we panic. //! //! By setting `cycle_fn` and `cycle_initial` arguments to `salsa::tracked`, queries can opt-in to //! fixed-point iteration instead. //! //! We call the query which triggers the cycle (that is, the query that is already on the stack //! when it is called again) the "cycle head". The cycle head is responsible for managing iteration //! of the cycle. When a cycle is encountered, if the cycle head has `cycle_fn` and `cycle_initial` //! set, it will call the `cycle_initial` function to generate an "empty" or "initial" value for //! fixed-point iteration, which will be returned to its caller. Then each query in the cycle will //! compute a value normally, but every computed value will track the head(s) of the cycles it is //! part of. Every query's "cycle heads" are the union of all the cycle heads of all the queries it //! depends on. A memoized query result with cycle heads is called a "provisional value". //! //! For example, if `qa` calls `qb`, and `qb` calls `qc`, and `qc` calls `qa`, then `qa` will call //! its `cycle_initial` function to get an initial value, and return that as its result to `qc`, //! marked with `qa` as cycle head. `qc` will compute its own provisional result based on that, and //! return to `qb` a result also marked with `qa` as cycle head. `qb` will similarly compute and //! return a provisional value back to `qa`. //! //! When a query observes that it has just computed a result which contains itself as a cycle head, //! it recognizes that it is responsible for resolving this cycle and calls its `cycle_fn` to //! decide what value to use. The `cycle_fn` function is passed the provisional value just computed //! for that query and the count of iterations so far, and returns the value to use for this //! iteration. This can be the computed value itself, or a different value (e.g., a fallback value). //! //! If the cycle head ever observes that the value returned by `cycle_fn` is the same as the //! provisional value from the previous iteration, this cycle has converged. The cycle head will //! mark that value as final (by removing itself as cycle head) and return it. //! //! Other queries in the cycle will still have provisional values recorded, but those values should //! now also be considered final! We don't eagerly walk the entire cycle to mark them final. //! Instead, we wait until the next time that provisional value is read, and then we check if all //! of its cycle heads have a final result, in which case it, too, can be marked final. (This is //! implemented in `shallow_verify_memo` and `validate_provisional`.) //! //! In nested cycle cases, the inner cycles are iterated as part of the outer cycle iteration. This helps //! to significantly reduce the number of iterations needed to reach a fixpoint. For nested cycles, //! the inner cycles head will transfer their lock ownership to the outer cycle. This ensures //! that, over time, the outer cycle will hold all necessary locks to complete the fixpoint iteration. //! Without this, different threads would compete for the locks of inner cycle heads, leading to potential //! hangs (but not deadlocks). use std::iter::FusedIterator; use thin_vec::{ThinVec, thin_vec}; use crate::key::DatabaseKeyIndex; use crate::sync::OnceLock; use crate::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use crate::{Id, Revision}; /// The maximum number of times we'll fixpoint-iterate before panicking. /// /// Should only be relevant in case of a badly configured cycle recovery. pub const MAX_ITERATIONS: IterationCount = IterationCount(200); /// Cycle recovery strategy: Is this query capable of recovering from /// a cycle that results from executing the function? If so, how? #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum CycleRecoveryStrategy { /// Cannot recover from cycles: panic. /// /// This is the default. Panic, /// Recovers from cycles by fixpoint iterating and/or falling /// back to a sentinel value. /// /// This choice is computed by the query's `cycle_recovery` /// function and initial value. Fixpoint, /// Recovers from cycles by inserting a fallback value for all /// queries that have a fallback, and ignoring any other query /// in the cycle (as if they were not computed). FallbackImmediate, } /// A "cycle head" is the query at which we encounter a cycle; that is, if A -> B -> C -> A, then A /// would be the cycle head. It returns an "initial value" when the cycle is encountered (if /// fixpoint iteration is enabled for that query), and then is responsible for re-iterating the /// cycle until it converges. #[derive(Debug)] #[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] pub struct CycleHead { pub(crate) database_key_index: DatabaseKeyIndex, pub(crate) iteration_count: AtomicIterationCount, /// Marks a cycle head as removed within its `CycleHeads` container. /// /// Cycle heads are marked as removed when the memo from the last iteration (a provisional memo) /// is used as the initial value for the next iteration. It's necessary to remove all but its own /// head from the `CycleHeads` container, because the query might now depend on fewer cycles /// (in case of conditional dependencies). However, we can't actually remove the cycle head /// within `fetch_cold_cycle` because we only have a readonly memo. That's what `removed` is used for. #[cfg_attr(feature = "persistence", serde(skip))] removed: AtomicBool, } impl CycleHead { pub const fn new( database_key_index: DatabaseKeyIndex, iteration_count: IterationCount, ) -> Self { Self { database_key_index, iteration_count: AtomicIterationCount(AtomicU8::new(iteration_count.0)), removed: AtomicBool::new(false), } } } impl Clone for CycleHead { fn clone(&self) -> Self { Self { database_key_index: self.database_key_index, iteration_count: self.iteration_count.load().into(), removed: self.removed.load(Ordering::Relaxed).into(), } } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default, PartialOrd, Ord)] #[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "persistence", serde(transparent))] pub struct IterationCount(u8); impl IterationCount { pub(crate) const fn initial() -> Self { Self(0) } pub(crate) const fn is_initial(self) -> bool { self.0 == 0 } pub(crate) const fn increment(self) -> Option { let next = Self(self.0 + 1); if next.0 <= MAX_ITERATIONS.0 { Some(next) } else { None } } pub(crate) const fn as_u32(self) -> u32 { self.0 as u32 } } impl std::fmt::Display for IterationCount { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } #[derive(Debug)] pub(crate) struct AtomicIterationCount(AtomicU8); impl AtomicIterationCount { pub(crate) fn load(&self) -> IterationCount { IterationCount(self.0.load(Ordering::Relaxed)) } pub(crate) fn load_mut(&mut self) -> IterationCount { IterationCount(*self.0.get_mut()) } pub(crate) fn store(&self, value: IterationCount) { self.0.store(value.0, Ordering::Release); } pub(crate) fn set(&mut self, value: IterationCount) { *self.0.get_mut() = value.0; } } impl std::fmt::Display for AtomicIterationCount { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.load().fmt(f) } } impl From for AtomicIterationCount { fn from(iteration_count: IterationCount) -> Self { AtomicIterationCount(iteration_count.0.into()) } } #[cfg(feature = "persistence")] impl serde::Serialize for AtomicIterationCount { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.load().serialize(serializer) } } #[cfg(feature = "persistence")] impl<'de> serde::Deserialize<'de> for AtomicIterationCount { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { IterationCount::deserialize(deserializer).map(Into::into) } } /// Any provisional value generated by any query in a cycle will track the cycle head(s) (can be /// plural in case of nested cycles) representing the cycles it is part of, and the current /// iteration count for each cycle head. This struct tracks these cycle heads. #[derive(Clone, Debug, Default)] pub struct CycleHeads(ThinVec); impl CycleHeads { pub(crate) fn is_empty(&self) -> bool { self.0.is_empty() } pub(crate) fn initial( database_key_index: DatabaseKeyIndex, iteration_count: IterationCount, ) -> Self { Self(thin_vec![CycleHead { database_key_index, iteration_count: iteration_count.into(), removed: false.into() }]) } pub(crate) fn iter(&self) -> CycleHeadsIterator<'_> { CycleHeadsIterator { inner: self.0.iter(), } } pub(crate) fn ids(&self) -> CycleHeadIdsIterator<'_> { CycleHeadIdsIterator { inner: self.iter() } } /// Iterates over all cycle heads that aren't equal to `own`. pub(crate) fn iter_not_eq( &self, own: DatabaseKeyIndex, ) -> impl DoubleEndedIterator { self.iter() .filter(move |head| head.database_key_index != own) } pub(crate) fn contains(&self, value: &DatabaseKeyIndex) -> bool { self.into_iter() .any(|head| head.database_key_index == *value) } /// Removes all cycle heads except `except` by marking them as removed. /// /// Note that the heads aren't actually removed. They're only marked as removed and will be /// skipped when iterating. This is because we might not have a mutable reference. pub(crate) fn remove_all_except(&self, except: DatabaseKeyIndex) { for head in self.0.iter() { if head.database_key_index == except { continue; } head.removed.store(true, Ordering::Release); } } /// Updates the iteration count for the head `cycle_head_index` to `new_iteration_count`. /// /// Unlike [`update_iteration_count`], this method takes a `&mut self` reference. It should /// be preferred if possible, as it avoids atomic operations. pub(crate) fn update_iteration_count_mut( &mut self, cycle_head_index: DatabaseKeyIndex, new_iteration_count: IterationCount, ) { if let Some(cycle_head) = self .0 .iter_mut() .find(|cycle_head| cycle_head.database_key_index == cycle_head_index) { cycle_head.iteration_count.set(new_iteration_count); } } /// Updates the iteration count for the head `cycle_head_index` to `new_iteration_count`. /// /// Unlike [`update_iteration_count_mut`], this method takes a `&self` reference. pub(crate) fn update_iteration_count( &self, cycle_head_index: DatabaseKeyIndex, new_iteration_count: IterationCount, ) { if let Some(cycle_head) = self .0 .iter() .find(|cycle_head| cycle_head.database_key_index == cycle_head_index) { cycle_head.iteration_count.store(new_iteration_count); } } #[inline] pub(crate) fn extend(&mut self, other: &Self) { self.0.reserve(other.0.len()); for head in other { debug_assert!(!head.removed.load(Ordering::Relaxed)); self.insert(head.database_key_index, head.iteration_count.load()); } } pub(crate) fn insert( &mut self, database_key_index: DatabaseKeyIndex, iteration_count: IterationCount, ) -> bool { if let Some(existing) = self .0 .iter_mut() .find(|candidate| candidate.database_key_index == database_key_index) { let removed = existing.removed.get_mut(); if *removed { *removed = false; existing.iteration_count.set(iteration_count); true } else { let existing_count = existing.iteration_count.load_mut(); assert_eq!( existing_count, iteration_count, "Can't merge cycle heads {:?} with different iteration counts ({existing_count:?}, {iteration_count:?})", existing.database_key_index ); false } } else { self.0 .push(CycleHead::new(database_key_index, iteration_count)); true } } #[cfg(feature = "salsa_unstable")] pub(crate) fn allocation_size(&self) -> usize { std::mem::size_of_val(self.0.as_slice()) } } #[cfg(feature = "persistence")] impl serde::Serialize for CycleHeads { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { use serde::ser::SerializeSeq; let mut seq = serializer.serialize_seq(None)?; for e in self { if e.removed.load(Ordering::Relaxed) { continue; } seq.serialize_element(e)?; } seq.end() } } #[cfg(feature = "persistence")] impl<'de> serde::Deserialize<'de> for CycleHeads { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let vec: ThinVec = serde::Deserialize::deserialize(deserializer)?; Ok(CycleHeads(vec)) } } impl IntoIterator for CycleHeads { type Item = CycleHead; type IntoIter = as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } #[derive(Clone)] pub struct CycleHeadsIterator<'a> { inner: std::slice::Iter<'a, CycleHead>, } impl<'a> Iterator for CycleHeadsIterator<'a> { type Item = &'a CycleHead; fn next(&mut self) -> Option { loop { let next = self.inner.next()?; if next.removed.load(Ordering::Relaxed) { continue; } return Some(next); } } } impl FusedIterator for CycleHeadsIterator<'_> {} impl DoubleEndedIterator for CycleHeadsIterator<'_> { fn next_back(&mut self) -> Option { loop { let next = self.inner.next_back()?; if next.removed.load(Ordering::Relaxed) { continue; } return Some(next); } } } impl<'a> std::iter::IntoIterator for &'a CycleHeads { type Item = &'a CycleHead; type IntoIter = CycleHeadsIterator<'a>; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl From for CycleHeads { fn from(value: CycleHead) -> Self { Self(thin_vec![value]) } } #[inline] pub(crate) fn empty_cycle_heads() -> &'static CycleHeads { static EMPTY_CYCLE_HEADS: OnceLock = OnceLock::new(); EMPTY_CYCLE_HEADS.get_or_init(|| CycleHeads(ThinVec::new())) } #[derive(Clone)] pub struct CycleHeadIdsIterator<'a> { inner: CycleHeadsIterator<'a>, } impl Iterator for CycleHeadIdsIterator<'_> { type Item = crate::Id; fn next(&mut self) -> Option { self.inner .next() .map(|head| head.database_key_index.key_index()) } } /// The context that the cycle recovery function receives when a query cycle occurs. pub struct Cycle<'a> { pub(crate) head_ids: CycleHeadIdsIterator<'a>, pub(crate) id: Id, pub(crate) iteration: u32, } impl Cycle<'_> { /// An iterator that outputs the [`Id`]s of the current cycle heads. /// This always contains the [`Id`] of the current query but it can contain additional cycle head [`Id`]s /// if this query is nested in an outer cycle or if it has nested cycles. pub fn head_ids(&self) -> CycleHeadIdsIterator<'_> { self.head_ids.clone() } /// The [`Id`] of the query that the current cycle recovery function is processing. pub fn id(&self) -> Id { self.id } /// The counter of the current fixed point iteration. pub fn iteration(&self) -> u32 { self.iteration } } #[derive(Debug)] pub enum ProvisionalStatus<'db> { Provisional { iteration: IterationCount, verified_at: Revision, cycle_heads: &'db CycleHeads, }, Final { iteration: IterationCount, verified_at: Revision, }, } impl<'db> ProvisionalStatus<'db> { pub(crate) fn cycle_heads(&self) -> &'db CycleHeads { match self { ProvisionalStatus::Provisional { cycle_heads, .. } => cycle_heads, ProvisionalStatus::Final { .. } => empty_cycle_heads(), } } pub(crate) const fn is_provisional(&self) -> bool { matches!(self, ProvisionalStatus::Provisional { .. }) } } salsa-0.26.2/src/database.rs000064400000000000000000000435601046102023000137270ustar 00000000000000use std::borrow::Cow; use std::ptr::NonNull; use crate::views::DatabaseDownCaster; use crate::zalsa::{IngredientIndex, ZalsaDatabase}; use crate::zalsa_local::CancellationToken; use crate::{Durability, Revision}; #[derive(Copy, Clone)] pub struct RawDatabase<'db> { pub(crate) ptr: NonNull<()>, _marker: std::marker::PhantomData<&'db dyn Database>, } impl<'db, Db: Database + ?Sized> From<&'db Db> for RawDatabase<'db> { #[inline] fn from(db: &'db Db) -> Self { RawDatabase { ptr: NonNull::from(db).cast(), _marker: std::marker::PhantomData, } } } impl<'db, Db: Database + ?Sized> From<&'db mut Db> for RawDatabase<'db> { #[inline] fn from(db: &'db mut Db) -> Self { RawDatabase { ptr: NonNull::from(db).cast(), _marker: std::marker::PhantomData, } } } /// The trait implemented by all Salsa databases. /// You can create your own subtraits of this trait using the `#[salsa::db]`(`crate::db`) procedural macro. pub trait Database: Send + ZalsaDatabase + AsDynDatabase { /// Enforces current LRU limits, evicting entries if necessary. /// /// **WARNING:** Just like an ordinary write, this method triggers /// cancellation. If you invoke it while a snapshot exists, it /// will block until that snapshot is dropped -- if that snapshot /// is owned by the current thread, this could trigger deadlock. fn trigger_lru_eviction(&mut self) { let zalsa_mut = self.zalsa_mut(); zalsa_mut.evict_lru(); } /// A "synthetic write" causes the system to act *as though* some /// input of durability `durability` has changed, triggering a new revision. /// This is mostly useful for profiling scenarios. /// /// **WARNING:** Just like an ordinary write, this method triggers /// cancellation. If you invoke it while a snapshot exists, it /// will block until that snapshot is dropped -- if that snapshot /// is owned by the current thread, this could trigger deadlock. fn synthetic_write(&mut self, durability: Durability) { let zalsa_mut = self.zalsa_mut(); zalsa_mut.new_revision(); zalsa_mut.runtime_mut().report_tracked_write(durability); } /// This method cancels all outstanding computations. /// If you invoke it while a snapshot exists, it /// will block until that snapshot is dropped -- if that snapshot /// is owned by the current thread, this could trigger deadlock. fn trigger_cancellation(&mut self) { let _ = self.zalsa_mut(); } /// Retrieves a [`CancellationToken`] for the current database handle. fn cancellation_token(&self) -> CancellationToken { self.zalsa_local().cancellation_token() } /// Reports that the query depends on some state unknown to salsa. /// /// Queries which report untracked reads will be re-executed in the next /// revision. fn report_untracked_read(&self) { let (zalsa, zalsa_local) = self.zalsas(); zalsa_local.report_untracked_read(zalsa.current_revision()) } /// Return the "debug name" (i.e., the struct name, etc) for an "ingredient", /// which are the fine-grained components we use to track data. This is intended /// for debugging and the contents of the returned string are not semver-guaranteed. /// /// Ingredient indices can be extracted from [`DatabaseKeyIndex`](`crate::DatabaseKeyIndex`) values. fn ingredient_debug_name(&self, ingredient_index: IngredientIndex) -> Cow<'_, str> { Cow::Borrowed( self.zalsa() .lookup_ingredient(ingredient_index) .debug_name(), ) } /// Starts unwinding the stack if the current revision is cancelled. /// /// This method can be called by query implementations that perform /// potentially expensive computations, in order to speed up propagation of /// cancellation. /// /// Cancellation will automatically be triggered by salsa on any query /// invocation. /// /// This method should not be overridden by `Database` implementors. A /// `salsa_event` is emitted when this method is called, so that should be /// used instead. fn unwind_if_revision_cancelled(&self) { let (zalsa, zalsa_local) = self.zalsas(); zalsa.unwind_if_revision_cancelled(zalsa_local); } /// Execute `op` with the database in thread-local storage for debug print-outs. #[inline(always)] fn attach(&self, op: impl FnOnce(&Self) -> R) -> R where Self: Sized, { crate::attach::attach(self, || op(self)) } #[cold] #[inline(never)] #[doc(hidden)] fn zalsa_register_downcaster(&self) -> &DatabaseDownCaster { self.zalsa().views().downcaster_for::() // The no-op downcaster is special cased in view caster construction. } #[doc(hidden)] #[inline(always)] fn downcast(&self) -> &dyn Database where Self: Sized, { // No-op self } } /// Upcast to a `dyn Database`. /// /// Only required because upcasting does not work for unsized generic parameters. pub trait AsDynDatabase { fn as_dyn_database(&self) -> &dyn Database; } impl AsDynDatabase for T { #[inline(always)] fn as_dyn_database(&self) -> &dyn Database { self } } pub fn current_revision(db: &Db) -> Revision { db.zalsa().current_revision() } #[cfg(feature = "persistence")] mod persistence { use crate::plumbing::Ingredient; use crate::zalsa::Zalsa; use crate::{Database, IngredientIndex, Runtime}; use std::fmt; use serde::de::{self, DeserializeSeed, SeqAccess}; use serde::ser::SerializeMap; impl dyn Database { /// Returns a type implementing [`serde::Serialize`], that can be used to serialize the /// current state of the database. pub fn as_serialize(&mut self) -> impl serde::Serialize + '_ { SerializeDatabase { runtime: self.zalsa().runtime(), ingredients: SerializeIngredients(self.zalsa()), } } /// Deserialize the database using a [`serde::Deserializer`]. /// /// This method will modify the database in-place based on the serialized data. pub fn deserialize<'db, D>(&mut self, deserializer: D) -> Result<(), D::Error> where D: serde::Deserializer<'db>, { DeserializeDatabase(self.zalsa_mut()).deserialize(deserializer) } } #[derive(serde::Serialize)] #[serde(rename = "Database")] pub struct SerializeDatabase<'db> { pub runtime: &'db Runtime, pub ingredients: SerializeIngredients<'db>, } pub struct SerializeIngredients<'db>(pub &'db Zalsa); impl serde::Serialize for SerializeIngredients<'_> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let SerializeIngredients(zalsa) = self; let mut ingredients = zalsa .ingredients() .filter(|ingredient| ingredient.should_serialize(zalsa)) .collect::>(); // Ensure structs are serialized before tracked functions, as deserializing a // memo requires its input struct to have been deserialized. ingredients.sort_by_key(|ingredient| ingredient.jar_kind()); let mut map = serializer.serialize_map(Some(ingredients.len()))?; for ingredient in ingredients { map.serialize_entry( &ingredient.ingredient_index().as_u32(), &SerializeIngredient(ingredient, zalsa), )?; } map.end() } } struct SerializeIngredient<'db>(&'db dyn Ingredient, &'db Zalsa); impl serde::Serialize for SerializeIngredient<'_> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut result = None; let mut serializer = Some(serializer); // SAFETY: `::as_serialize` take `&mut self`. unsafe { self.0.serialize(self.1, &mut |serialize| { let serializer = serializer.take().expect( "`Ingredient::serialize` must invoke the serialization callback only once", ); result = Some(erased_serde::serialize(&serialize, serializer)) }) }; result.expect("`Ingredient::serialize` must invoke the serialization callback") } } #[derive(serde::Deserialize)] #[serde(field_identifier, rename_all = "lowercase")] enum DatabaseField { Runtime, Ingredients, } pub struct DeserializeDatabase<'db>(pub &'db mut Zalsa); impl<'de> de::DeserializeSeed<'de> for DeserializeDatabase<'_> { type Value = (); fn deserialize(self, deserializer: D) -> Result where D: de::Deserializer<'de>, { // Note that we have to deserialize using a manual visitor here because the // `Deserialize` derive does not support fields that use `DeserializeSeed`. deserializer.deserialize_struct("Database", &["runtime", "ingredients"], self) } } impl<'de> serde::de::Visitor<'de> for DeserializeDatabase<'_> { type Value = (); fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("struct Database") } fn visit_seq(self, mut seq: V) -> Result<(), V::Error> where V: SeqAccess<'de>, { let mut runtime = seq .next_element()? .ok_or_else(|| de::Error::invalid_length(0, &self))?; let () = seq .next_element_seed(DeserializeIngredients(self.0))? .ok_or_else(|| de::Error::invalid_length(1, &self))?; self.0.runtime_mut().deserialize_from(&mut runtime); Ok(()) } fn visit_map(self, mut map: V) -> Result<(), V::Error> where V: serde::de::MapAccess<'de>, { let mut runtime = None; let mut ingredients = None; while let Some(key) = map.next_key()? { match key { DatabaseField::Runtime => { if runtime.is_some() { return Err(serde::de::Error::duplicate_field("runtime")); } runtime = Some(map.next_value()?); } DatabaseField::Ingredients => { if ingredients.is_some() { return Err(serde::de::Error::duplicate_field("ingredients")); } ingredients = Some(map.next_value_seed(DeserializeIngredients(self.0))?); } } } let mut runtime = runtime.ok_or_else(|| serde::de::Error::missing_field("runtime"))?; let () = ingredients.ok_or_else(|| serde::de::Error::missing_field("ingredients"))?; self.0.runtime_mut().deserialize_from(&mut runtime); Ok(()) } } struct DeserializeIngredients<'db>(&'db mut Zalsa); impl<'de> serde::de::Visitor<'de> for DeserializeIngredients<'_> { type Value = (); fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map") } fn visit_map(self, mut access: M) -> Result where M: serde::de::MapAccess<'de>, { let DeserializeIngredients(zalsa) = self; while let Some(index) = access.next_key::()? { let index = IngredientIndex::new(index); // Remove the ingredient temporarily, to avoid holding an overlapping mutable borrow // to the ingredient as well as the database. let mut ingredient = zalsa.take_ingredient(index); // Deserialize the ingredient. access.next_value_seed(DeserializeIngredient(&mut *ingredient, zalsa))?; zalsa.replace_ingredient(index, ingredient); } Ok(()) } } impl<'de> serde::de::DeserializeSeed<'de> for DeserializeIngredients<'_> { type Value = (); fn deserialize(self, deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_map(self) } } struct DeserializeIngredient<'db>(&'db mut dyn Ingredient, &'db mut Zalsa); impl<'de> serde::de::DeserializeSeed<'de> for DeserializeIngredient<'_> { type Value = (); fn deserialize(self, deserializer: D) -> Result where D: serde::Deserializer<'de>, { let deserializer = &mut ::erase(deserializer); self.0 .deserialize(self.1, deserializer) .map_err(serde::de::Error::custom) } } } #[cfg(feature = "salsa_unstable")] pub use memory_usage::IngredientInfo; #[cfg(feature = "salsa_unstable")] pub(crate) use memory_usage::{MemoInfo, SlotInfo}; #[cfg(feature = "salsa_unstable")] mod memory_usage { use hashbrown::HashMap; use crate::Database; impl dyn Database { /// Returns memory usage information about ingredients in the database. pub fn memory_usage(&self) -> DatabaseInfo { let mut queries = HashMap::new(); let mut structs = Vec::new(); for input_ingredient in self.zalsa().ingredients() { let Some(input_info) = input_ingredient.memory_usage(self) else { continue; }; let mut size_of_fields = 0; let mut size_of_metadata = 0; let mut count = 0; let mut heap_size_of_fields = None; for input_slot in input_info { count += 1; size_of_fields += input_slot.size_of_fields; size_of_metadata += input_slot.size_of_metadata; if let Some(slot_heap_size) = input_slot.heap_size_of_fields { heap_size_of_fields = Some(heap_size_of_fields.unwrap_or_default() + slot_heap_size); } for memo in input_slot.memos { let info = queries.entry(memo.debug_name).or_insert(IngredientInfo { debug_name: memo.output.debug_name, ..Default::default() }); info.count += 1; info.size_of_fields += memo.output.size_of_fields; info.size_of_metadata += memo.output.size_of_metadata; if let Some(memo_heap_size) = memo.output.heap_size_of_fields { info.heap_size_of_fields = Some(info.heap_size_of_fields.unwrap_or_default() + memo_heap_size); } } } structs.push(IngredientInfo { count, size_of_fields, size_of_metadata, heap_size_of_fields, debug_name: input_ingredient.debug_name(), }); } DatabaseInfo { structs, queries } } } /// Memory usage information about ingredients in the Salsa database. pub struct DatabaseInfo { /// Information about any Salsa structs. pub structs: Vec, /// Memory usage information for memoized values of a given query, keyed /// by the query function name. pub queries: HashMap<&'static str, IngredientInfo>, } /// Information about instances of a particular Salsa ingredient. #[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct IngredientInfo { debug_name: &'static str, count: usize, size_of_metadata: usize, size_of_fields: usize, heap_size_of_fields: Option, } impl IngredientInfo { /// Returns the debug name of the ingredient. pub fn debug_name(&self) -> &'static str { self.debug_name } /// Returns the total stack size of the fields of any instances of this ingredient, in bytes. pub fn size_of_fields(&self) -> usize { self.size_of_fields } /// Returns the total heap size of the fields of any instances of this ingredient, in bytes. /// /// Returns `None` if the ingredient doesn't specify a `heap_size` function. pub fn heap_size_of_fields(&self) -> Option { self.heap_size_of_fields } /// Returns the total size of Salsa metadata of any instances of this ingredient, in bytes. pub fn size_of_metadata(&self) -> usize { self.size_of_metadata } /// Returns the number of instances of this ingredient. pub fn count(&self) -> usize { self.count } } /// Memory usage information about a particular instance of struct, input or output. pub struct SlotInfo { pub(crate) debug_name: &'static str, pub(crate) size_of_metadata: usize, pub(crate) size_of_fields: usize, pub(crate) heap_size_of_fields: Option, pub(crate) memos: Vec, } /// Memory usage information about a particular memo. pub struct MemoInfo { pub(crate) debug_name: &'static str, pub(crate) output: SlotInfo, } } salsa-0.26.2/src/database_impl.rs000064400000000000000000000024041046102023000147400ustar 00000000000000use tracing::Level; use crate::storage::HasStorage; use crate::{Database, Storage}; /// Default database implementation that you can use if you don't /// require any custom user data. #[derive(Clone)] pub struct DatabaseImpl { storage: Storage, } impl Default for DatabaseImpl { fn default() -> Self { Self { // Default behavior: tracing debug log the event. storage: Storage::new(if tracing::enabled!(Level::DEBUG) { Some(Box::new(|event| { crate::tracing::debug!("salsa_event({:?})", event) })) } else { None }), } } } impl DatabaseImpl { /// Create a new database; equivalent to `Self::default`. pub fn new() -> Self { Self::default() } pub fn storage(&self) -> &Storage { &self.storage } } impl Database for DatabaseImpl {} // SAFETY: The `storage` and `storage_mut` fields return a reference to the same storage field owned by `self`. unsafe impl HasStorage for DatabaseImpl { #[inline(always)] fn storage(&self) -> &Storage { &self.storage } #[inline(always)] fn storage_mut(&mut self) -> &mut Storage { &mut self.storage } } salsa-0.26.2/src/durability.rs000064400000000000000000000074531046102023000143340ustar 00000000000000/// Describes how likely a value is to change—how "durable" it is. /// /// By default, inputs have `Durability::LOW` and interned values have /// `Durability::HIGH`. But inputs can be explicitly set with other /// durabilities. /// /// We use durabilities to optimize the work of "revalidating" a query /// after some input has changed. Ordinarily, in a new revision, /// queries have to trace all their inputs back to the base inputs to /// determine if any of those inputs have changed. But if we know that /// the only changes were to inputs of low durability (the common /// case), and we know that the query only used inputs of medium /// durability or higher, then we can skip that enumeration. /// /// Typically, one assigns low durabilities to inputs that the user is /// frequently editing. Medium or high durabilities are used for /// configuration, the source from library crates, or other things /// that are unlikely to be edited. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Durability(DurabilityVal); #[cfg(feature = "persistence")] impl serde::Serialize for Durability { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serde::Serialize::serialize(&(self.0 as u8), serializer) } } #[cfg(feature = "persistence")] impl<'de> serde::Deserialize<'de> for Durability { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { u8::deserialize(deserializer).map(|value| Self(DurabilityVal::from(value))) } } impl std::fmt::Debug for Durability { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if f.alternate() { match self.0 { DurabilityVal::Low => f.write_str("Durability::LOW"), DurabilityVal::Medium => f.write_str("Durability::MEDIUM"), DurabilityVal::High => f.write_str("Durability::HIGH"), } } else { f.debug_tuple("Durability") .field(&(self.0 as usize)) .finish() } } } // We use an enum here instead of a u8 for niches. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] enum DurabilityVal { Low = 0, Medium = 1, High = 2, } impl From for DurabilityVal { fn from(value: u8) -> Self { match value { 0 => DurabilityVal::Low, 1 => DurabilityVal::Medium, 2 => DurabilityVal::High, _ => panic!("invalid durability"), } } } impl Durability { /// Low durability: things that change frequently. /// /// Example: part of the crate being edited pub const LOW: Durability = Durability(DurabilityVal::Low); /// Medium durability: things that change sometimes, but rarely. /// /// Example: a Cargo.toml file pub const MEDIUM: Durability = Durability(DurabilityVal::Medium); /// High durability: things that are not expected to change under /// common usage. /// /// Example: the standard library or something from crates.io pub const HIGH: Durability = Durability(DurabilityVal::High); /// The minimum possible durability; equivalent to LOW but /// "conceptually" distinct (i.e., if we add more durability /// levels, this could change). pub(crate) const MIN: Durability = Self::LOW; /// The maximum possible durability; equivalent to HIGH but /// "conceptually" distinct (i.e., if we add more durability /// levels, this could change). pub(crate) const MAX: Durability = Self::HIGH; /// Number of durability levels. pub(crate) const LEN: usize = Self::HIGH.0 as usize + 1; pub(crate) fn index(self) -> usize { self.0 as usize } } impl Default for Durability { fn default() -> Self { Durability::LOW } } salsa-0.26.2/src/event.rs000064400000000000000000000105211046102023000132730ustar 00000000000000use crate::Revision; use crate::cycle::IterationCount; use crate::key::DatabaseKeyIndex; use crate::sync::thread::{self, ThreadId}; /// The `Event` struct identifies various notable things that can /// occur during salsa execution. Instances of this struct are given /// to `salsa_event`. #[derive(Debug)] pub struct Event { /// The id of the thread that triggered the event. pub thread_id: ThreadId, /// What sort of event was it. pub kind: EventKind, } impl Event { pub fn new(kind: EventKind) -> Self { Self { thread_id: thread::current().id(), kind, } } } /// An enum identifying the various kinds of events that can occur. #[derive(Debug)] pub enum EventKind { /// Occurs when we found that all inputs to a memoized value are /// up-to-date and hence the value can be re-used without /// executing the closure. /// /// Executes before the "re-used" value is returned. DidValidateMemoizedValue { /// The database-key for the affected value. Implements `Debug`. database_key: DatabaseKeyIndex, }, /// Indicates that another thread (with id `other_thread_id`) is processing the /// given query (`database_key`), so we will block until they /// finish. /// /// Executes after we have registered with the other thread but /// before they have answered us. WillBlockOn { /// The id of the thread we will block on. other_thread_id: ThreadId, /// The database-key for the affected value. Implements `Debug`. database_key: DatabaseKeyIndex, }, /// Indicates that the function for this query will be executed. /// This is either because it has never executed before or because /// its inputs may be out of date. WillExecute { /// The database-key for the affected value. Implements `Debug`. database_key: DatabaseKeyIndex, }, /// Salsa starts a new fixpoint iteration for the cycle with `database_key` as its /// outermost cycle. WillIterateCycle { /// The database-key for the cycle head. Implements `Debug`. database_key: DatabaseKeyIndex, iteration_count: IterationCount, }, /// Salsa completed a fixpoint iteration for the cycle with `database_key` as its /// outermost cycle. DidFinalizeCycle { database_key: DatabaseKeyIndex, iteration_count: IterationCount, }, /// Indicates that `unwind_if_cancelled` was called and salsa will check if /// the current revision has been cancelled. WillCheckCancellation, /// Indicates that one [`Handle`](`crate::Handle`) has set the cancellation flag. /// When other active handles execute salsa methods, they will observe this flag /// and panic with a sentinel value of type [`Cancelled`](`crate::Cancelled`). DidSetCancellationFlag, /// Discovered that a query used to output a given output but no longer does. WillDiscardStaleOutput { /// Key for the query that is executing and which no longer outputs the given value. execute_key: DatabaseKeyIndex, /// Key for the query that is no longer output output_key: DatabaseKeyIndex, }, /// Tracked structs or memoized data were discarded (freed). DidDiscard { /// Value being discarded. key: DatabaseKeyIndex, }, /// Discarded accumulated data from a given fn DidDiscardAccumulated { /// The key of the fn that accumulated results executor_key: DatabaseKeyIndex, /// Accumulator that was accumulated into accumulator: DatabaseKeyIndex, }, /// Indicates that a value was newly interned. DidInternValue { // The key of the interned value. key: DatabaseKeyIndex, // The revision the value was interned in. revision: Revision, }, /// Indicates that a value was interned by reusing an existing slot. DidReuseInternedValue { // The key of the interned value. key: DatabaseKeyIndex, // The revision the value was interned in. revision: Revision, }, /// Indicates that a previously interned value was read in a new revision. DidValidateInternedValue { // The key of the interned value. key: DatabaseKeyIndex, // The revision the value was interned in. revision: Revision, }, } salsa-0.26.2/src/function/accumulated.rs000064400000000000000000000102231046102023000162650ustar 00000000000000use crate::accumulator::accumulated_map::{AccumulatedMap, InputAccumulatedValues}; use crate::accumulator::{self}; use crate::function::{Configuration, IngredientImpl}; use crate::hash::FxHashSet; use crate::zalsa::ZalsaDatabase; use crate::zalsa_local::QueryOriginRef; use crate::{DatabaseKeyIndex, Id}; impl IngredientImpl where C: Configuration, { /// Helper used by `accumulate` functions. Computes the results accumulated by `database_key_index` /// and its inputs. pub fn accumulated_by<'db, A>(&self, db: &'db C::DbView, key: Id) -> Vec<&'db A> where A: accumulator::Accumulator, { let (zalsa, zalsa_local) = db.zalsas(); // NOTE: We don't have a precise way to track accumulated values at present, // so we report any read of them as an untracked read. // // Like tracked struct fields, accumulated values are essentially a "side channel output" // from a tracked function, hence we can't report this as a read of the tracked function(s) // whose accumulated values we are probing, since the accumulated values may have changed // even when the main return value of the function has not changed. // // Unlike tracked struct fields, we don't have a distinct id or ingredient to represent // "the values of type A accumulated by tracked function X". Typically accumulated values // are read from outside of salsa anyway so this is not a big deal. zalsa_local.report_untracked_read(zalsa.current_revision()); let Some(accumulator) = >::from_zalsa(zalsa) else { return vec![]; }; let mut output = vec![]; // First ensure the result is up to date self.fetch(db, zalsa, zalsa_local, key); let db_key = self.database_key_index(key); let mut visited: FxHashSet = FxHashSet::default(); let mut stack: Vec = vec![db_key]; // Do a depth-first search across the dependencies of `key`, reading the values accumulated by // each dependency. while let Some(k) = stack.pop() { // Already visited `k`? if !visited.insert(k) { continue; } let ingredient = zalsa.lookup_ingredient(k.ingredient_index()); // Extend `output` with any values accumulated by `k`. // SAFETY: `db` owns the `ingredient` let (accumulated_map, input) = unsafe { ingredient.accumulated(db.into(), k.key_index()) }; if let Some(accumulated_map) = accumulated_map { accumulated_map.extend_with_accumulated(accumulator.index(), &mut output); } // Skip over the inputs because we know that the entire sub-graph has no accumulated values if input.is_empty() { continue; } // Find the inputs of `k` and push them onto the stack. // // Careful: to ensure the user gets a consistent ordering in their // output vector, we want to push in execution order, so reverse order to // ensure the first child that was executed will be the first child popped // from the stack. let Some(origin) = ingredient.origin(zalsa, k.key_index()) else { continue; }; if let QueryOriginRef::Derived(edges) | QueryOriginRef::DerivedUntracked(edges) = origin { stack.reserve(edges.len()); } stack.extend(origin.inputs().rev()); visited.reserve(stack.len()); } output } pub(super) fn accumulated_map<'db>( &'db self, db: &'db C::DbView, key: Id, ) -> (Option<&'db AccumulatedMap>, InputAccumulatedValues) { let (zalsa, zalsa_local) = db.zalsas(); // NEXT STEP: stash and refactor `fetch` to return an `&Memo` so we can make this work let memo = self.refresh_memo(db, zalsa, zalsa_local, key); ( memo.revisions.accumulated(), memo.revisions.accumulated_inputs.load(), ) } } salsa-0.26.2/src/function/backdate.rs000064400000000000000000000101441046102023000155360ustar 00000000000000use crate::Backtrace; use crate::DatabaseKeyIndex; use crate::function::memo::Memo; use crate::function::{Configuration, IngredientImpl}; use crate::zalsa_local::QueryRevisions; use std::fmt; impl IngredientImpl where C: Configuration, { /// If the value/durability of this memo is equal to what is found in `revisions`/`value`, /// then update `revisions.changed_at` to match `self.revisions.changed_at`. This is invoked /// on an old memo when a new memo has been produced to check whether there have been changed. pub(super) fn backdate_if_appropriate<'db>( &self, old_memo: &Memo<'db, C>, index: DatabaseKeyIndex, revisions: &mut QueryRevisions, value: &C::Output<'db>, ) { // We've seen issues where queries weren't re-validated when backdating provisional values // in ty. This is more of a bandaid because we're close to a release and don't have the time to prove // right now whether backdating could be made safe for queries participating in queries. // TODO: Write a test that demonstrates that backdating queries participating in a cycle isn't safe // OR write many tests showing that it is (and fixing the case where it didn't correctly account for today). if !revisions.cycle_heads().is_empty() || old_memo.may_be_provisional() { return; } if let Some(old_value) = &old_memo.value { // Careful: if the value became less durable than it // used to be, that is a "breaking change" that our // consumers must be aware of. Becoming *more* durable // is not. See the test `durable_to_less_durable`. if revisions.durability >= old_memo.revisions.durability && C::values_equal(old_value, value) { crate::tracing::debug!( "{index:?} value is equal, back-dating to {:?}", old_memo.revisions.changed_at, ); if old_memo.revisions.changed_at > revisions.changed_at { report_backdate_violation( index, old_memo.revisions.changed_at, revisions.changed_at, ); } revisions.changed_at = old_memo.revisions.changed_at; } } } } #[cold] #[inline(never)] fn report_backdate_violation( index: DatabaseKeyIndex, old_changed_at: crate::Revision, new_changed_at: crate::Revision, ) { if cfg!(debug_assertions) { let message = BackdateViolation { index, old_changed_at, new_changed_at, backtrace: None, }; panic!("{message}"); } else { let message = BackdateViolation { index, old_changed_at, new_changed_at, backtrace: Backtrace::capture(), }; crate::tracing::warn!("{message}"); } } struct BackdateViolation { index: DatabaseKeyIndex, old_changed_at: crate::Revision, new_changed_at: crate::Revision, backtrace: Option, } impl fmt::Display for BackdateViolation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "query {:?} returned the same value, but the previous execution changed at {:?} and \ the new execution changed at {:?}. This usually means the query re-executed because \ an input changed, but then branched on untracked state (for example, a global \ variable, a non-salsa field on the database, or filesystem state read outside salsa) \ and no longer read that input. This is usually a bug in the query implementation. \ Queries that branch on untracked state can also produce stale results. If the query \ has no untracked reads, please open a Salsa issue.", self.index, self.old_changed_at, self.new_changed_at, )?; if let Some(backtrace) = &self.backtrace { write!(f, "\n{backtrace}")?; } Ok(()) } } salsa-0.26.2/src/function/delete.rs000064400000000000000000000033561046102023000152510ustar 00000000000000use std::ptr::NonNull; use crate::function::Configuration; use crate::function::memo::Memo; /// Stores the list of memos that have been deleted so they can be freed /// once the next revision starts. See the comment on the field /// `deleted_entries` of [`FunctionIngredient`][] for more details. pub(super) struct DeletedEntries { memos: boxcar::Vec>>, } #[allow(clippy::undocumented_unsafe_blocks)] // TODO(#697) document safety unsafe impl Send for SharedBox {} #[allow(clippy::undocumented_unsafe_blocks)] // TODO(#697) document safety unsafe impl Sync for SharedBox {} impl Default for DeletedEntries { fn default() -> Self { Self { memos: Default::default(), } } } impl DeletedEntries { /// # Safety /// /// The memo must be valid and safe to free when the `DeletedEntries` list is cleared or dropped. pub(super) unsafe fn push(&self, memo: NonNull>) { // Safety: The memo must be valid and safe to free when the `DeletedEntries` list is cleared or dropped. let memo = unsafe { std::mem::transmute::>, NonNull>>(memo) }; self.memos.push(SharedBox(memo)); } /// Free all deleted memos, keeping the list available for reuse. pub(super) fn clear(&mut self) { self.memos.clear(); } } /// A wrapper around `NonNull` that frees the allocation when it is dropped. struct SharedBox(NonNull); impl Drop for SharedBox { fn drop(&mut self) { // SAFETY: Guaranteed by the caller of `DeletedEntries::push`. unsafe { drop(Box::from_raw(self.0.as_ptr())) }; } } salsa-0.26.2/src/function/diff_outputs.rs000064400000000000000000000043221046102023000165140ustar 00000000000000use crate::active_query::CompletedQuery; use crate::function::memo::Memo; use crate::function::{Configuration, IngredientImpl}; use crate::hash::FxIndexSet; use crate::zalsa::Zalsa; use crate::zalsa_local::{QueryOriginRef, QueryRevisions, output_edges}; use crate::{DatabaseKeyIndex, Event, EventKind}; impl IngredientImpl where C: Configuration, { /// Compute the old and new outputs and invoke `remove_stale_output` for each output that /// was generated before but is not generated now. pub(super) fn diff_outputs( &self, zalsa: &Zalsa, key: DatabaseKeyIndex, old_memo: &Memo<'_, C>, completed_query: &CompletedQuery, ) { diff_outputs_on_revision(zalsa, key, &old_memo.revisions, completed_query); } } fn diff_outputs_on_revision( zalsa: &Zalsa, key: DatabaseKeyIndex, old_revisions: &QueryRevisions, completed_query: &CompletedQuery, ) { let (QueryOriginRef::Derived(edges) | QueryOriginRef::DerivedUntracked(edges)) = old_revisions.origin.as_ref() else { return; }; // Note that tracked structs are not stored as direct query outputs, but they are still outputs // that need to be reported as stale. for (identity, id) in &completed_query.stale_tracked_structs { let output = DatabaseKeyIndex::new(identity.ingredient_index(), *id); report_stale_output(zalsa, key, output); } let mut stale_outputs = output_edges(edges).collect::>(); if stale_outputs.is_empty() { return; } // Preserve any outputs that were recreated in the current revision. for new_output in completed_query.revisions.origin.as_ref().outputs() { stale_outputs.swap_remove(&new_output); } // Any outputs that were created in a previous revision but not the current one are stale. for output in stale_outputs { report_stale_output(zalsa, key, output); } } fn report_stale_output(zalsa: &Zalsa, key: DatabaseKeyIndex, output: DatabaseKeyIndex) { zalsa.event(&|| { Event::new(EventKind::WillDiscardStaleOutput { execute_key: key, output_key: output, }) }); output.remove_stale_output(zalsa, key); } salsa-0.26.2/src/function/eviction/lru.rs000064400000000000000000000031021046102023000164160ustar 00000000000000//! Least Recently Used (LRU) eviction policy. //! //! This policy tracks the most recently accessed items and evicts //! the least recently used ones when the cache exceeds its capacity. use std::num::NonZeroUsize; use crate::Id; use crate::hash::FxLinkedHashSet; use crate::sync::Mutex; use super::{EvictionPolicy, HasCapacity}; /// Least Recently Used eviction policy. /// /// When the number of memoized values exceeds the configured capacity, /// the least recently accessed values are evicted at the start of each /// new revision. pub struct Lru { capacity: Option, set: Mutex>, } impl Lru { #[inline(never)] fn insert(&self, id: Id) { self.set.lock().insert(id); } } impl EvictionPolicy for Lru { fn new(cap: usize) -> Self { Self { capacity: NonZeroUsize::new(cap), set: Mutex::default(), } } #[inline(always)] fn record_use(&self, id: Id) { if self.capacity.is_some() { self.insert(id); } } fn set_capacity(&mut self, capacity: usize) { self.capacity = NonZeroUsize::new(capacity); if self.capacity.is_none() { self.set.get_mut().clear(); } } fn for_each_evicted(&mut self, mut cb: impl FnMut(Id)) { let Some(cap) = self.capacity else { return; }; let set = self.set.get_mut(); while set.len() > cap.get() { if let Some(id) = set.pop_front() { cb(id); } } } } impl HasCapacity for Lru {} salsa-0.26.2/src/function/eviction/noop.rs000064400000000000000000000010511046102023000165700ustar 00000000000000//! No-op eviction policy - cache grows unbounded. //! //! This is the default eviction policy when no LRU capacity is specified. use crate::{Id, function::EvictionPolicy}; /// No eviction - cache grows unbounded. pub struct NoopEviction; impl EvictionPolicy for NoopEviction { fn new(_cap: usize) -> Self { Self } #[inline(always)] fn record_use(&self, _id: Id) {} #[inline(always)] fn set_capacity(&mut self, _capacity: usize) {} #[inline(always)] fn for_each_evicted(&mut self, _cb: impl FnMut(Id)) {} } salsa-0.26.2/src/function/eviction.rs000064400000000000000000000026001046102023000156160ustar 00000000000000//! Pluggable cache eviction strategies for memoized function values. //! //! This module provides the [`EvictionPolicy`] trait that allows different //! eviction strategies to be used for salsa tracked functions. mod lru; mod noop; pub use lru::Lru; pub use noop::NoopEviction; use crate::Id; /// Trait for cache eviction strategies. /// /// Implementations control when memoized values are evicted from the cache. /// The eviction policy is selected at compile time via the `Configuration` trait. pub trait EvictionPolicy: Send + Sync { /// Create a new eviction policy with the given capacity. fn new(capacity: usize) -> Self; /// Record that an item was accessed. fn record_use(&self, id: Id); /// Set the maximum capacity. fn set_capacity(&mut self, capacity: usize); /// Iterate over items that should be evicted. /// /// Called once per revision during `reset_for_new_revision`. /// The callback `cb` should be invoked for each item to evict. fn for_each_evicted(&mut self, cb: impl FnMut(Id)); } /// Marker trait for eviction policies that have a configurable capacity. /// /// This trait is used to conditionally generate the `set_lru_capacity` method /// on tracked functions. Only policies that implement this trait will expose /// runtime capacity configuration. pub trait HasCapacity: EvictionPolicy {} salsa-0.26.2/src/function/execute.rs000064400000000000000000001037401046102023000154470ustar 00000000000000use smallvec::SmallVec; use crate::active_query::CompletedQuery; use crate::cycle::{CycleHeads, CycleRecoveryStrategy, IterationCount}; use crate::function::memo::Memo; use crate::function::sync::ReleaseMode; use crate::function::{ClaimGuard, Configuration, IngredientImpl}; use crate::hash::{FxHashSet, FxIndexSet}; use crate::ingredient::WaitForResult; use crate::plumbing::ZalsaLocal; use crate::sync::thread; use crate::tracked_struct::Identity; use crate::zalsa::{MemoIngredientIndex, Zalsa}; use crate::zalsa_local::{ActiveQueryGuard, QueryEdge, QueryEdgeKind, QueryRevisions}; use crate::{Cancelled, Cycle, tracing}; use crate::{DatabaseKeyIndex, Event, EventKind, Id}; impl IngredientImpl where C: Configuration, { /// Executes the query function for the given `active_query`. Creates and stores /// a new memo with the result, backdated if possible. Once this completes, /// the query will have been popped off the active query stack. /// /// # Parameters /// /// * `db`, the database. /// * `active_query`, the active stack frame for the query to execute. /// * `opt_old_memo`, the older memo, if any existed. Used for backdating. /// /// # Returns /// The newly computed memo or `None` if this query is part of a larger cycle /// and `execute` blocked on a cycle head running on another thread. In this case, /// the memo is potentially outdated and needs to be refetched. #[inline(never)] pub(super) fn execute<'db>( &'db self, db: &'db C::DbView, mut claim_guard: ClaimGuard<'db>, opt_old_memo: Option<&Memo<'db, C>>, ) -> Option<&'db Memo<'db, C>> { let database_key_index = claim_guard.database_key_index(); let zalsa = claim_guard.zalsa(); let id = database_key_index.key_index(); let memo_ingredient_index = self.memo_ingredient_index(zalsa, id); crate::tracing::info!("{:?}: executing query", database_key_index); zalsa.event(&|| { Event::new(EventKind::WillExecute { database_key: database_key_index, }) }); let (new_value, mut completed_query) = match C::CYCLE_STRATEGY { CycleRecoveryStrategy::Panic => { let (new_value, active_query) = Self::execute_query( db, zalsa, claim_guard .zalsa_local() .push_query(database_key_index, IterationCount::initial()), opt_old_memo, ); (new_value, active_query.pop()) } CycleRecoveryStrategy::FallbackImmediate | CycleRecoveryStrategy::Fixpoint => { let zalsa_local = claim_guard.zalsa_local(); let was_disabled = zalsa_local.set_cancellation_disabled(true); let res = self.execute_maybe_iterate( db, opt_old_memo, &mut claim_guard, memo_ingredient_index, ); zalsa_local.set_cancellation_disabled(was_disabled); res } }; if let Some(old_memo) = opt_old_memo { // If the new value is equal to the old one, then it didn't // really change, even if some of its inputs have. So we can // "backdate" its `changed_at` revision to be the same as the // old value. self.backdate_if_appropriate( old_memo, database_key_index, &mut completed_query.revisions, &new_value, ); // Diff the new outputs with the old, to discard any no-longer-emitted // outputs and update the tracked struct IDs for seeding the next revision. self.diff_outputs(zalsa, database_key_index, old_memo, &completed_query); } let memo = self.insert_memo( zalsa, id, Memo::new( Some(new_value), zalsa.current_revision(), completed_query.revisions, ), memo_ingredient_index, ); if claim_guard.drop() { None } else { Some(memo) } } fn execute_maybe_iterate<'db>( &'db self, db: &'db C::DbView, opt_old_memo: Option<&Memo<'db, C>>, claim_guard: &mut ClaimGuard<'db>, memo_ingredient_index: MemoIngredientIndex, ) -> (C::Output<'db>, CompletedQuery) { claim_guard.set_release_mode(ReleaseMode::Default); let database_key_index = claim_guard.database_key_index(); let zalsa = claim_guard.zalsa(); let id = database_key_index.key_index(); // Our provisional value from the previous iteration, when doing fixpoint iteration. // This is different from `opt_old_memo` which might be from a different revision. let mut last_provisional_memo_opt: Option<&Memo<'db, C>> = None; let mut last_stale_tracked_ids: Vec<(Identity, Id)> = Vec::new(); let mut iteration_count = IterationCount::initial(); if let Some(old_memo) = opt_old_memo { if old_memo.verified_at.load() == zalsa.current_revision() { // The `DependencyGraph` locking propagates panics when another thread is blocked on a panicking query. // However, the locking doesn't handle the case where a thread fetches the result of a panicking // cycle head query **after** all locks were released. That's what we do here. // We could consider re-executing the entire cycle but: // a) It's tricky to ensure that all queries participating in the cycle will re-execute // (we can't rely on `iteration_count` being updated for nested cycles because the nested cycles may have completed successfully). // b) It's guaranteed that this query will panic again anyway. // That's why we simply propagate the panic here. It simplifies our lives and it also avoids duplicate panic messages. if old_memo.value.is_none() { tracing::warn!( "Propagating panic for cycle head that panicked in an earlier execution in that revision" ); Cancelled::PropagatedPanic.throw(); } // Only use the last provisional memo if it was a cycle head in the last iteration. This is to // force at least two executions. if old_memo.cycle_heads().contains(&database_key_index) { last_provisional_memo_opt = Some(old_memo); } iteration_count = old_memo.revisions.iteration(); } } let _poison_guard = PoisonProvisionalIfPanicking::new(self, zalsa, id, memo_ingredient_index); let (new_value, completed_query) = loop { let active_query = claim_guard .zalsa_local() .push_query(database_key_index, iteration_count); // Tracked struct ids that existed in the previous revision // but weren't recreated in the last iteration. It's important that we seed the next // query with these ids because the query might re-create them as part of the next iteration. // This is not only important to ensure that the re-created tracked structs have the same ids, // it's also important to ensure that these tracked structs get removed // if they aren't recreated when reaching the final iteration. active_query.seed_tracked_struct_ids(&last_stale_tracked_ids); let (mut new_value, mut active_query) = Self::execute_query( db, zalsa, active_query, last_provisional_memo_opt.or(opt_old_memo), ); // Take the cycle heads to not-fight-rust's-borrow-checker. let mut cycle_heads = active_query.take_cycle_heads(); // If there are no cycle heads, break out of the loop. if cycle_heads.is_empty() { let mut completed_query = active_query.pop(); if !iteration_count.is_initial() { iteration_count = iteration_count.increment().unwrap_or_else(|| { tracing::warn!( "{database_key_index:?}: execute: too many cycle iterations" ); panic!("{database_key_index:?}: execute: too many cycle iterations") }); completed_query .revisions .update_cycle_participant_iteration_count(iteration_count); } break (new_value, completed_query); } let (max_iteration_count, depends_on_self) = collect_all_cycle_heads( zalsa, &mut cycle_heads, database_key_index, iteration_count, ); let outer_cycle = outer_cycle( zalsa, claim_guard.zalsa_local(), &cycle_heads, database_key_index, ); // Did the new result we got depend on our own provisional value, in a cycle? // If not, return because this query is not a cycle head. if !depends_on_self { let Some(outer_cycle) = outer_cycle else { panic!( "cycle participant with non-empty cycle heads and that doesn't depend on itself must have an outer cycle responsible to finalize the query later (query: {database_key_index:?}, cycle heads: {cycle_heads:?})." ); }; // For FallbackImmediate, use the fallback value instead of the computed value // for all cycle participants. This ensures that the results don't depend on the query call order, see // https://github.com/salsa-rs/salsa/pull/798#issuecomment-2812855285. let new_value = if C::CYCLE_STRATEGY == CycleRecoveryStrategy::FallbackImmediate { C::cycle_initial(db, id, C::id_to_input(zalsa, id)) } else { new_value }; let completed_query = complete_cycle_participant( active_query, claim_guard, cycle_heads, outer_cycle, iteration_count, ); break (new_value, completed_query); } // Get the last provisional value for this query so that we can compare it with the new value // to test if the cycle converged. let last_provisional_memo = last_provisional_memo_opt.unwrap_or_else(|| { // This is our first time around the loop; a provisional value must have been // inserted into the memo table when the cycle was hit, so let's pull our // initial provisional value from there. let memo = self .get_memo_from_table_for(zalsa, id, memo_ingredient_index) .unwrap_or_else(|| { unreachable!( "{database_key_index:#?} is a cycle head, \ but no provisional memo found" ) }); debug_assert!(memo.may_be_provisional()); memo }); let last_provisional_value = last_provisional_memo.value.as_ref(); let last_provisional_value = last_provisional_value.expect( "`fetch_cold_cycle` should have inserted a provisional memo with Cycle::initial", ); tracing::debug!( "{database_key_index:?}: execute: \ I am a cycle head, comparing last provisional value with new value" ); // If this is the outermost cycle, use the maximum iteration count of all cycles. // This is important for when later iterations introduce new cycle heads (that then // become the outermost cycle). We want to ensure that the iteration count keeps increasing // for all queries or they won't be re-executed because `validate_same_iteration` would // pass when we go from 1 -> 0 and then increment by 1 to 1). iteration_count = if outer_cycle.is_none() { max_iteration_count } else { // Otherwise keep the iteration count because outer cycles // already have a cycle head with this exact iteration count (and we don't allow // heads from different iterations). iteration_count }; // For FallbackImmediate, the value always converges immediately (we use the // fallback directly). We still iterate if metadata hasn't converged. // For Fixpoint, ask the recovery function what value to use and check convergence. let value_converged = if C::CYCLE_STRATEGY == CycleRecoveryStrategy::FallbackImmediate { // Use the fallback value instead of the computed value. new_value = C::cycle_initial(db, id, C::id_to_input(zalsa, id)); true } else { let cycle = Cycle { head_ids: cycle_heads.ids(), id, iteration: iteration_count.as_u32(), }; // We are in a cycle that hasn't converged; ask the user's // cycle-recovery function what to do (it may return the same value or a different one): new_value = C::recover_from_cycle( db, &cycle, last_provisional_value, new_value, C::id_to_input(zalsa, id), ); C::values_equal(&new_value, last_provisional_value) }; let new_cycle_heads = active_query.take_cycle_heads(); assert_no_new_cycle_heads(&cycle_heads, new_cycle_heads, database_key_index); let completed_query = match try_complete_cycle_head( active_query, claim_guard, cycle_heads, &last_provisional_memo.revisions, outer_cycle, iteration_count, value_converged, ) { Ok(completed_query) => { break (new_value, completed_query); } Err((completed_query, new_iteration_count)) => { iteration_count = new_iteration_count; completed_query } }; let new_memo = self.insert_memo( zalsa, id, Memo::new( Some(new_value), zalsa.current_revision(), completed_query.revisions, ), memo_ingredient_index, ); last_provisional_memo_opt = Some(new_memo); last_stale_tracked_ids = completed_query.stale_tracked_structs; continue; }; tracing::debug!( "{database_key_index:?}: execute_maybe_iterate: result.revisions = {revisions:#?}", revisions = &completed_query.revisions ); (new_value, completed_query) } #[inline] fn execute_query<'db>( db: &'db C::DbView, zalsa: &'db Zalsa, active_query: ActiveQueryGuard<'db>, opt_old_memo: Option<&Memo<'db, C>>, ) -> (C::Output<'db>, ActiveQueryGuard<'db>) { if let Some(old_memo) = opt_old_memo { // If we already executed this query once, then use the tracked-struct ids from the // previous execution as the starting point for the new one. active_query.seed_tracked_struct_ids(old_memo.revisions.tracked_struct_ids()); // Copy over all inputs and outputs from a previous iteration. // This is necessary to: // * ensure that tracked struct created during the previous iteration // (and are owned by the query) are alive even if the query in this iteration no longer creates them. // * ensure the final returned memo depends on all inputs from all iterations. if old_memo.may_be_provisional() && old_memo.verified_at.load() == zalsa.current_revision() { active_query.seed_iteration(&old_memo.revisions); } } // Query was not previously executed, or value is potentially // stale, or value is absent. Let's execute! let new_value = C::execute( db, C::id_to_input(zalsa, active_query.database_key_index.key_index()), ); (new_value, active_query) } } /// Replaces any inserted memo with a fixpoint initial memo without a value if the current thread panics. /// /// A regular query doesn't insert any memo if it panics and the query /// simply gets re-executed if any later called query depends on the panicked query (and will panic again unless the query isn't deterministic). /// /// Unfortunately, this isn't the case for cycle heads because Salsa first inserts the fixpoint initial memo and later inserts /// provisional memos for every iteration. Detecting whether a query has previously panicked /// in `fetch` (e.g., `validate_same_iteration`) and requires re-execution is probably possible but not very straightforward /// and it's easy to get it wrong, which results in infinite loops where `Memo::provisional_retry` keeps retrying to get the latest `Memo` /// but `fetch` doesn't re-execute the query for reasons. /// /// Specifically, a Memo can linger after a panic, which is then incorrectly returned /// by `fetch_cold_cycle` because it passes the `shallow_verified_memo` check instead of inserting /// a new fix point initial value if that happens. /// /// We could insert a fixpoint initial value here, but it seems unnecessary. struct PoisonProvisionalIfPanicking<'a, C: Configuration> { ingredient: &'a IngredientImpl, zalsa: &'a Zalsa, id: Id, memo_ingredient_index: MemoIngredientIndex, } impl<'a, C: Configuration> PoisonProvisionalIfPanicking<'a, C> { fn new( ingredient: &'a IngredientImpl, zalsa: &'a Zalsa, id: Id, memo_ingredient_index: MemoIngredientIndex, ) -> Self { Self { ingredient, zalsa, id, memo_ingredient_index, } } } impl Drop for PoisonProvisionalIfPanicking<'_, C> { fn drop(&mut self) { if thread::panicking() { let revisions = QueryRevisions::fixpoint_initial( self.ingredient.database_key_index(self.id), IterationCount::initial(), ); let memo = Memo::new(None, self.zalsa.current_revision(), revisions); self.ingredient .insert_memo(self.zalsa, self.id, memo, self.memo_ingredient_index); } } } /// Returns the key of any potential outer cycle head or `None` if there is no outer cycle. /// /// That is, any query that's currently blocked on the result computed by this query (claiming it results in a cycle). fn outer_cycle( zalsa: &Zalsa, zalsa_local: &ZalsaLocal, cycle_heads: &CycleHeads, current_key: DatabaseKeyIndex, ) -> Option { // First, look for the outer most cycle head on the same thread. // Using the outer most over the inner most should reduce the need // for transitive transfers. // SAFETY: We don't call into with_query_stack recursively if let Some(same_thread) = unsafe { zalsa_local.with_query_stack_unchecked(|stack| { stack .iter() .find(|active_query| { active_query.database_key_index != current_key && cycle_heads.contains(&active_query.database_key_index) }) .map(|active_query| active_query.database_key_index) }) } { return Some(same_thread); } // Check for any outer cycle head running on a different thread. cycle_heads .iter_not_eq(current_key) .rfind(|head| { let ingredient = zalsa.lookup_ingredient(head.database_key_index.ingredient_index()); matches!( ingredient.wait_for(zalsa, head.database_key_index.key_index()), WaitForResult::Cycle { inner: false } ) }) .map(|head| head.database_key_index) } /// Ensure that we resolve the latest cycle heads from any provisional value this query depended on during execution. /// /// ```txt /// E -> C -> D -> B -> A -> B (cycle) /// -- A completes, heads = [B] /// E -> C -> D -> B -> C (cycle) /// -> D (cycle) /// -- B completes, heads = [B, C, D] /// E -> C -> D -> E (cycle) /// -- D completes, heads = [E, B, C, D] /// E -> C /// -- C completes, heads = [E, B, C, D] /// E -> X -> A /// -- X completes, heads = [B] /// ``` /// /// Note how `X` only depends on `A`. It doesn't know that it's part of the outer cycle `X`. /// An old implementation resolved the cycle heads 1-level deep but that's not enough, because /// `X` then completes with `[B, C, D]` as it's heads. But `B`, `C`, and `D` are no longer on the stack /// when `X` completes (which is the real outermost cycle). That's why we need to resolve all cycle heads /// recursively, so that `X` completes with `[B, C, D, E] fn collect_all_cycle_heads( zalsa: &Zalsa, cycle_heads: &mut CycleHeads, database_key_index: DatabaseKeyIndex, iteration_count: IterationCount, ) -> (IterationCount, bool) { fn collect_recursive( zalsa: &Zalsa, current_head: DatabaseKeyIndex, me: DatabaseKeyIndex, query_heads: &CycleHeads, missing_heads: &mut SmallVec<[(DatabaseKeyIndex, IterationCount); 4]>, ) -> (IterationCount, bool) { if current_head == me { return (IterationCount::initial(), true); } let mut max_iteration_count = IterationCount::initial(); let mut depends_on_self = false; let ingredient = zalsa.lookup_ingredient(current_head.ingredient_index()); let provisional_status = ingredient .provisional_status(zalsa, current_head.key_index()) .expect("cycle head memo must have been created during the execution"); // A query should only ever depend on other heads that are provisional. // If this invariant is violated, it means that this query participates in a cycle, // but it wasn't executed in the last iteration of said cycle. assert!(provisional_status.is_provisional()); for head in provisional_status.cycle_heads() { let iteration_count = head.iteration_count.load(); max_iteration_count = max_iteration_count.max(iteration_count); if query_heads.contains(&head.database_key_index) { continue; } let head_as_tuple = (head.database_key_index, iteration_count); if missing_heads.contains(&head_as_tuple) { continue; } missing_heads.push((head.database_key_index, iteration_count)); let (nested_max_iteration_count, nested_depends_on_self) = collect_recursive( zalsa, head.database_key_index, me, query_heads, missing_heads, ); max_iteration_count = max_iteration_count.max(nested_max_iteration_count); depends_on_self |= nested_depends_on_self; } (max_iteration_count, depends_on_self) } let mut missing_heads: SmallVec<[(DatabaseKeyIndex, IterationCount); 4]> = SmallVec::new(); let mut max_iteration_count = iteration_count; let mut depends_on_self = false; for head in &*cycle_heads { let (recursive_max_iteration, recursive_depends_on_self) = collect_recursive( zalsa, head.database_key_index, database_key_index, cycle_heads, &mut missing_heads, ); max_iteration_count = max_iteration_count.max(recursive_max_iteration); depends_on_self |= recursive_depends_on_self; } for (head, iteration) in missing_heads { cycle_heads.insert(head, iteration); } (max_iteration_count, depends_on_self) } // Called when completing the query of a cycle head participating // in an outer cycle head (which doesn't depend on itself). fn complete_cycle_participant( active_query: ActiveQueryGuard, claim_guard: &mut ClaimGuard, cycle_heads: CycleHeads, outer_cycle: DatabaseKeyIndex, iteration_count: IterationCount, ) -> CompletedQuery { // For as long as this query participates in any cycle, don't release its lock, instead // transfer it to the outermost cycle head. This prevents any other thread // from claiming this query (all cycle heads are potential entry points to the same cycle), // which would result in them competing for the same locks (we want the locks to converge to a single cycle head). claim_guard.set_release_mode(ReleaseMode::TransferTo(outer_cycle)); let zalsa = claim_guard.zalsa(); let database_key_index = active_query.database_key_index; let mut completed_query = active_query.pop(); flatten_cycle_dependencies(zalsa, &mut completed_query.revisions); *completed_query.revisions.verified_final.get_mut() = false; completed_query.revisions.set_cycle_heads(cycle_heads); let iteration_count = iteration_count.increment().unwrap_or_else(|| { tracing::warn!("{database_key_index:?}: execute: too many cycle iterations"); panic!("{database_key_index:?}: execute: too many cycle iterations") }); // The outermost query only bumps the iteration count of cycle heads. It doesn't // increment the iteration count for cycle participants. It's important that we bump the // iteration count here or the head will re-use the same iteration count in the next // iteration (which can break cache invalidation). completed_query .revisions .update_cycle_participant_iteration_count(iteration_count); completed_query } /// Tries to complete the cycle head if it has converged. /// /// Returns `Ok` if the cycle head has converged or if it is part of an outer cycle. /// Returns `Err` if the cycle head needs to keep iterating. fn try_complete_cycle_head( active_query: ActiveQueryGuard, claim_guard: &mut ClaimGuard, cycle_heads: CycleHeads, last_provisional_revisions: &QueryRevisions, outer_cycle: Option, iteration_count: IterationCount, value_converged: bool, ) -> Result { let me = active_query.database_key_index; let zalsa = claim_guard.zalsa(); let mut completed_query = active_query.pop(); flatten_cycle_dependencies(zalsa, &mut completed_query.revisions); // It's important to force a re-execution of the cycle if `changed_at` or `durability` has changed // to ensure the reduced durability and changed propagates to all queries depending on this head. let metadata_converged = last_provisional_revisions.durability == completed_query.revisions.durability && last_provisional_revisions.changed_at == completed_query.revisions.changed_at && last_provisional_revisions.origin.is_derived_untracked() == completed_query.revisions.origin.is_derived_untracked(); let this_converged = value_converged && metadata_converged; if let Some(outer_cycle) = outer_cycle { tracing::info!( "Detected nested cycle {me:?}, iterate it as part of the outer cycle {outer_cycle:?}" ); completed_query.revisions.set_cycle_heads(cycle_heads); // Store whether this cycle has converged, so that the outer cycle can check it. completed_query .revisions .set_cycle_converged(this_converged); *completed_query.revisions.verified_final.get_mut() = false; // Transfer ownership of this query to the outer cycle, so that it can claim it // and other threads don't compete for the same lock. claim_guard.set_release_mode(ReleaseMode::TransferTo(outer_cycle)); return Ok(completed_query); } // This is the outermost cycle, drive the cycle forward: // ..test if all inner cycles have converged as well. let converged = this_converged && cycle_heads.iter_not_eq(me).all(|head| { let database_key_index = head.database_key_index; let ingredient = zalsa.lookup_ingredient(database_key_index.ingredient_index()); let converged = ingredient.cycle_converged(zalsa, database_key_index.key_index()); if !converged { tracing::debug!("inner cycle {database_key_index:?} has not converged",); } converged }); if converged { tracing::debug!( "{me:?}: execute: fixpoint iteration has a final value after {iteration_count:?} iterations" ); // Set the nested cycles as verified. This is necessary because // `validate_provisional` doesn't follow cycle heads recursively (and the memos now depend on all cycle heads). for head in cycle_heads.iter_not_eq(me) { let ingredient = zalsa.lookup_ingredient(head.database_key_index.ingredient_index()); ingredient.finalize_cycle_head(zalsa, head.database_key_index.key_index()); } *completed_query.revisions.verified_final.get_mut() = true; zalsa.event(&|| { Event::new(EventKind::DidFinalizeCycle { database_key: me, iteration_count, }) }); return Ok(completed_query); } // The fixpoint iteration hasn't converged. Iterate again... let iteration_count = iteration_count.increment().unwrap_or_else(|| { tracing::warn!("{me:?}: execute: too many cycle iterations"); panic!("{me:?}: execute: too many cycle iterations") }); zalsa.event(&|| { Event::new(EventKind::WillIterateCycle { database_key: me, iteration_count, }) }); tracing::info!("{me:?}: execute: iterate again ({iteration_count:?})...",); // Update the iteration count of nested cycles. for head in cycle_heads.iter_not_eq(me) { let ingredient = zalsa.lookup_ingredient(head.database_key_index.ingredient_index()); ingredient.set_cycle_iteration_count( zalsa, head.database_key_index.key_index(), iteration_count, ); } debug_assert!(completed_query.revisions.cycle_heads().is_empty()); // Update the iteration count of this cycle head, but only after restoring // the cycle heads array (or this becomes a no-op). // We don't call the same method on `cycle_heads` because that one doens't update // the `memo.iteration_count` *completed_query.revisions.verified_final.get_mut() = false; completed_query.revisions.set_cycle_heads(cycle_heads); completed_query .revisions .update_iteration_count_mut(me, iteration_count); Err((completed_query, iteration_count)) } fn assert_no_new_cycle_heads( cycle_heads: &CycleHeads, new_cycle_heads: CycleHeads, me: DatabaseKeyIndex, ) { for head in new_cycle_heads { if !cycle_heads.contains(&head.database_key_index) { panic!( "Cycle recovery function for {me:?} introduced a cycle, depending on {:?}. This is not allowed.", head.database_key_index ); } } } thread_local! { /// Pool the `seen` and `flattened` sets for reuse on the same thread. /// /// Benchmarks showed that repeatedly allocating and regrowing those sets is expensive. static FLATTEN_MAPS: std::cell::Cell, FxHashSet)>> = const { std::cell::Cell::new(None) }; } /// Flattens the dependencies of `head` so that `head`'s origin only depends on finalized queries, /// or salsa structs (input, tracked, interned). fn flatten_cycle_dependencies(zalsa: &Zalsa, head: &mut QueryRevisions) { let (mut flattened, mut seen) = FLATTEN_MAPS.take().unwrap_or_default(); debug_assert!(flattened.is_empty()); debug_assert!(seen.is_empty()); #[cfg(feature = "accumulator")] { assert!( head.accumulated_inputs.load().is_empty(), "Fixpoint iteration doesn't support accumulated values because it doesn't preserve the original query dependency tree." ) } // Don't insert the key of `head` here. This is important to ensure that we copy over the // dependencies from this memo in the previous iteration. // e.g. if we have `a2 -> b2 -> a1`, we need to copy over `a`'s dependencies from iteration 1. let edges = head.origin.as_ref().edges(); flattened.reserve(edges.len()); for edge in head.origin.as_ref().edges() { match edge.kind() { QueryEdgeKind::Input(input) => { let ingredient = zalsa.lookup_ingredient(input.ingredient_index()); ingredient.flatten_cycle_head_dependencies( zalsa, input.key_index(), &mut flattened, &mut seen, ); } QueryEdgeKind::Output(_) => { // Unlike `ingredient.collect_flattened_cycle_inputs`, carry over outputs // created by the query head because those are owned by this query. flattened.insert(*edge); } } } head.origin .set_edges(flattened.drain(..).collect()) .expect("Executing query to always be derived or derived untracked."); seen.clear(); FLATTEN_MAPS.set(Some((flattened, seen))); } salsa-0.26.2/src/function/fetch.rs000064400000000000000000000220271046102023000150740ustar 00000000000000use crate::cycle::{CycleRecoveryStrategy, IterationCount}; use crate::function::eviction::EvictionPolicy; use crate::function::memo::Memo; use crate::function::sync::ClaimResult; use crate::function::{Configuration, IngredientImpl, Reentrancy}; use crate::zalsa::{MemoIngredientIndex, Zalsa}; use crate::zalsa_local::{QueryRevisions, ZalsaLocal}; use crate::{DatabaseKeyIndex, Id}; impl IngredientImpl where C: Configuration, { #[inline] pub fn fetch<'db>( &'db self, db: &'db C::DbView, zalsa: &'db Zalsa, zalsa_local: &'db ZalsaLocal, id: Id, ) -> &'db C::Output<'db> { zalsa.unwind_if_revision_cancelled(zalsa_local); let database_key_index = self.database_key_index(id); #[cfg(debug_assertions)] let _span = crate::tracing::debug_span!("fetch", query = ?database_key_index).entered(); let memo = self.refresh_memo(db, zalsa, zalsa_local, id); // SAFETY: We just refreshed the memo so it is guaranteed to contain a value now. let memo_value = unsafe { memo.value.as_ref().unwrap_unchecked() }; self.eviction.record_use(id); zalsa_local.report_tracked_read( database_key_index, memo.revisions.durability, memo.revisions.changed_at, memo.cycle_heads(), #[cfg(feature = "accumulator")] memo.revisions.accumulated().is_some(), #[cfg(feature = "accumulator")] &memo.revisions.accumulated_inputs, ); memo_value } #[inline(always)] pub(super) fn refresh_memo<'db>( &'db self, db: &'db C::DbView, zalsa: &'db Zalsa, zalsa_local: &'db ZalsaLocal, id: Id, ) -> &'db Memo<'db, C> { let memo_ingredient_index = self.memo_ingredient_index(zalsa, id); loop { if let Some(memo) = self .fetch_hot(zalsa, id, memo_ingredient_index) .or_else(|| self.fetch_cold(zalsa, zalsa_local, db, id, memo_ingredient_index)) { return memo; } } } #[inline(always)] fn fetch_hot<'db>( &'db self, zalsa: &'db Zalsa, id: Id, memo_ingredient_index: MemoIngredientIndex, ) -> Option<&'db Memo<'db, C>> { let memo = self.get_memo_from_table_for(zalsa, id, memo_ingredient_index)?; memo.value.as_ref()?; let database_key_index = self.database_key_index(id); let can_shallow_update = self.shallow_verify_memo(zalsa, database_key_index, memo); if can_shallow_update.yes() && !memo.may_be_provisional() { self.update_shallow(zalsa, database_key_index, memo, can_shallow_update); // SAFETY: memo is present in memo_map and we have verified that it is // still valid for the current revision. unsafe { Some(self.extend_memo_lifetime(memo)) } } else { None } } fn fetch_cold<'db>( &'db self, zalsa: &'db Zalsa, zalsa_local: &'db ZalsaLocal, db: &'db C::DbView, id: Id, memo_ingredient_index: MemoIngredientIndex, ) -> Option<&'db Memo<'db, C>> { let database_key_index = self.database_key_index(id); // Try to claim this query: if someone else has claimed it already, go back and start again. let claim_guard = match self .sync_table .try_claim(zalsa, zalsa_local, id, Reentrancy::Allow) { ClaimResult::Claimed(guard) => guard, ClaimResult::Running(blocked_on) => { let _ = blocked_on.block_on(zalsa); return None; } ClaimResult::Cycle { .. } => { return Some(self.fetch_cold_cycle( zalsa, zalsa_local, db, id, database_key_index, memo_ingredient_index, )); } }; // Now that we've claimed the item, check again to see if there's a "hot" value. let opt_old_memo = self.get_memo_from_table_for(zalsa, id, memo_ingredient_index); if let Some(old_memo) = opt_old_memo { if old_memo.value.is_some() { let can_shallow_update = self.shallow_verify_memo(zalsa, database_key_index, old_memo); if can_shallow_update.yes() && self.validate_may_be_provisional( zalsa, zalsa_local, database_key_index, old_memo, ) { self.update_shallow(zalsa, database_key_index, old_memo, can_shallow_update); // SAFETY: memo is present in memo_map and we have verified that it is // still valid for the current revision. return unsafe { Some(self.extend_memo_lifetime(old_memo)) }; } let verify_result = self.deep_verify_memo(db, zalsa, old_memo, database_key_index); if verify_result.is_unchanged() { // SAFETY: memo is present in memo_map and we have verified that it is // still valid for the current revision. return unsafe { Some(self.extend_memo_lifetime(old_memo)) }; } } } self.execute(db, claim_guard, opt_old_memo) } #[cold] #[inline(never)] fn fetch_cold_cycle<'db>( &'db self, zalsa: &'db Zalsa, zalsa_local: &'db ZalsaLocal, db: &'db C::DbView, id: Id, database_key_index: DatabaseKeyIndex, memo_ingredient_index: MemoIngredientIndex, ) -> &'db Memo<'db, C> { // no provisional value; create/insert/return initial provisional value match C::CYCLE_STRATEGY { // SAFETY: We do not access the query stack reentrantly. CycleRecoveryStrategy::Panic => unsafe { zalsa_local.with_query_stack_unchecked(|stack| { panic!( "dependency graph cycle when querying {database_key_index:#?}, \ set cycle_fn/cycle_initial to fixpoint iterate.\n\ Query stack:\n{stack:#?}", ); }) }, CycleRecoveryStrategy::Fixpoint | CycleRecoveryStrategy::FallbackImmediate => { // check if there's a provisional value for this query // Note we don't `validate_may_be_provisional` the memo here as we want to reuse an // existing provisional memo if it exists let memo_guard = self.get_memo_from_table_for(zalsa, id, memo_ingredient_index); if let Some(memo) = &memo_guard { // Ideally, we'd use the last provisional memo even if it wasn't a cycle head in the last iteration // but that would require inserting itself as a cycle head, which either requires clone // on the value OR a concurrent `Vec` for cycle heads. if memo.verified_at.load() == zalsa.current_revision() && memo.value.is_some() && memo.revisions.cycle_heads().contains(&database_key_index) { memo.revisions .cycle_heads() .remove_all_except(database_key_index); crate::tracing::debug!( "hit cycle at {database_key_index:#?}, \ returning last provisional value: {:#?}", memo.revisions ); // SAFETY: memo is present in memo_map. return unsafe { self.extend_memo_lifetime(memo) }; } } crate::tracing::debug!( "hit cycle at {database_key_index:#?}, \ inserting and returning fixpoint initial value" ); let iteration = memo_guard .and_then(|old_memo| { if old_memo.verified_at.load() == zalsa.current_revision() && old_memo.value.is_some() { Some(old_memo.revisions.iteration()) } else { None } }) .unwrap_or(IterationCount::initial()); let revisions = QueryRevisions::fixpoint_initial(database_key_index, iteration); let initial_value = C::cycle_initial(db, id, C::id_to_input(zalsa, id)); self.insert_memo( zalsa, id, Memo::new(Some(initial_value), zalsa.current_revision(), revisions), memo_ingredient_index, ) } } } } salsa-0.26.2/src/function/inputs.rs000064400000000000000000000007571046102023000153330ustar 00000000000000use crate::Id; use crate::function::{Configuration, IngredientImpl}; use crate::zalsa::Zalsa; use crate::zalsa_local::QueryOriginRef; impl IngredientImpl where C: Configuration, { pub(super) fn origin<'db>(&self, zalsa: &'db Zalsa, key: Id) -> Option> { let memo_ingredient_index = self.memo_ingredient_index(zalsa, key); self.get_memo_from_table_for(zalsa, key, memo_ingredient_index) .map(|m| m.revisions.origin.as_ref()) } } salsa-0.26.2/src/function/maybe_changed_after.rs000064400000000000000000000603041046102023000177320ustar 00000000000000#[cfg(feature = "accumulator")] use crate::accumulator::accumulated_map::InputAccumulatedValues; use crate::cycle::{CycleHeads, CycleRecoveryStrategy, ProvisionalStatus}; use crate::function::memo::{Memo, TryClaimCycleHeadsIter, TryClaimHeadsResult}; use crate::function::sync::ClaimResult; use crate::function::{Configuration, IngredientImpl, Reentrancy}; use std::sync::atomic::Ordering; use crate::key::DatabaseKeyIndex; use crate::zalsa::{MemoIngredientIndex, Zalsa, ZalsaDatabase}; use crate::zalsa_local::{QueryEdge, QueryEdgeKind, QueryOriginRef, QueryRevisions, ZalsaLocal}; use crate::{Id, Revision}; /// Result of memo validation. #[derive(Debug, Copy, Clone)] pub enum VerifyResult { /// Memo has changed and needs to be recomputed. Changed, /// Memo remains valid. /// /// The inner value tracks whether the memo or any of its dependencies have an /// accumulated value. Unchanged { #[cfg(feature = "accumulator")] accumulated: InputAccumulatedValues, }, } impl VerifyResult { pub(crate) const fn changed_if(changed: bool) -> Self { if changed { Self::changed() } else { Self::unchanged() } } pub(crate) const fn changed() -> Self { Self::Changed } pub(crate) const fn unchanged() -> Self { Self::Unchanged { #[cfg(feature = "accumulator")] accumulated: InputAccumulatedValues::Empty, } } #[inline] #[cfg(feature = "accumulator")] pub(crate) fn unchanged_with_accumulated(accumulated: InputAccumulatedValues) -> Self { Self::Unchanged { accumulated } } #[inline] #[cfg(not(feature = "accumulator"))] pub(crate) fn unchanged_with_accumulated() -> Self { Self::unchanged() } pub(crate) const fn is_unchanged(&self) -> bool { matches!(self, Self::Unchanged { .. }) } } impl IngredientImpl where C: Configuration, { pub(super) fn maybe_changed_after<'db>( &'db self, db: &'db C::DbView, id: Id, revision: Revision, ) -> VerifyResult { let (zalsa, zalsa_local) = db.zalsas(); let memo_ingredient_index = self.memo_ingredient_index(zalsa, id); zalsa.unwind_if_revision_cancelled(zalsa_local); loop { let database_key_index = self.database_key_index(id); crate::tracing::debug!( "{database_key_index:?}: maybe_changed_after(revision = {revision:?})" ); // Check if we have a verified version: this is the hot path. let memo_guard = self.get_memo_from_table_for(zalsa, id, memo_ingredient_index); let Some(memo) = memo_guard else { // No memo? Assume has changed. return VerifyResult::changed(); }; let can_shallow_update = self.shallow_verify_memo(zalsa, database_key_index, memo); if can_shallow_update.yes() && !memo.may_be_provisional() { self.update_shallow(zalsa, database_key_index, memo, can_shallow_update); return if memo.revisions.changed_at > revision { VerifyResult::changed() } else { VerifyResult::unchanged_with_accumulated( #[cfg(feature = "accumulator")] { memo.revisions.accumulated_inputs.load() }, ) }; } if let Some(mcs) = self.maybe_changed_after_cold( zalsa, zalsa_local, db, id, revision, memo_ingredient_index, ) { return mcs; } else { // We failed to claim, have to retry. } } } #[inline(never)] fn maybe_changed_after_cold<'db>( &'db self, zalsa: &Zalsa, zalsa_local: &ZalsaLocal, db: &'db C::DbView, key_index: Id, revision: Revision, memo_ingredient_index: MemoIngredientIndex, ) -> Option { let database_key_index = self.database_key_index(key_index); let claim_guard = match self .sync_table .try_claim(zalsa, zalsa_local, key_index, Reentrancy::Deny) { ClaimResult::Claimed(guard) => guard, ClaimResult::Running(blocked_on) => { let _ = blocked_on.block_on(zalsa); return None; } ClaimResult::Cycle { .. } => { return Some(maybe_changed_after_cold_cycle( zalsa_local, database_key_index, C::CYCLE_STRATEGY, )); } }; // Load the current memo, if any. let Some(old_memo) = self.get_memo_from_table_for(zalsa, key_index, memo_ingredient_index) else { return Some(VerifyResult::changed()); }; crate::tracing::debug!( "{database_key_index:?}: maybe_changed_after_cold, successful claim, \ revision = {revision:?}, old_memo = {old_memo:#?}", old_memo = old_memo.tracing_debug() ); let can_shallow_update = self.shallow_verify_memo(zalsa, database_key_index, old_memo); if can_shallow_update.yes() && self.validate_may_be_provisional(zalsa, zalsa_local, database_key_index, old_memo) { self.update_shallow(zalsa, database_key_index, old_memo, can_shallow_update); return Some(if old_memo.revisions.changed_at > revision { VerifyResult::changed() } else { VerifyResult::unchanged_with_accumulated( #[cfg(feature = "accumulator")] { old_memo.revisions.accumulated_inputs.load() }, ) }); } let deep_verify = self.deep_verify_memo(db, zalsa, old_memo, database_key_index); if deep_verify.is_unchanged() { // Check if the inputs are still valid. We can just compare `changed_at`. return Some(if old_memo.revisions.changed_at > revision { VerifyResult::changed() } else { // Returns unchanged but propagates the accumulated values deep_verify }); } // If inputs have changed, but we have an old value, we can re-execute. // It is possible the result will be equal to the old value and hence // backdated. In that case, although we will have computed a new memo, // the value has not logically changed. if old_memo.value.is_some() && !old_memo.may_be_provisional() { let memo = self.execute(db, claim_guard, Some(old_memo))?; let changed_at = memo.revisions.changed_at; // Always assume that a provisional value has changed. // // We don't know if a provisional value has actually changed. To determine whether a provisional // value has changed, we need to iterate the outer cycle, which cannot be done here. return Some(if changed_at > revision || memo.may_be_provisional() { VerifyResult::changed() } else { VerifyResult::unchanged_with_accumulated( #[cfg(feature = "accumulator")] match memo.revisions.accumulated() { Some(_) => InputAccumulatedValues::Any, None => memo.revisions.accumulated_inputs.load(), }, ) }); } // Otherwise, nothing for it: have to consider the value to have changed. Some(VerifyResult::changed()) } /// `Some` if the memo's value and `changed_at` time is still valid in this revision. /// Does only a shallow O(1) check, doesn't walk the dependencies. /// /// In general, a provisional memo (from cycle iteration) does not verify. Since we don't /// eagerly finalize all provisional memos in cycle iteration, we have to lazily check here /// (via `validate_provisional`) whether a may-be-provisional memo should actually be verified /// final, because its cycle heads are all now final. #[inline] pub(super) fn shallow_verify_memo( &self, zalsa: &Zalsa, database_key_index: DatabaseKeyIndex, memo: &Memo<'_, C>, ) -> ShallowUpdate { crate::tracing::debug!( "{database_key_index:?}: shallow_verify_memo(memo = {memo:#?})", memo = memo.tracing_debug() ); let verified_at = memo.verified_at.load(); let revision_now = zalsa.current_revision(); if verified_at == revision_now { // Already verified. return ShallowUpdate::Verified; } let last_changed = zalsa.last_changed_revision(memo.revisions.durability); crate::tracing::trace!( "{database_key_index:?}: check_durability({database_key_index:#?}, last_changed={:?} <= verified_at={:?}) = {:?}", last_changed, verified_at, last_changed <= verified_at, ); if last_changed <= verified_at { // No input of the suitable durability has changed since last verified. ShallowUpdate::HigherDurability } else { ShallowUpdate::No } } #[inline] pub(super) fn update_shallow( &self, zalsa: &Zalsa, database_key_index: DatabaseKeyIndex, memo: &Memo<'_, C>, update: ShallowUpdate, ) { if let ShallowUpdate::HigherDurability = update { memo.mark_as_verified(zalsa, database_key_index); memo.mark_outputs_as_verified(zalsa, database_key_index); } } /// Validates this memo if it is a provisional memo. Returns true for: /// * non provisional memos /// * provisional memos that have been successfully marked as verified final, that is, its /// cycle heads have all been finalized. /// * provisional memos that have been created in the same revision and iteration and are part of the same cycle. #[inline] pub(super) fn validate_may_be_provisional( &self, zalsa: &Zalsa, zalsa_local: &ZalsaLocal, database_key_index: DatabaseKeyIndex, memo: &Memo<'_, C>, ) -> bool { if !memo.may_be_provisional() { return true; } let cycle_heads = memo.cycle_heads(); if cycle_heads.is_empty() { return true; } crate::tracing::trace!( "{database_key_index:?}: validate_may_be_provisional(memo = {memo:#?})", memo = memo.tracing_debug() ); let verified_at = memo.verified_at.load(); validate_provisional( zalsa, database_key_index, &memo.revisions, verified_at, cycle_heads, ) || validate_same_iteration( zalsa, zalsa_local, database_key_index, verified_at, cycle_heads, ) } /// VerifyResult::Unchanged if the memo's value and `changed_at` time is up-to-date in the /// current revision. When this returns Unchanged with no cycle heads, it also updates the /// memo's `verified_at` field if needed to make future calls cheaper. /// /// Takes an [`ActiveQueryGuard`] argument because this function recursively /// walks dependencies of `old_memo` and may even execute them to see if their /// outputs have changed. pub(super) fn deep_verify_memo( &self, db: &C::DbView, zalsa: &Zalsa, old_memo: &Memo<'_, C>, database_key_index: DatabaseKeyIndex, ) -> VerifyResult { match old_memo.revisions.origin.as_ref() { QueryOriginRef::Derived(edges) => { crate::tracing::debug!( "{database_key_index:?}: deep_verify_memo(old_memo = {old_memo:#?})", old_memo = old_memo.tracing_debug() ); let is_provisional = old_memo.may_be_provisional(); // If the value is from the same revision but is still provisional, consider it changed // because we're now in a new iteration. if is_provisional { return VerifyResult::changed(); } // If the old memo participate in a cycle, but the query doesn't have cycle handling, // always return changed. The reasoning here is: // // * cycle heads flatten their dependecies. Therefore, no query with cycle handling // participating in the same cycle should ever call `maybe_changed_after` on any other query. // (we don't get here). // * the query can't be reached from any other query without cycle handling because, // executing it would immediately panic because of the cycle. // * The only other place where we can reach this code is from `fetch`, this is when // the outer cycle is being re-executed. Given that the cycle re-executes, this // query must always be considered changed. // // For queries with cycle handling, verify the flattened // dependencies of the cycle head instead. if C::CYCLE_STRATEGY == CycleRecoveryStrategy::Panic && old_memo.was_cycle_participant() { return VerifyResult::changed(); } let verified_at = old_memo.verified_at.load(); let result = deep_verify_edges( db.into(), zalsa, &old_memo.revisions, verified_at, edges, database_key_index, ); if result.is_unchanged() { old_memo.mark_as_verified(zalsa, database_key_index); } result } QueryOriginRef::Assigned(_) => { // If the value was assigned by another query, // and that query were up-to-date, // then we would have updated the `verified_at` field already. // So the fact that we are here means that it was not specified // during this revision or is otherwise stale. // // Example of how this can happen: // // Conditionally specified queries // where the value is specified // in rev 1 but not in rev 2. VerifyResult::changed() } QueryOriginRef::DerivedUntracked(_) => { // Untracked inputs? Have to assume that it changed. VerifyResult::changed() } } } } fn maybe_changed_after_cold_cycle( zalsa_local: &ZalsaLocal, database_key_index: DatabaseKeyIndex, cycle_recovery_strategy: CycleRecoveryStrategy, ) -> VerifyResult { match cycle_recovery_strategy { // SAFETY: We do not access the query stack reentrantly. CycleRecoveryStrategy::Panic => unsafe { zalsa_local.with_query_stack_unchecked(|stack| { panic!( "dependency graph cycle when validating {database_key_index:#?}, \ set cycle_fn/cycle_initial to fixpoint iterate.\n\ Query stack:\n{stack:#?}", ); }) }, // We flatten the dependencies of queries with cycle handling that participate in a query. // Verifying those queries should never result in a cycle because all function dependencies were removed. // That means, if we hit this path, then some query introduced a new cycle that didn't exist // in the previous revision. We have to consider this query changed so that we ultimately // insert the fixpoint initial value in `fetch_cold_cycle`. CycleRecoveryStrategy::FallbackImmediate | CycleRecoveryStrategy::Fixpoint => { crate::tracing::debug!( "hit cycle at {database_key_index:?} in `maybe_changed_after`, returning changed", ); VerifyResult::changed() } } } fn deep_verify_edges( db: crate::database::RawDatabase, zalsa: &Zalsa, #[allow(unused)] old_revisions: &QueryRevisions, old_verified_at: Revision, edges: &[QueryEdge], database_key_index: DatabaseKeyIndex, ) -> VerifyResult { #[cfg(feature = "accumulator")] let mut inputs = InputAccumulatedValues::Empty; // Fully tracked inputs? Iterate over the inputs and check them, one by one. // // NB: It's important here that we are iterating the inputs in the order that // they executed. It's possible that if the value of some input I0 is no longer // valid, then some later input I1 might never have executed at all, so verifying // it is still up to date is meaningless. for &edge in edges { match edge.kind() { QueryEdgeKind::Input(dependency_index) => { let input_result = dependency_index.maybe_changed_after(db, zalsa, old_verified_at); match input_result { VerifyResult::Changed => { return VerifyResult::changed(); } #[cfg(feature = "accumulator")] VerifyResult::Unchanged { accumulated } => { inputs |= accumulated; } #[cfg(not(feature = "accumulator"))] VerifyResult::Unchanged { .. } => {} } } QueryEdgeKind::Output(dependency_index) => { // Subtle: Mark outputs as validated now, even though we may // later find an input that requires us to re-execute the function. // Even if it re-execute, the function will wind up writing the same value, // since all prior inputs were green. It's important to do this during // this loop, because it's possible that one of our input queries will // re-execute and may read one of our earlier outputs // (e.g., in a scenario where we do something like // `e = Entity::new(..); query(e);` and `query` reads a field of `e`). // // NB. Accumulators are also outputs, but the above logic doesn't // quite apply to them. Since multiple values are pushed, the first value // may be unchanged, but later values could be different. // In that case, however, the data accumulated // by this function cannot be read until this function is marked green, // so even if we mark them as valid here, the function will re-execute // and overwrite the contents. dependency_index.mark_validated_output(zalsa, database_key_index); } } } let result = VerifyResult::unchanged_with_accumulated( #[cfg(feature = "accumulator")] inputs, ); // This value is only read once the memo is verified. It's therefore safe // to write a non-final value here. #[cfg(feature = "accumulator")] old_revisions.accumulated_inputs.store(inputs); result } /// Check if this memo's cycle heads have all been finalized. If so, mark it verified final and /// return true, if not return false. fn validate_provisional( zalsa: &Zalsa, database_key_index: DatabaseKeyIndex, memo_revisions: &QueryRevisions, memo_verified_at: Revision, cycle_heads: &CycleHeads, ) -> bool { crate::tracing::trace!("{database_key_index:?}: validate_provisional({database_key_index:?})",); // Test if our cycle heads (with the same revision) are now finalized. for cycle_head in cycle_heads { let Some(provisional_status) = zalsa .lookup_ingredient(cycle_head.database_key_index.ingredient_index()) .provisional_status(zalsa, cycle_head.database_key_index.key_index()) else { return false; }; match provisional_status { ProvisionalStatus::Provisional { .. } => return false, ProvisionalStatus::Final { iteration, verified_at, .. } => { // Only consider the cycle head if it is from the same revision as the memo if verified_at != memo_verified_at { return false; } // It's important to also account for the iteration for the case where: // thread 1: `b` -> `a` (but only in the first iteration) // -> `c` -> `b` // thread 2: `a` -> `b` // // If we don't account for the iteration, then `a` (from iteration 0) will be finalized // because its cycle head `b` is now finalized, but `b` never pulled `a` in the last iteration. if iteration != cycle_head.iteration_count.load() { return false; } } } } // Relaxed is sufficient here because there are no other writes we need to ensure have // happened before marking this memo as verified-final. memo_revisions.verified_final.store(true, Ordering::Relaxed); true } /// If this is a provisional memo, validate that it was cached in the same iteration of the /// same cycle(s) that we are still executing. If so, it is valid for reuse. This avoids /// runaway re-execution of the same queries within a fixpoint iteration. fn validate_same_iteration( zalsa: &Zalsa, zalsa_local: &ZalsaLocal, memo_database_key_index: DatabaseKeyIndex, memo_verified_at: Revision, cycle_heads: &CycleHeads, ) -> bool { crate::tracing::trace!("validate_same_iteration({memo_database_key_index:?})",); // This is an optimization to avoid unnecessary re-execution within the same revision. // Don't apply it when verifying memos from past revisions. We want them to re-execute // to verify their cycle heads and all participating queries. if memo_verified_at != zalsa.current_revision() { return false; } // Always return `false` for cycle initial values "unless" they are running in the same thread. if cycle_heads .iter_not_eq(memo_database_key_index) .next() .is_none() { // SAFETY: We do not access the query stack reentrantly. let on_stack = unsafe { zalsa_local.with_query_stack_unchecked(|stack| { stack .iter() .rev() .any(|query| query.database_key_index == memo_database_key_index) }) }; return on_stack; } let cycle_heads_iter = TryClaimCycleHeadsIter::new(zalsa, cycle_heads); for cycle_head in cycle_heads_iter { match cycle_head { TryClaimHeadsResult::Cycle { head_iteration_count, memo_iteration_count: current_iteration_count, verified_at: head_verified_at, } => { if head_verified_at != memo_verified_at { return false; } if head_iteration_count != current_iteration_count { return false; } } _ => { return false; } } } true } #[derive(Copy, Clone, Eq, PartialEq)] pub(super) enum ShallowUpdate { /// The memo is from this revision and has already been verified Verified, /// The revision for the memo's durability hasn't changed. It can be marked as verified /// in this revision. HigherDurability, /// The memo requires a deep verification. No, } impl ShallowUpdate { pub(super) fn yes(&self) -> bool { matches!( self, ShallowUpdate::Verified | ShallowUpdate::HigherDurability ) } } salsa-0.26.2/src/function/memo.rs000064400000000000000000000445561046102023000147530ustar 00000000000000use std::any::Any; use std::fmt::{Debug, Formatter}; use std::mem::transmute; use std::ptr::NonNull; use crate::cycle::{ CycleHeads, CycleHeadsIterator, IterationCount, ProvisionalStatus, empty_cycle_heads, }; use crate::function::{Configuration, IngredientImpl}; use crate::ingredient::WaitForResult; use crate::key::DatabaseKeyIndex; use crate::revision::AtomicRevision; use crate::sync::atomic::Ordering; use crate::table::memo::MemoTableWithTypesMut; use crate::zalsa::{MemoIngredientIndex, Zalsa}; use crate::zalsa_local::{QueryOriginRef, QueryRevisions}; use crate::{Event, EventKind, Id, Revision}; impl IngredientImpl { /// Inserts the memo for the given key; (atomically) overwrites and returns any previously existing memo pub(super) fn insert_memo_into_table_for<'db>( &self, zalsa: &'db Zalsa, id: Id, memo: NonNull>, memo_ingredient_index: MemoIngredientIndex, ) -> Option>> { // SAFETY: The table stores 'static memos (to support `Any`), the memos are in fact valid // for `'db` though as we delay their dropping to the end of a revision. let static_memo = unsafe { transmute::>, NonNull>>(memo) }; let old_static_memo = zalsa .memo_table_for::>(id) .insert(memo_ingredient_index, static_memo)?; // SAFETY: The table stores 'static memos (to support `Any`), the memos are in fact valid // for `'db` though as we delay their dropping to the end of a revision. Some(unsafe { transmute::>, NonNull>>(old_static_memo) }) } /// Loads the current memo for `key_index`. This does not hold any sort of /// lock on the `memo_map` once it returns, so this memo could immediately /// become outdated if other threads store into the `memo_map`. pub(super) fn get_memo_from_table_for<'db>( &self, zalsa: &'db Zalsa, id: Id, memo_ingredient_index: MemoIngredientIndex, ) -> Option<&'db Memo<'db, C>> { let static_memo = zalsa .memo_table_for::>(id) .get(memo_ingredient_index)?; // SAFETY: The table stores 'static memos (to support `Any`), the memos are in fact valid // for `'db` though as we delay their dropping to the end of a revision. Some(unsafe { transmute::<&Memo<'static, C>, &'db Memo<'db, C>>(static_memo.as_ref()) }) } /// Evicts the existing memo for the given key, replacing it /// with an equivalent memo that has no value. If the memo is untracked /// or has values assigned as output of another query, this has no effect. pub(super) fn evict_value_from_memo_for( table: MemoTableWithTypesMut<'_>, memo_ingredient_index: MemoIngredientIndex, ) { let map = |memo: &mut Memo<'static, C>| { match memo.revisions.origin.as_ref() { QueryOriginRef::Assigned(_) | QueryOriginRef::DerivedUntracked(_) => { // Careful: Cannot evict memos whose values were // assigned as output of another query // or those with untracked inputs // as their values cannot be reconstructed. } QueryOriginRef::Derived(_) => { // Set the memo value to `None`. memo.value = None; } } }; table.map_memo(memo_ingredient_index, map) } } #[derive(Debug)] pub struct Memo<'db, C: Configuration> { /// The result of the query, if we decide to memoize it. pub(super) value: Option>, /// Last revision when this memo was verified; this begins /// as the current revision. pub(super) verified_at: AtomicRevision, /// Revision information pub(super) revisions: QueryRevisions, } impl<'db, C: Configuration> Memo<'db, C> { pub(super) fn new( value: Option>, revision_now: Revision, revisions: QueryRevisions, ) -> Self { debug_assert!( !revisions.verified_final.load(Ordering::Relaxed) || revisions.cycle_heads().is_empty(), "Memo must be finalized if it has no cycle heads" ); Memo { value, verified_at: AtomicRevision::from(revision_now), revisions, } } /// Returns `true` if this memo should be serialized. pub(super) fn should_serialize(&self) -> bool { // TODO: Serialization is a good opportunity to prune old query results based on // the `verified_at` revision. self.value.is_some() && !self.may_be_provisional() } /// True if this may be a provisional cycle-iteration result. #[inline] pub(super) fn may_be_provisional(&self) -> bool { // Relaxed is OK here, because `verified_final` is only ever mutated in one direction (from // `false` to `true`), and changing it to `true` on memos with cycle heads where it was // ever `false` is purely an optimization; if we read an out-of-date `false`, it just means // we might go validate it again unnecessarily. !self.revisions.verified_final.load(Ordering::Relaxed) } /// Cycle heads that should be propagated to dependent queries. #[inline(always)] pub(super) fn cycle_heads(&self) -> &CycleHeads { if self.may_be_provisional() { self.revisions.cycle_heads() } else { empty_cycle_heads() } } /// Returns `true` if this memo was part of a cycle in it's last iteration. #[inline(always)] pub(super) fn was_cycle_participant(&self) -> bool { !self.revisions.cycle_heads().is_empty() } /// Mark memo as having been verified in the `revision_now`, which should /// be the current revision. /// The caller is responsible to update the memo's `accumulated` state if their accumulated /// values have changed since. #[inline] pub(super) fn mark_as_verified(&self, zalsa: &Zalsa, database_key_index: DatabaseKeyIndex) { zalsa.event(&|| { Event::new(EventKind::DidValidateMemoizedValue { database_key: database_key_index, }) }); self.verified_at.store(zalsa.current_revision()); } pub(super) fn mark_outputs_as_verified( &self, zalsa: &Zalsa, database_key_index: DatabaseKeyIndex, ) { for output in self.revisions.origin.as_ref().outputs() { output.mark_validated_output(zalsa, database_key_index); } } pub(super) fn tracing_debug(&self) -> impl std::fmt::Debug + use<'_, 'db, C> { struct TracingDebug<'memo, 'db, C: Configuration> { memo: &'memo Memo<'db, C>, } impl std::fmt::Debug for TracingDebug<'_, '_, C> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("Memo") .field( "value", if self.memo.value.is_some() { &"Some()" } else { &"None" }, ) .field("verified_at", &self.memo.verified_at) .field("revisions", &self.memo.revisions) .finish() } } TracingDebug { memo: self } } } impl crate::table::memo::Memo for Memo<'static, C> where C::Output<'static>: Send + Sync + Any, { fn remove_outputs(&self, zalsa: &Zalsa, executor: DatabaseKeyIndex) { for stale_output in self.revisions.origin.as_ref().outputs() { stale_output.remove_stale_output(zalsa, executor); } for (identity, id) in self.revisions.tracked_struct_ids() { let key = DatabaseKeyIndex::new(identity.ingredient_index(), *id); key.remove_stale_output(zalsa, executor); } } #[cfg(feature = "salsa_unstable")] fn memory_usage(&self) -> crate::database::MemoInfo { let size_of = std::mem::size_of::>() + self.revisions.allocation_size(); let heap_size = if let Some(value) = self.value.as_ref() { C::heap_size(value) } else { Some(0) }; crate::database::MemoInfo { debug_name: C::DEBUG_NAME, output: crate::database::SlotInfo { size_of_metadata: size_of - std::mem::size_of::>(), debug_name: std::any::type_name::>(), size_of_fields: std::mem::size_of::>(), heap_size_of_fields: heap_size, memos: Vec::new(), }, } } } #[cfg(feature = "persistence")] mod persistence { use crate::function::Configuration; use crate::function::memo::Memo; use crate::revision::AtomicRevision; use crate::zalsa_local::persistence::MappedQueryRevisions; use crate::zalsa_local::{QueryOrigin, QueryRevisions}; use serde::Deserialize; use serde::ser::SerializeStruct; /// A reference to the fields of a [`Memo`], with its [`QueryRevisions`] transformed. pub(crate) struct MappedMemo<'memo, 'db, C: Configuration> { pub(crate) value: Option<&'memo C::Output<'db>>, pub(crate) verified_at: AtomicRevision, pub(crate) revisions: MappedQueryRevisions<'memo>, } impl<'db, C: Configuration> Memo<'db, C> { pub(crate) fn with_origin(&self, origin: QueryOrigin) -> MappedMemo<'_, 'db, C> { let Memo { ref verified_at, ref value, ref revisions, } = *self; MappedMemo { value: value.as_ref(), verified_at: AtomicRevision::from(verified_at.load()), revisions: revisions.with_origin(origin), } } } impl serde::Serialize for MappedMemo<'_, '_, C> where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { struct SerializeValue<'me, 'db, C: Configuration>(&'me C::Output<'db>); impl serde::Serialize for SerializeValue<'_, '_, C> where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { C::serialize(self.0, serializer) } } let MappedMemo { value, verified_at, revisions, } = self; let value = value.expect( "attempted to serialize memo where `Memo::should_serialize` returned `false`", ); let mut s = serializer.serialize_struct("Memo", 3)?; s.serialize_field("value", &SerializeValue::(value))?; s.serialize_field("verified_at", &verified_at)?; s.serialize_field("revisions", &revisions)?; s.end() } } impl<'de, C> serde::Deserialize<'de> for Memo<'static, C> where C: Configuration, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { #[derive(Deserialize)] #[serde(rename = "Memo")] pub struct DeserializeMemo { #[serde(bound = "C: Configuration")] value: DeserializeValue, verified_at: AtomicRevision, revisions: QueryRevisions, } struct DeserializeValue(C::Output<'static>); impl<'de, C> serde::Deserialize<'de> for DeserializeValue where C: Configuration, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { C::deserialize(deserializer) .map(DeserializeValue) .map_err(serde::de::Error::custom) } } let memo = DeserializeMemo::::deserialize(deserializer)?; Ok(Memo { value: Some(memo.value.0), verified_at: memo.verified_at, revisions: memo.revisions, }) } } } #[derive(Debug)] pub(super) enum TryClaimHeadsResult { /// Claiming the cycle head results in a cycle. Cycle { head_iteration_count: IterationCount, memo_iteration_count: IterationCount, verified_at: Revision, }, /// The cycle head is not finalized, but it can be claimed. Available, /// The cycle head is currently executed on another thread. Running, } /// Iterator to try claiming the transitive cycle heads of a memo. pub(super) struct TryClaimCycleHeadsIter<'a> { zalsa: &'a Zalsa, cycle_heads: CycleHeadsIterator<'a>, } impl<'a> TryClaimCycleHeadsIter<'a> { pub(super) fn new(zalsa: &'a Zalsa, cycle_heads: &'a CycleHeads) -> Self { Self { zalsa, cycle_heads: cycle_heads.iter(), } } } impl Iterator for TryClaimCycleHeadsIter<'_> { type Item = TryClaimHeadsResult; fn next(&mut self) -> Option { let head = self.cycle_heads.next()?; let head_database_key = head.database_key_index; let head_key_index = head_database_key.key_index(); let ingredient = self .zalsa .lookup_ingredient(head_database_key.ingredient_index()); match ingredient.wait_for(self.zalsa, head_key_index) { WaitForResult::Cycle { .. } => { // We hit a cycle blocking on the cycle head; this means this query actively // participates in the cycle and some other query is blocked on this thread. crate::tracing::trace!("Waiting for {head_database_key:?} results in a cycle"); let provisional_status = ingredient .provisional_status(self.zalsa, head_key_index) .expect("cycle head memo to exist"); let (current_iteration_count, verified_at) = match provisional_status { ProvisionalStatus::Provisional { iteration, verified_at, cycle_heads: _, } => (iteration, verified_at), ProvisionalStatus::Final { iteration, verified_at, } => (iteration, verified_at), }; Some(TryClaimHeadsResult::Cycle { memo_iteration_count: current_iteration_count, head_iteration_count: head.iteration_count.load(), verified_at, }) } WaitForResult::Running(running) => { crate::tracing::trace!("Ingredient {head_database_key:?} is running: {running:?}"); Some(TryClaimHeadsResult::Running) } WaitForResult::Available => Some(TryClaimHeadsResult::Available), } } } #[cfg(all(not(feature = "shuttle"), target_pointer_width = "64"))] mod _memory_usage { use crate::cycle::CycleRecoveryStrategy; use crate::ingredient::Location; use crate::plumbing::{self, IngredientIndices, MemoIngredientSingletonIndex, SalsaStructInDb}; use crate::table::memo::MemoTableWithTypes; use crate::zalsa::Zalsa; use crate::{Database, Id, Revision}; use std::any::TypeId; use std::num::NonZeroUsize; // Memo's are stored a lot, make sure their size doesn't randomly increase. const _: [(); std::mem::size_of::>()] = [(); std::mem::size_of::<[usize; 6]>()]; struct DummyStruct; impl SalsaStructInDb for DummyStruct { type MemoIngredientMap = MemoIngredientSingletonIndex; const LEAF_TYPE_IDS: &'static [typeid::ConstTypeId] = &[]; fn lookup_ingredient_index(_: &Zalsa) -> IngredientIndices { unimplemented!() } fn cast(_: Id, _: TypeId) -> Option { unimplemented!() } unsafe fn memo_table(_: &Zalsa, _: Id, _: Revision) -> MemoTableWithTypes<'_> { unimplemented!() } fn entries(_: &Zalsa) -> impl Iterator + '_ { std::iter::empty() } } struct DummyConfiguration; impl super::Configuration for DummyConfiguration { const DEBUG_NAME: &'static str = ""; const LOCATION: Location = Location { file: "", line: 0 }; const PERSIST: bool = false; const CYCLE_STRATEGY: CycleRecoveryStrategy = CycleRecoveryStrategy::Panic; type DbView = dyn Database; type SalsaStruct<'db> = DummyStruct; type Input<'db> = (); type Output<'db> = NonZeroUsize; type Eviction = crate::function::eviction::NoopEviction; fn values_equal<'db>(_: &Self::Output<'db>, _: &Self::Output<'db>) -> bool { unimplemented!() } fn id_to_input(_: &Zalsa, _: Id) -> Self::Input<'_> { unimplemented!() } fn execute<'db>(_: &'db Self::DbView, _: Self::Input<'db>) -> Self::Output<'db> { unimplemented!() } fn cycle_initial<'db>( _: &'db Self::DbView, _: Id, _: Self::Input<'db>, ) -> Self::Output<'db> { unimplemented!() } fn recover_from_cycle<'db>( _: &'db Self::DbView, _: &crate::Cycle, _: &Self::Output<'db>, value: Self::Output<'db>, _: Self::Input<'db>, ) -> Self::Output<'db> { value } fn serialize(_: &Self::Output<'_>, _: S) -> Result where S: plumbing::serde::Serializer, { unimplemented!() } fn deserialize<'de, D>(_: D) -> Result, D::Error> where D: plumbing::serde::Deserializer<'de>, { unimplemented!() } } } salsa-0.26.2/src/function/specify.rs000064400000000000000000000142471046102023000154520ustar 00000000000000#[cfg(feature = "accumulator")] use crate::accumulator::accumulated_map::InputAccumulatedValues; use crate::active_query::CompletedQuery; use crate::function::memo::Memo; use crate::function::{Configuration, IngredientImpl}; use crate::revision::AtomicRevision; use crate::sync::atomic::AtomicBool; use crate::tracked_struct::TrackedStructInDb; use crate::zalsa::{Zalsa, ZalsaDatabase}; use crate::zalsa_local::{QueryOrigin, QueryOriginRef, QueryRevisions, QueryRevisionsExtra}; use crate::{DatabaseKeyIndex, Id}; impl IngredientImpl where C: Configuration, { /// Specify the value for `key` *and* record that we did so. /// Used for explicit calls to `specify`, but not needed for pre-declared tracked struct fields. pub fn specify_and_record<'db>(&'db self, db: &'db C::DbView, key: Id, value: C::Output<'db>) where C::Input<'db>: TrackedStructInDb, { let (zalsa, zalsa_local) = db.zalsas(); let (active_query_key, current_deps) = match zalsa_local.active_query() { Some(v) => v, None => panic!("can only use `specify` inside a tracked function"), }; // `specify` only works if the key is a tracked struct created in the current query. // // The reason is this. We want to ensure that the same result is reached regardless of // the "path" that the user takes through the execution graph. // If you permit values to be specified from other queries, you can have a situation like this: // * Q0 creates the tracked struct T0 // * Q1 specifies the value for F(T0) // * Q2 invokes F(T0) // * Q3 invokes Q1 and then Q2 // * Q4 invokes Q2 and then Q1 // // Now, if We invoke Q3 first, We get one result for Q2, but if We invoke Q4 first, We get a different value. That's no good. let database_key_index = >::database_key_index(zalsa, key); if !zalsa_local.is_tracked_struct_of_active_query(database_key_index) { panic!("can only use `specify` on salsa structs created during the current tracked fn"); } // Subtle: we treat the "input" to a set query as if it were // volatile. // // The idea is this. You have the current query C that // created the entity E, and it is setting the value F(E) of the function F. // When some other query R reads the field F(E), in order to have obtained // the entity E, it has to have executed the query C. // // This will have forced C to either: // // - not create E this time, in which case R shouldn't have it (some kind of leak has occurred) // - assign a value to F(E), in which case `verified_at` will be the current revision and `changed_at` will be updated appropriately // - NOT assign a value to F(E), in which case we need to re-execute the function (which typically panics). // // So, ruling out the case of a leak having occurred, that means that the reader R will either see: // // - a result that is verified in the current revision, because it was set, which will use the set value // - a result that is NOT verified and has untracked inputs, which will re-execute (and likely panic) let revision = zalsa.current_revision(); let mut completed_query = CompletedQuery { revisions: QueryRevisions { changed_at: current_deps.changed_at, durability: current_deps.durability, origin: QueryOrigin::assigned(active_query_key), #[cfg(feature = "accumulator")] accumulated_inputs: Default::default(), verified_final: AtomicBool::new(true), extra: QueryRevisionsExtra::default(), }, stale_tracked_structs: Vec::new(), }; let memo_ingredient_index = self.memo_ingredient_index(zalsa, key); if let Some(old_memo) = self.get_memo_from_table_for(zalsa, key, memo_ingredient_index) { self.backdate_if_appropriate( old_memo, database_key_index, &mut completed_query.revisions, &value, ); self.diff_outputs(zalsa, database_key_index, old_memo, &completed_query); } let memo = Memo { value: Some(value), verified_at: AtomicRevision::from(revision), revisions: completed_query.revisions, }; crate::tracing::debug!( "specify: about to add memo {:#?} for key {:?}", memo.tracing_debug(), key ); self.insert_memo(zalsa, key, memo, memo_ingredient_index); // Record that the current query *specified* a value for this cell. let database_key_index = self.database_key_index(key); zalsa_local.add_output(database_key_index); } /// Invoked when the query `executor` has been validated as having green inputs /// and `key` is a value that was specified by `executor`. /// Marks `key` as valid in the current revision since if `executor` had re-executed, /// it would have specified `key` again. pub(super) fn validate_specified_value( &self, zalsa: &Zalsa, executor: DatabaseKeyIndex, key: Id, ) { let memo_ingredient_index = self.memo_ingredient_index(zalsa, key); let memo = match self.get_memo_from_table_for(zalsa, key, memo_ingredient_index) { Some(m) => m, None => return, }; // If we are marking this as validated, it must be a value that was // assigned by `executor`. match memo.revisions.origin.as_ref() { QueryOriginRef::Assigned(by_query) => assert_eq!(by_query, executor), _ => panic!( "expected a query assigned by `{:?}`, not `{:?}`", executor, memo.revisions.origin, ), } let database_key_index = self.database_key_index(key); memo.mark_as_verified(zalsa, database_key_index); #[cfg(feature = "accumulator")] memo.revisions .accumulated_inputs .store(InputAccumulatedValues::Empty); } } salsa-0.26.2/src/function/sync.rs000064400000000000000000000467411046102023000147700ustar 00000000000000use rustc_hash::FxHashMap; use std::collections::hash_map::OccupiedEntry; use crate::key::DatabaseKeyIndex; use crate::plumbing::ZalsaLocal; use crate::runtime::{ BlockOnTransferredOwner, BlockResult, BlockTransferredResult, Running, WaitResult, }; use crate::sync::Mutex; use crate::sync::thread::{self}; use crate::tracing; use crate::zalsa::Zalsa; use crate::{Id, IngredientIndex}; pub(crate) type SyncGuard<'me> = crate::sync::MutexGuard<'me, FxHashMap>; /// Tracks the keys that are currently being processed; used to coordinate between /// worker threads. pub(crate) struct SyncTable { syncs: Mutex>, ingredient: IngredientIndex, } pub(crate) enum ClaimResult<'a, Guard = ClaimGuard<'a>> { /// Successfully claimed the query. Claimed(Guard), /// Can't claim the query because it is running on an other thread. Running(Running<'a>), /// Claiming the query results in a cycle. Cycle { /// `true` if this is a cycle with an inner query. For example, if `a` transferred its ownership to /// `b`. If the thread claiming `b` tries to claim `a`, then this results in a cycle except when calling /// [`SyncTable::try_claim`] with [`Reentrant::Allow`]. inner: bool, }, } pub(crate) struct SyncState { /// The thread id that currently owns this query (actively executing it or iterating it as part of a larger cycle). id: SyncOwner, /// Set to true if any other queries are blocked, /// waiting for this query to complete. anyone_waiting: bool, /// Whether any other query has transferred its lock ownership to this query. /// This is only an optimization so that the expensive unblocking of transferred queries /// can be skipped if `false`. This field might be `true` in cases where queries *were* transferred /// to this query, but have since then been transferred to another query (in a later iteration). is_transfer_target: bool, /// Whether this query has been claimed by the query that currently owns it. /// /// If `a` has been transferred to `b` and the stack for t1 is `b -> a`, then `a` can be claimed /// and `claimed_twice` is set to `true`. However, t2 won't be able to claim `a` because /// it doesn't own `b`. claimed_twice: bool, } impl SyncTable { pub(crate) fn new(ingredient: IngredientIndex) -> Self { Self { syncs: Default::default(), ingredient, } } /// Claims the given key index, or blocks if it is running on another thread. pub(crate) fn try_claim<'me>( &'me self, zalsa: &'me Zalsa, zalsa_local: &'me ZalsaLocal, key_index: Id, reentrant: Reentrancy, ) -> ClaimResult<'me> { let mut write = self.syncs.lock(); match write.entry(key_index) { std::collections::hash_map::Entry::Occupied(occupied_entry) => { let id = match occupied_entry.get().id { SyncOwner::Thread(id) => id, SyncOwner::Transferred => { return match self.try_claim_transferred( zalsa, zalsa_local, occupied_entry, reentrant, ) { Ok(claimed) => claimed, Err(other_thread) => match other_thread.block(write) { BlockResult::Cycle => ClaimResult::Cycle { inner: false }, BlockResult::Running(running) => ClaimResult::Running(running), }, }; } }; let SyncState { anyone_waiting, .. } = occupied_entry.into_mut(); // NB: `Ordering::Relaxed` is sufficient here, // as there are no loads that are "gated" on this // value. Everything that is written is also protected // by a lock that must be acquired. The role of this // boolean is to decide *whether* to acquire the lock, // not to gate future atomic reads. *anyone_waiting = true; match zalsa.runtime().block( DatabaseKeyIndex::new(self.ingredient, key_index), id, write, ) { BlockResult::Running(blocked_on) => ClaimResult::Running(blocked_on), BlockResult::Cycle => ClaimResult::Cycle { inner: false }, } } std::collections::hash_map::Entry::Vacant(vacant_entry) => { vacant_entry.insert(SyncState { id: SyncOwner::Thread(thread::current().id()), anyone_waiting: false, is_transfer_target: false, claimed_twice: false, }); ClaimResult::Claimed(ClaimGuard { key_index, zalsa, zalsa_local, sync_table: self, mode: ReleaseMode::Default, }) } } } /// Claims the given key index, or blocks if it is running on another thread. pub(crate) fn peek_claim<'me>( &'me self, zalsa: &'me Zalsa, key_index: Id, reentrant: Reentrancy, ) -> ClaimResult<'me, ()> { let mut write = self.syncs.lock(); match write.entry(key_index) { std::collections::hash_map::Entry::Occupied(occupied_entry) => { let id = match occupied_entry.get().id { SyncOwner::Thread(id) => id, SyncOwner::Transferred => { return match self.peek_claim_transferred(zalsa, occupied_entry, reentrant) { Ok(claimed) => claimed, Err(other_thread) => match other_thread.block(write) { BlockResult::Cycle => ClaimResult::Cycle { inner: false }, BlockResult::Running(running) => ClaimResult::Running(running), }, }; } }; let SyncState { anyone_waiting, .. } = occupied_entry.into_mut(); // NB: `Ordering::Relaxed` is sufficient here, // as there are no loads that are "gated" on this // value. Everything that is written is also protected // by a lock that must be acquired. The role of this // boolean is to decide *whether* to acquire the lock, // not to gate future atomic reads. *anyone_waiting = true; match zalsa.runtime().block( DatabaseKeyIndex::new(self.ingredient, key_index), id, write, ) { BlockResult::Running(blocked_on) => ClaimResult::Running(blocked_on), BlockResult::Cycle => ClaimResult::Cycle { inner: false }, } } std::collections::hash_map::Entry::Vacant(_) => ClaimResult::Claimed(()), } } #[cold] #[inline(never)] fn try_claim_transferred<'me>( &'me self, zalsa: &'me Zalsa, zalsa_local: &'me ZalsaLocal, mut entry: OccupiedEntry, reentrant: Reentrancy, ) -> Result, Box>> { let key_index = *entry.key(); let database_key_index = DatabaseKeyIndex::new(self.ingredient, key_index); let thread_id = thread::current().id(); match zalsa .runtime() .block_transferred(database_key_index, thread_id) { BlockTransferredResult::ImTheOwner if reentrant.is_allow() => { let SyncState { id, claimed_twice, .. } = entry.into_mut(); debug_assert!(!*claimed_twice); *id = SyncOwner::Thread(thread_id); *claimed_twice = true; Ok(ClaimResult::Claimed(ClaimGuard { key_index, zalsa, zalsa_local, sync_table: self, mode: ReleaseMode::SelfOnly, })) } BlockTransferredResult::ImTheOwner => Ok(ClaimResult::Cycle { inner: true }), BlockTransferredResult::OwnedBy(other_thread) => { entry.get_mut().anyone_waiting = true; Err(other_thread) } BlockTransferredResult::Released => { entry.insert(SyncState { id: SyncOwner::Thread(thread_id), anyone_waiting: false, is_transfer_target: false, claimed_twice: false, }); Ok(ClaimResult::Claimed(ClaimGuard { key_index, zalsa, zalsa_local, sync_table: self, mode: ReleaseMode::Default, })) } } } #[cold] #[inline(never)] fn peek_claim_transferred<'me>( &'me self, zalsa: &'me Zalsa, mut entry: OccupiedEntry, reentrant: Reentrancy, ) -> Result, Box>> { let key_index = *entry.key(); let database_key_index = DatabaseKeyIndex::new(self.ingredient, key_index); let thread_id = thread::current().id(); match zalsa .runtime() .block_transferred(database_key_index, thread_id) { BlockTransferredResult::ImTheOwner if reentrant.is_allow() => { Ok(ClaimResult::Claimed(())) } BlockTransferredResult::ImTheOwner => Ok(ClaimResult::Cycle { inner: true }), BlockTransferredResult::OwnedBy(other_thread) => { entry.get_mut().anyone_waiting = true; Err(other_thread) } BlockTransferredResult::Released => Ok(ClaimResult::Claimed(())), } } /// Marks `key_index` as a transfer target. /// /// Returns the `SyncOwnerId` of the thread that currently owns this query. /// /// Note: The result of this method will immediately become stale unless the thread owning `key_index` /// is currently blocked on this thread (claiming `key_index` from this thread results in a cycle). pub(super) fn mark_as_transfer_target(&self, key_index: Id) -> Option { let mut syncs = self.syncs.lock(); syncs.get_mut(&key_index).map(|state| { // We set `anyone_waiting` to true because it is used in `ClaimGuard::release` // to exit early if the query doesn't need to release any locks. // However, there are now dependent queries that need to be released, that's why we set `anyone_waiting` to true, // so that `ClaimGuard::release` no longer exits early. state.anyone_waiting = true; state.is_transfer_target = true; state.id }) } } #[derive(Copy, Clone, Debug)] pub enum SyncOwner { /// Query is owned by this thread Thread(thread::ThreadId), /// The query's lock ownership has been transferred to another query. /// E.g. if `a` transfers its ownership to `b`, then only the thread in the critical path /// to complete `b` can claim `a` (in most instances, only the thread owning `b` can claim `a`). /// /// The thread owning `a` is stored in the `DependencyGraph`. /// /// A query can be marked as `Transferred` even if it has since then been released by the owning query. /// In that case, the query is effectively unclaimed and the `Transferred` state is stale. The reason /// for this is that it avoids the need for locking each sync table when releasing the transferred queries. Transferred, } /// Marks an active 'claim' in the synchronization map. The claim is /// released when this value is dropped. #[must_use] pub(crate) struct ClaimGuard<'me> { key_index: Id, zalsa: &'me Zalsa, sync_table: &'me SyncTable, mode: ReleaseMode, zalsa_local: &'me ZalsaLocal, } impl<'me> ClaimGuard<'me> { pub(crate) const fn zalsa(&self) -> &'me Zalsa { self.zalsa } pub(crate) fn zalsa_local(&self) -> &'me ZalsaLocal { self.zalsa_local } pub(crate) const fn database_key_index(&self) -> DatabaseKeyIndex { DatabaseKeyIndex::new(self.sync_table.ingredient, self.key_index) } pub(crate) fn set_release_mode(&mut self, mode: ReleaseMode) { self.mode = mode; } #[cold] #[inline(never)] fn release_panicking(&self) { let mut syncs = self.sync_table.syncs.lock(); let state = syncs.remove(&self.key_index).expect("key claimed twice?"); let result = if self.zalsa_local.should_trigger_local_cancellation() { WaitResult::Cancelled } else { WaitResult::Panicked }; tracing::debug!( "Release claim on {:?} due to {:?}", self.database_key_index(), result ); self.release(state, result); } #[inline(always)] fn release(&self, state: SyncState, wait_result: WaitResult) { let SyncState { anyone_waiting, is_transfer_target, claimed_twice, .. } = state; if !anyone_waiting { return; } let runtime = self.zalsa.runtime(); let database_key_index = self.database_key_index(); if claimed_twice { runtime.undo_transfer_lock(database_key_index); } runtime.unblock_queries_blocked_on(database_key_index, wait_result); if is_transfer_target { runtime.unblock_transferred_queries_owned_by(database_key_index, wait_result); } } #[cold] #[inline(never)] fn release_self(&self) { let mut syncs = self.sync_table.syncs.lock(); let std::collections::hash_map::Entry::Occupied(mut state) = syncs.entry(self.key_index) else { panic!("key should only be claimed/released once"); }; if state.get().claimed_twice { state.get_mut().claimed_twice = false; state.get_mut().id = SyncOwner::Transferred; } else { self.release(state.remove(), WaitResult::Completed); } } #[cold] #[inline(never)] pub(crate) fn transfer(&self, new_owner: DatabaseKeyIndex) -> bool { let owner_ingredient = self.zalsa.lookup_ingredient(new_owner.ingredient_index()); // Get the owning thread of `new_owner`. // The thread id is guaranteed to not be stale because `new_owner` must be blocked on `self_key` // or `transfer_lock` will panic (at least in debug builds). let Some(new_owner_thread_id) = owner_ingredient.mark_as_transfer_target(new_owner.key_index()) else { self.release( self.sync_table .syncs .lock() .remove(&self.key_index) .expect("key should only be claimed/released once"), WaitResult::Panicked, ); panic!("new owner to be a locked query") }; let mut syncs = self.sync_table.syncs.lock(); let self_key = self.database_key_index(); tracing::debug!( "Transferring lock ownership of {self_key:?} to {new_owner:?} ({new_owner_thread_id:?})" ); let SyncState { id, claimed_twice, .. } = syncs .get_mut(&self.key_index) .expect("key should only be claimed/released once"); *id = SyncOwner::Transferred; *claimed_twice = false; self.zalsa .runtime() .transfer_lock(self_key, new_owner, new_owner_thread_id, syncs) } /// Drops the claim on the memo. /// /// Returns `true` if the lock was transferred to another query and /// this thread blocked waiting for the new owner's lock to be released. /// In that case, any computed memo need to be refetched because they may have /// changed since `drop` was called. pub(crate) fn drop(mut self) -> bool { let refetch = self.drop_impl(); std::mem::forget(self); refetch } fn drop_impl(&mut self) -> bool { match self.mode { ReleaseMode::Default => { let mut syncs = self.sync_table.syncs.lock(); let state = syncs .remove(&self.key_index) .expect("key should only be claimed/released once"); self.release(state, WaitResult::Completed); false } ReleaseMode::SelfOnly => { self.release_self(); false } ReleaseMode::TransferTo(new_owner) => self.transfer(new_owner), } } } impl Drop for ClaimGuard<'_> { fn drop(&mut self) { if thread::panicking() { self.release_panicking(); return; } self.drop_impl(); } } impl std::fmt::Debug for SyncTable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SyncTable").finish() } } /// Controls how the lock is released when the `ClaimGuard` is dropped. #[derive(Copy, Clone, Debug, Default)] pub(crate) enum ReleaseMode { /// The default release mode. /// /// Releases the query for which this claim guard holds the lock and any queries that have /// transferred ownership to this query. #[default] Default, /// Only releases the lock for this query. Any query that has transferred ownership to this query /// will remain locked. /// /// If this thread panics, the query will be released as normal (default mode). SelfOnly, /// Transfers the ownership of the lock to the specified query. /// /// The query will remain locked and only the thread owning the transfer target will be resumed. /// /// The transfer target must be a query that's blocked on this query to guarantee that the transfer target doesn't complete /// before the transfer is finished (which would leave this query locked forever). /// /// If this thread panics, the query will be released as normal (default mode). TransferTo(DatabaseKeyIndex), } impl std::fmt::Debug for ClaimGuard<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ClaimGuard") .field("key_index", &self.key_index) .field("mode", &self.mode) .finish_non_exhaustive() } } /// Controls whether this thread can claim a query that transferred its ownership to a query /// this thread currently holds the lock for. /// /// For example: if query `a` transferred its ownership to query `b`, and this thread holds /// the lock for `b`, then this thread can also claim `a` — but only when using [`Self::Allow`]. #[derive(Copy, Clone, PartialEq, Eq)] pub(crate) enum Reentrancy { /// Allow `try_claim` to reclaim a query's that transferred its ownership to a query /// hold by this thread. Allow, /// Only allow claiming queries that haven't been claimed by any thread. Deny, } impl Reentrancy { const fn is_allow(self) -> bool { matches!(self, Reentrancy::Allow) } } salsa-0.26.2/src/function.rs000064400000000000000000000733561046102023000140160ustar 00000000000000pub(crate) use maybe_changed_after::VerifyResult; pub(crate) use sync::{ClaimGuard, ClaimResult, Reentrancy, SyncGuard, SyncOwner, SyncTable}; use std::any::Any; use std::fmt; use std::ptr::NonNull; use std::sync::OnceLock; use std::sync::atomic::Ordering; use crate::cycle::{CycleRecoveryStrategy, IterationCount, ProvisionalStatus}; use crate::database::RawDatabase; use crate::function::delete::DeletedEntries; use crate::hash::{FxHashSet, FxIndexSet}; use crate::ingredient::{Ingredient, WaitForResult}; use crate::key::DatabaseKeyIndex; use crate::plumbing::{self, MemoIngredientMap}; use crate::salsa_struct::SalsaStructInDb; use crate::sync::Arc; use crate::table::Table; use crate::table::memo::MemoTableTypes; use crate::views::DatabaseDownCaster; use crate::zalsa::{IngredientIndex, JarKind, MemoIngredientIndex, Zalsa}; use crate::zalsa_local::{QueryEdge, QueryOriginRef}; use crate::{Cycle, Id, Revision}; #[cfg(feature = "accumulator")] mod accumulated; mod backdate; mod delete; mod diff_outputs; mod eviction; mod execute; mod fetch; mod inputs; mod maybe_changed_after; mod memo; mod specify; mod sync; pub use eviction::{EvictionPolicy, HasCapacity, Lru, NoopEviction}; pub type Memo = memo::Memo<'static, C>; pub trait Configuration: Any { const DEBUG_NAME: &'static str; const LOCATION: crate::ingredient::Location; const PERSIST: bool; /// The database that this function is associated with. type DbView: ?Sized + crate::Database; /// The "salsa struct type" that this function is associated with. /// This can be just `salsa::Id` for functions that intern their arguments /// and are not clearly associated with any one salsa struct. type SalsaStruct<'db>: SalsaStructInDb; /// The input to the function type Input<'db>: Send + Sync; /// The value computed by the function. type Output<'db>: Send + Sync; /// The eviction policy for this function's memoized values. type Eviction: EvictionPolicy; /// Determines whether this function can recover from being a participant in a cycle /// (and, if so, how). const CYCLE_STRATEGY: CycleRecoveryStrategy; /// Invokes after a new result `new_value` has been computed for which an older memoized value /// existed `old_value`, or in fixpoint iteration. Returns true if the new value is equal to /// the older one. /// /// This invokes user code in form of the `Eq` impl. fn values_equal<'db>(old_value: &Self::Output<'db>, new_value: &Self::Output<'db>) -> bool; /// Convert from the id used internally to the value that execute is expecting. /// This is a no-op if the input to the function is a salsa struct. fn id_to_input(zalsa: &Zalsa, key: Id) -> Self::Input<'_>; /// Returns the size of any heap allocations in the output value, in bytes. fn heap_size(_value: &Self::Output<'_>) -> Option { None } /// Invoked when we need to compute the value for the given key, either because we've never /// computed it before or because the old one relied on inputs that have changed. /// /// This invokes the function the user wrote. fn execute<'db>(db: &'db Self::DbView, input: Self::Input<'db>) -> Self::Output<'db>; /// Get the cycle recovery initial value. fn cycle_initial<'db>( db: &'db Self::DbView, id: Id, input: Self::Input<'db>, ) -> Self::Output<'db>; /// Decide what value to use for this cycle iteration. Takes ownership of the new value /// and returns an owned value to use. /// /// The function is called for every iteration of the cycle head, regardless of whether the cycle /// has converged (the values are equal). /// /// # Id /// /// The id can be used to uniquely identify the query instance. This can be helpful /// if the cycle function has to re-identify a value it returned previously. /// /// # Values /// /// The `last_provisional_value` is the value from the previous iteration of this cycle /// and `value` is the new value that was computed in the current iteration. /// /// # Iteration count /// /// The `iteration` parameter isn't guaranteed to start from zero or to be contiguous: /// /// * **Initial value**: `iteration` may be non-zero on the first call for a given query if that /// query becomes the outermost cycle head after a nested cycle complete a few iterations. In this case, /// `iteration` continues from the nested cycle's iteration count rather than resetting to zero. /// * **Non-contiguous values**: The iteration count can be non-contigious for cycle heads /// that are only conditionally part of a cycle. /// /// # Return value /// /// The function should return the value to use for this iteration. This can be the `value` /// that was computed, or a different value (e.g., a fallback value). This cycle will continue /// iterating until the returned value equals the previous iteration's value. fn recover_from_cycle<'db>( db: &'db Self::DbView, cycle: &Cycle, last_provisional_value: &Self::Output<'db>, value: Self::Output<'db>, input: Self::Input<'db>, ) -> Self::Output<'db>; /// Serialize the output type using `serde`. /// /// Panics if the value is not persistable, i.e. `Configuration::PERSIST` is `false`. fn serialize(value: &Self::Output<'_>, serializer: S) -> Result where S: plumbing::serde::Serializer; /// Deserialize the output type using `serde`. /// /// Panics if the value is not persistable, i.e. `Configuration::PERSIST` is `false`. fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: plumbing::serde::Deserializer<'de>; } /// Function ingredients are the "workhorse" of salsa. /// /// They are used for tracked functions, for the "value" fields of tracked structs, and for the fields of input structs. /// The function ingredient is fairly complex and so its code is spread across multiple modules, typically one per method. /// The main entry points are: /// /// * the `fetch` method, which is invoked when the function is called by the user's code; /// it will return a memoized value if one exists, or execute the function otherwise. /// * the `specify` method, which can only be used when the key is an entity created by the active query. /// It sets the value of the function imperatively, so that when later fetches occur, they'll return this value. /// * the `store` method, which can only be invoked with an `&mut` reference, and is to set input fields. pub struct IngredientImpl { /// The ingredient index we were assigned in the database. /// Used to construct `DatabaseKeyIndex` values. index: IngredientIndex, /// The index for the memo/sync tables /// /// This may be a [`crate::memo_ingredient_indices::MemoIngredientSingletonIndex`] or a /// [`crate::memo_ingredient_indices::MemoIngredientIndices`], depending on whether the /// tracked function's struct is a plain salsa struct or an enum `#[derive(Supertype)]`. memo_ingredient_indices: as SalsaStructInDb>::MemoIngredientMap, /// Eviction policy - type determined by Configuration. /// Used to find memos to throw out when we have too many memoized values. eviction: C::Eviction, /// An downcaster to `C::DbView`. /// /// # Safety /// /// The supplied database must be be the same as the database used to construct the [`Views`] /// instances that this downcaster was derived from. view_caster: OnceLock>, sync_table: SyncTable, /// When `fetch` and friends executes, they return a reference to the /// value stored in the memo that is extended to live as long as the `&self` /// reference we start with. This means that whenever we remove something /// from `memo_map` with an `&self` reference, there *could* be references to its /// internals still in use. Therefore we push the memo into this queue and /// only *actually* free up memory when a new revision starts (which means /// we have an `&mut` reference to self). /// /// You might think that we could do this only if the memo was verified in the /// current revision: you would be right, but we are being defensive, because /// we don't know that we can trust the database to give us the same runtime /// everytime and so forth. deleted_entries: DeletedEntries, } impl IngredientImpl where C: Configuration, { pub fn new( index: IngredientIndex, memo_ingredient_indices: as SalsaStructInDb>::MemoIngredientMap, eviction_capacity: usize, ) -> Self { Self { index, memo_ingredient_indices, eviction: C::Eviction::new(eviction_capacity), deleted_entries: Default::default(), view_caster: OnceLock::new(), sync_table: SyncTable::new(index), } } /// Set the view-caster for this tracked function ingredient, if it has /// not already been initialized. #[inline] pub fn get_or_init( &self, view_caster: impl FnOnce() -> DatabaseDownCaster, ) -> &Self { // Note that we must set this lazily as we don't have access to the database // type when ingredients are registered into the `Zalsa`. self.view_caster.get_or_init(view_caster); self } #[inline] pub fn database_key_index(&self, key: Id) -> DatabaseKeyIndex { DatabaseKeyIndex::new(self.index, key) } /// Set eviction capacity. Only available when eviction policy supports it. pub fn set_capacity(&mut self, capacity: usize) where C::Eviction: HasCapacity, { self.eviction.set_capacity(capacity); } /// Returns a reference to the memo value that lives as long as self. /// This is UNSAFE: the caller is responsible for ensuring that the /// memo will not be released so long as the `&self` is valid. /// This is done by (a) ensuring the memo is present in the memo-map /// when this function is called and (b) ensuring that any entries /// removed from the memo-map are added to `deleted_entries`, which is /// only cleared with `&mut self`. unsafe fn extend_memo_lifetime<'this>( &'this self, memo: &memo::Memo<'this, C>, ) -> &'this memo::Memo<'this, C> { // SAFETY: the caller must guarantee that the memo will not be released before `&self` unsafe { std::mem::transmute(memo) } } fn insert_memo<'db>( &'db self, zalsa: &'db Zalsa, id: Id, mut memo: memo::Memo<'db, C>, memo_ingredient_index: MemoIngredientIndex, ) -> &'db memo::Memo<'db, C> { if let Some(tracked_struct_ids) = memo.revisions.tracked_struct_ids_mut() { tracked_struct_ids.shrink_to_fit(); } // We convert to a `NonNull` here as soon as possible because we are going to alias // into the `Box`, which is a `noalias` type. // FIXME: Use `Box::into_non_null` once stable let memo = NonNull::from(Box::leak(Box::new(memo))); if let Some(old_value) = self.insert_memo_into_table_for(zalsa, id, memo, memo_ingredient_index) { // In case there is a reference to the old memo out there, we have to store it // in the deleted entries. This will get cleared when a new revision starts. // // SAFETY: Once the revision starts, there will be no outstanding borrows to the // memo contents, and so it will be safe to free. unsafe { self.deleted_entries.push(old_value) }; } // SAFETY: memo has been inserted into the table unsafe { self.extend_memo_lifetime(memo.as_ref()) } } #[inline] fn memo_ingredient_index(&self, zalsa: &Zalsa, id: Id) -> MemoIngredientIndex { self.memo_ingredient_indices.get_zalsa_id(zalsa, id) } fn view_caster(&self) -> &DatabaseDownCaster { self.view_caster .get() .expect("tracked function ingredients cannot be accessed before calling `init`") } } impl Ingredient for IngredientImpl where C: Configuration, { fn location(&self) -> &'static crate::ingredient::Location { &C::LOCATION } fn ingredient_index(&self) -> IngredientIndex { self.index } unsafe fn maybe_changed_after( &self, _zalsa: &Zalsa, db: RawDatabase<'_>, input: Id, revision: Revision, ) -> VerifyResult { // SAFETY: The `db` belongs to the ingredient as per caller invariant let db = unsafe { self.view_caster().downcast_unchecked(db) }; self.maybe_changed_after(db, input, revision) } fn collect_minimum_serialized_edges( &self, zalsa: &Zalsa, edge: QueryEdge, serialized_edges: &mut FxIndexSet, visited_edges: &mut FxHashSet, ) { let input = edge.key().key_index(); let Some(memo) = self.get_memo_from_table_for(zalsa, input, self.memo_ingredient_index(zalsa, input)) else { return; }; let origin = memo.revisions.origin.as_ref(); visited_edges.insert(edge); // Collect the minimum dependency tree. for edge in origin.edges() { // Avoid forming cycles. if visited_edges.contains(edge) { continue; } // Avoid flattening edges that we're going to serialize directly. if serialized_edges.contains(edge) { continue; } let dependency = zalsa.lookup_ingredient(edge.key().ingredient_index()); dependency.collect_minimum_serialized_edges( zalsa, *edge, serialized_edges, visited_edges, ) } } /// Returns `final` if the memo has the `verified_final` flag set. /// /// Otherwise, the value is still provisional. For both final and provisional, it also /// returns the iteration in which this memo was created (always 0 except for cycle heads). fn provisional_status<'db>( &self, zalsa: &'db Zalsa, input: Id, ) -> Option> { let memo = self.get_memo_from_table_for(zalsa, input, self.memo_ingredient_index(zalsa, input))?; let iteration = memo.revisions.iteration(); let verified_final = memo.revisions.verified_final.load(Ordering::Relaxed); Some(if verified_final { ProvisionalStatus::Final { iteration, verified_at: memo.verified_at.load(), } } else { ProvisionalStatus::Provisional { iteration, verified_at: memo.verified_at.load(), cycle_heads: memo.cycle_heads(), } }) } fn set_cycle_iteration_count(&self, zalsa: &Zalsa, input: Id, iteration_count: IterationCount) { let Some(memo) = self.get_memo_from_table_for(zalsa, input, self.memo_ingredient_index(zalsa, input)) else { return; }; memo.revisions .set_iteration_count(Self::database_key_index(self, input), iteration_count); } fn finalize_cycle_head(&self, zalsa: &Zalsa, input: Id) { let Some(memo) = self.get_memo_from_table_for(zalsa, input, self.memo_ingredient_index(zalsa, input)) else { return; }; memo.revisions.verified_final.store(true, Ordering::Release); } fn flatten_cycle_head_dependencies( &self, zalsa: &Zalsa, id: Id, flattened_input_outputs: &mut FxIndexSet, seen: &mut FxHashSet, ) { let memo_index = self.memo_ingredient_index(zalsa, id); let Some(memo) = self.get_memo_from_table_for(zalsa, id, memo_index) else { return; }; let database_key_index = self.database_key_index(id); // Only flatten dependencies of provisional queries, because only those // contain cyclic dependencies. if !memo.may_be_provisional() { flattened_input_outputs.insert(QueryEdge::input(database_key_index)); return; } // There's nothing to do if we've visited this query before. if !seen.insert(database_key_index) { return; } let inputs = memo.revisions.origin.as_ref().inputs(); match C::CYCLE_STRATEGY { // For queries with cycle handling, simply extend the input/outputs, because // they already flattened their own dependencies when completing the query. CycleRecoveryStrategy::FallbackImmediate | CycleRecoveryStrategy::Fixpoint => { flattened_input_outputs.extend(inputs.map(QueryEdge::input)); } // For regular queries, recurse CycleRecoveryStrategy::Panic => { for input in inputs { let ingredient = zalsa.lookup_ingredient(input.ingredient_index()); ingredient.flatten_cycle_head_dependencies( zalsa, input.key_index(), flattened_input_outputs, seen, ); } } } } fn cycle_converged(&self, zalsa: &Zalsa, input: Id) -> bool { let Some(memo) = self.get_memo_from_table_for(zalsa, input, self.memo_ingredient_index(zalsa, input)) else { return true; }; memo.revisions.cycle_converged() } fn mark_as_transfer_target(&self, key_index: Id) -> Option { self.sync_table.mark_as_transfer_target(key_index) } /// Attempts to claim `key_index` without blocking. /// /// * [`WaitForResult::Running`] if the `key_index` is running on another thread. It's up to the caller to block on the other thread /// to wait until the result becomes available. /// * [`WaitForResult::Available`] It is (or at least was) possible to claim the `key_index` /// * [`WaitResult::Cycle`] Claiming the `key_index` results in a cycle because it's on the current's thread query stack or /// running on another thread that is blocked on this thread. fn wait_for<'me>(&'me self, zalsa: &'me Zalsa, key_index: Id) -> WaitForResult<'me> { match self .sync_table .peek_claim(zalsa, key_index, Reentrancy::Deny) { ClaimResult::Running(blocked_on) => WaitForResult::Running(blocked_on), ClaimResult::Cycle { inner } => WaitForResult::Cycle { inner }, ClaimResult::Claimed(()) => WaitForResult::Available, } } fn origin<'db>(&self, zalsa: &'db Zalsa, key: Id) -> Option> { self.origin(zalsa, key) } fn mark_validated_output( &self, zalsa: &Zalsa, executor: DatabaseKeyIndex, output_key: crate::Id, ) { self.validate_specified_value(zalsa, executor, output_key); } fn remove_stale_output( &self, _zalsa: &Zalsa, _executor: DatabaseKeyIndex, _stale_output_key: crate::Id, ) { // This function is invoked when a query Q specifies the value for `stale_output_key` in rev 1, // but not in rev 2. We don't do anything in this case, we just leave the (now stale) memo. // Since its `verified_at` field has not changed, it will be considered dirty if it is invoked. } fn requires_reset_for_new_revision(&self) -> bool { true } fn reset_for_new_revision(&mut self, table: &mut Table) { self.eviction.for_each_evicted(|evict| { let ingredient_index = table.ingredient_index(evict); Self::evict_value_from_memo_for( table.memos_mut(evict), self.memo_ingredient_indices.get(ingredient_index), ) }); self.deleted_entries.clear(); } fn debug_name(&self) -> &'static str { C::DEBUG_NAME } fn jar_kind(&self) -> JarKind { JarKind::TrackedFn } fn memo_table_types(&self) -> &Arc { unreachable!("function does not allocate pages") } fn memo_table_types_mut(&mut self) -> &mut Arc { unreachable!("function does not allocate pages") } #[cfg(feature = "accumulator")] unsafe fn accumulated<'db>( &'db self, db: RawDatabase<'db>, key_index: Id, ) -> ( Option<&'db crate::accumulator::accumulated_map::AccumulatedMap>, crate::accumulator::accumulated_map::InputAccumulatedValues, ) { // SAFETY: The `db` belongs to the ingredient as per caller invariant let db = unsafe { self.view_caster().downcast_unchecked(db) }; self.accumulated_map(db, key_index) } fn is_persistable(&self) -> bool { C::PERSIST } fn should_serialize(&self, zalsa: &Zalsa) -> bool { if !C::PERSIST { return false; } // We only serialize the query if there are any memos associated with it. for entry in as SalsaStructInDb>::entries(zalsa) { let memo_ingredient_index = self.memo_ingredient_indices.get(entry.ingredient_index()); let memo = self.get_memo_from_table_for(zalsa, entry.key_index(), memo_ingredient_index); if memo.is_some_and(|memo| memo.should_serialize()) { return true; } } false } #[cfg(feature = "persistence")] unsafe fn serialize<'db>( &'db self, zalsa: &'db Zalsa, f: &mut dyn FnMut(&dyn erased_serde::Serialize), ) { f(&persistence::SerializeIngredient { zalsa, ingredient: self, }) } #[cfg(feature = "persistence")] fn deserialize( &mut self, zalsa: &mut Zalsa, deserializer: &mut dyn erased_serde::Deserializer, ) -> Result<(), erased_serde::Error> { let deserialize = persistence::DeserializeIngredient { zalsa, ingredient: self, }; serde::de::DeserializeSeed::deserialize(deserialize, deserializer) } } impl std::fmt::Debug for IngredientImpl where C: Configuration, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct(std::any::type_name::()) .field("index", &self.index) .finish() } } #[cfg(feature = "persistence")] mod persistence { use super::{Configuration, IngredientImpl, Memo}; use crate::hash::{FxHashSet, FxIndexSet}; use crate::plumbing::{MemoIngredientMap, SalsaStructInDb}; use crate::zalsa::Zalsa; use crate::zalsa_local::{QueryEdge, QueryOrigin, QueryOriginRef}; use crate::{Id, IngredientIndex}; use serde::de; use serde::ser::SerializeMap; use std::ptr::NonNull; pub struct SerializeIngredient<'db, C> where C: Configuration, { pub zalsa: &'db Zalsa, pub ingredient: &'db IngredientImpl, } impl serde::Serialize for SerializeIngredient<'_, C> where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let Self { ingredient, zalsa } = self; let count = as SalsaStructInDb>::entries(zalsa) .filter(|entry| { let memo_ingredient_index = ingredient .memo_ingredient_indices .get(entry.ingredient_index()); let memo = ingredient.get_memo_from_table_for( zalsa, entry.key_index(), memo_ingredient_index, ); memo.is_some_and(|memo| memo.should_serialize()) }) .count(); let mut map = serializer.serialize_map(Some(count))?; let mut visited_edges = FxHashSet::default(); let mut flattened_edges = FxIndexSet::default(); for entry in as SalsaStructInDb>::entries(zalsa) { let memo_ingredient_index = ingredient .memo_ingredient_indices .get(entry.ingredient_index()); let memo = ingredient.get_memo_from_table_for( zalsa, entry.key_index(), memo_ingredient_index, ); if let Some(memo) = memo.filter(|memo| memo.should_serialize()) { // Flatten the dependencies of this query down to the base inputs. let flattened_origin = match memo.revisions.origin.as_ref() { QueryOriginRef::Derived(edges) => { collect_minimum_serialized_edges( zalsa, edges, &mut visited_edges, &mut flattened_edges, ); QueryOrigin::derived(flattened_edges.drain(..).collect()) } QueryOriginRef::DerivedUntracked(edges) => { collect_minimum_serialized_edges( zalsa, edges, &mut visited_edges, &mut flattened_edges, ); QueryOrigin::derived_untracked(flattened_edges.drain(..).collect()) } QueryOriginRef::Assigned(key) => { let dependency = zalsa.lookup_ingredient(key.ingredient_index()); assert!( dependency.is_persistable(), "specified query `{}` must be persistable", dependency.debug_name() ); QueryOrigin::assigned(key) } }; let memo = memo.with_origin(flattened_origin); // TODO: Group structs by ingredient index into a nested map. let key = format!( "{}:{}", entry.ingredient_index().as_u32(), entry.key_index().as_bits() ); map.serialize_entry(&key, &memo)?; visited_edges.clear(); } } map.end() } } // Flatten the dependency edges before serialization. fn collect_minimum_serialized_edges( zalsa: &Zalsa, edges: &[QueryEdge], visited_edges: &mut FxHashSet, flattened_edges: &mut FxIndexSet, ) { for &edge in edges { let dependency = zalsa.lookup_ingredient(edge.key().ingredient_index()); if dependency.is_persistable() { // If the dependency will be serialized, we can serialize the edge directly. flattened_edges.insert(edge); } else { // Otherwise, serialize the minimum edges necessary to cover the dependency. dependency.collect_minimum_serialized_edges( zalsa, edge, flattened_edges, visited_edges, ); } } } pub struct DeserializeIngredient<'db, C> where C: Configuration, { pub zalsa: &'db Zalsa, pub ingredient: &'db mut IngredientImpl, } impl<'de, C> de::DeserializeSeed<'de> for DeserializeIngredient<'_, C> where C: Configuration, { type Value = (); fn deserialize(self, deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_map(self) } } impl<'de, C> de::Visitor<'de> for DeserializeIngredient<'_, C> where C: Configuration, { type Value = (); fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("a map") } fn visit_map(self, mut access: M) -> Result where M: de::MapAccess<'de>, { let DeserializeIngredient { zalsa, ingredient } = self; while let Some((key, memo)) = access.next_entry::<&str, Memo>()? { let (ingredient_index, id) = key .split_once(':') .ok_or_else(|| de::Error::custom("invalid database key"))?; let ingredient_index = IngredientIndex::new( ingredient_index.parse::().map_err(de::Error::custom)?, ); let id = Id::from_bits(id.parse::().map_err(de::Error::custom)?); let memo_ingredient_index = ingredient.memo_ingredient_indices.get(ingredient_index); // SAFETY: We provide the current revision. let memo_table = unsafe { zalsa.table().dyn_memos(id, zalsa.current_revision()) }; memo_table.insert( memo_ingredient_index, // FIXME: Use `Box::into_non_null` once stable. NonNull::from(Box::leak(Box::new(memo))), ); } Ok(()) } } } salsa-0.26.2/src/hash.rs000064400000000000000000000015741046102023000131050ustar 00000000000000use std::hash::{BuildHasher, Hash, Hasher}; pub(crate) type FxHasher = std::hash::BuildHasherDefault; pub(crate) type FxIndexSet = indexmap::IndexSet; pub(crate) type FxLinkedHashSet = hashlink::LinkedHashSet; pub(crate) type FxHashSet = std::collections::HashSet; pub(crate) fn hash(t: &T) -> u64 { FxHasher::default().hash_one(t) } // `TypeId` is a 128-bit hash internally, and it's `Hash` implementation // writes the lower 64-bits. Hashing it again would be unnecessary. #[derive(Default)] pub(crate) struct TypeIdHasher(u64); impl Hasher for TypeIdHasher { fn write(&mut self, _: &[u8]) { unreachable!("`TypeId` calls `write_u64`"); } #[inline] fn write_u64(&mut self, id: u64) { self.0 = id; } #[inline] fn finish(&self) -> u64 { self.0 } } salsa-0.26.2/src/id.rs000064400000000000000000000132761046102023000125600ustar 00000000000000use std::fmt::Debug; use std::hash::{Hash, Hasher}; use std::num::NonZeroU32; use crate::zalsa::Zalsa; /// The `Id` of a salsa struct in the database [`Table`](`crate::table::Table`). /// /// The high-order bits of an `Id` store a 32-bit generation counter, while /// the low-order bits pack a [`PageIndex`](`crate::table::PageIndex`) and /// [`SlotIndex`](`crate::table::SlotIndex`) within the page. /// /// The low-order bits of `Id` are a `u32` ranging from `0..Id::MAX_U32`. /// The maximum range is smaller than a standard `u32` to leave /// room for niches; currently there is only one niche, so that /// `Option` is the same size as an `Id`. /// /// As an end-user of `Salsa` you will generally not use `Id` directly, /// it is wrapped in new types. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Id { index: NonZeroU32, generation: u32, } impl Id { pub const MAX_U32: u32 = u32::MAX - 0xFF; pub const MAX_USIZE: usize = Self::MAX_U32 as usize; /// Create a `salsa::Id` from a u32 value, without a generation. This /// value should be less than [`Self::MAX_U32`]. /// /// In general, you should not need to create salsa ids yourself, /// but it can be useful if you are using the type as a general /// purpose "identifier" internally. /// /// # Safety /// /// The supplied value must be less than [`Self::MAX_U32`]. #[doc(hidden)] #[track_caller] #[inline] pub const unsafe fn from_index(index: u32) -> Self { debug_assert!(index < Self::MAX_U32); Id { // SAFETY: Caller obligation. index: unsafe { NonZeroU32::new_unchecked(index + 1) }, generation: 0, } } /// Create a `salsa::Id` from a `u64` value. /// /// This should only be used to recreate an `Id` together with `Id::as_u64`. /// /// # Safety /// /// The data bits of the supplied value must represent a valid `Id` returned /// by `Id::as_u64`. #[doc(hidden)] #[track_caller] #[inline] pub const unsafe fn from_bits_unchecked(bits: u64) -> Self { // SAFETY: Caller obligation. let index = unsafe { NonZeroU32::new_unchecked(bits as u32) }; let generation = (bits >> 32) as u32; Id { index, generation } } /// Create a `salsa::Id` from a `u64` value. /// /// This should only be used to recreate an `Id` together with `Id::as_u64`, /// and may panic if the `Id` is invalid. #[inline] #[doc(hidden)] pub const fn from_bits(bits: u64) -> Self { let index = NonZeroU32::new(bits as u32).expect("attempted to create invalid `Id`"); let generation = (bits >> 32) as u32; Id { index, generation } } /// Return a `u64` representation of this `Id`. #[inline] pub fn as_bits(self) -> u64 { u64::from(self.index.get()) | (u64::from(self.generation) << 32) } /// Returns a new `Id` with same index, but the generation incremented by one. /// /// Returns `None` if the generation would overflow, i.e. the current generation /// is `u32::MAX`. #[inline] pub fn next_generation(self) -> Option { self.generation() .checked_add(1) .map(|generation| self.with_generation(generation)) } /// Mark the `Id` with a generation. /// /// This `Id` will refer to the same page and slot in the database, /// but will differ from other identifiers of the slot based on the /// provided generation. #[inline] pub const fn with_generation(self, generation: u32) -> Id { Id { index: self.index, generation, } } /// Return the index portion of this `Id`. #[inline] pub const fn index(self) -> u32 { self.index.get() - 1 } /// Return the generation of this `Id`. #[inline] pub const fn generation(self) -> u32 { self.generation } } impl Hash for Id { fn hash(&self, state: &mut H) { // Convert to a `u64` to avoid dispatching multiple calls to `H::write`. state.write_u64(self.as_bits()); } } #[cfg(feature = "persistence")] impl serde::Serialize for Id { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serde::Serialize::serialize(&self.as_bits(), serializer) } } #[cfg(feature = "persistence")] impl<'de> serde::Deserialize<'de> for Id { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { serde::Deserialize::deserialize(deserializer).map(Self::from_bits) } } impl Debug for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.generation() == 0 { write!(f, "Id({:x})", self.index()) } else { write!(f, "Id({:x}g{:x})", self.index(), self.generation()) } } } /// Internal salsa trait for types that can be represented as a salsa id. pub trait AsId: Sized { fn as_id(&self) -> Id; } /// Internal Salsa trait for types that are just a newtype'd [`Id`][]. pub trait FromId { fn from_id(id: Id) -> Self; } impl AsId for Id { #[inline] fn as_id(&self) -> Id { *self } } impl FromId for Id { #[inline] fn from_id(id: Id) -> Self { id } } /// Enums cannot use [`FromId`] because they need access to the DB to tell the `TypeId` of the variant, /// so they use this trait instead, that has a blanket implementation for `FromId`. pub trait FromIdWithDb { fn from_id(id: Id, zalsa: &Zalsa) -> Self; } impl FromIdWithDb for T { #[inline] fn from_id(id: Id, _zalsa: &Zalsa) -> Self { FromId::from_id(id) } } salsa-0.26.2/src/ingredient.rs000064400000000000000000000332241046102023000143070ustar 00000000000000use std::any::{Any, TypeId}; use std::fmt; use crate::cycle::{IterationCount, ProvisionalStatus}; use crate::database::RawDatabase; use crate::function::VerifyResult; use crate::hash::{FxHashSet, FxIndexSet}; use crate::runtime::Running; use crate::sync::Arc; use crate::table::Table; use crate::table::memo::MemoTableTypes; use crate::zalsa::{IngredientIndex, JarKind, Zalsa, transmute_data_mut_ptr, transmute_data_ptr}; use crate::zalsa_local::{QueryEdge, QueryOriginRef}; use crate::{DatabaseKeyIndex, Id, Revision}; /// A "jar" is a group of ingredients that are added atomically. /// /// Each type implementing jar can be added to the database at most once. pub trait Jar: Any { /// Create the ingredients given the index of the first one. /// /// All subsequent ingredients will be assigned contiguous indices. fn create_ingredients( zalsa: &mut Zalsa, first_index: IngredientIndex, ) -> Vec>; /// This returns the [`TypeId`] of the ID struct, that is, the struct that wraps `salsa::Id` /// and carry the name of the jar. fn id_struct_type_id() -> TypeId; } pub struct Location { pub file: &'static str, pub line: u32, } pub trait Ingredient: Any + fmt::Debug + Send + Sync { fn debug_name(&self) -> &'static str; fn location(&self) -> &'static Location; fn jar_kind(&self) -> JarKind; /// Has the value for `input` in this ingredient changed after `revision`? /// /// # Safety /// /// The passed in database needs to be the same one that the ingredient was created with. unsafe fn maybe_changed_after( &self, zalsa: &Zalsa, db: RawDatabase<'_>, input: Id, revision: Revision, ) -> VerifyResult; /// Collects the minimum edges necessary to serialize a given dependency edge on this ingredient, /// without necessarily serializing the dependency edge itself. /// /// This generally only returns any transitive input dependencies, i.e. the leaves of the dependency /// tree, as most other fine-grained dependencies are covered by the inputs. /// /// Note that any ingredients returned by this function must be persistable. fn collect_minimum_serialized_edges( &self, zalsa: &Zalsa, edge: QueryEdge, serialized_edges: &mut FxIndexSet, visited_edges: &mut FxHashSet, ); /// Returns information about the current provisional status of `input`. /// /// Is it a provisional value or has it been finalized and in which iteration. /// /// Returns `None` if `input` doesn't exist. fn provisional_status<'db>( &self, _zalsa: &'db Zalsa, _input: Id, ) -> Option> { unreachable!( "provisional_status should only be called on cycle heads and only functions can be cycle heads" ); } /// Invoked when the current thread needs to wait for a result for the given `key_index`. /// This call doesn't block the current thread. Instead, it's up to the caller to block /// in case `key_index` is [running](`WaitForResult::Running`) on another thread. /// /// A return value of [`WaitForResult::Available`] indicates that a result is now available. /// A return value of [`WaitForResult::Running`] indicates that `key_index` is currently running /// on an other thread, it's up to caller to block until the result becomes available if desired. /// A return value of [`WaitForResult::Cycle`] means that a cycle was encountered; the waited-on query is either already claimed /// by the current thread, or by a thread waiting on the current thread. fn wait_for<'me>(&'me self, _zalsa: &'me Zalsa, _key_index: Id) -> WaitForResult<'me> { unreachable!( "wait_for should only be called on cycle heads and only functions can be cycle heads" ); } /// Invoked when a query transfers its lock-ownership to `_key_index`. Returns the thread /// owning the lock for `_key_index` or `None` if `_key_index` is not claimed. /// /// Note: The returned `SyncOwnerId` may be outdated as soon as this function returns **unless** /// it's guaranteed that `_key_index` is blocked on the current thread. fn mark_as_transfer_target(&self, _key_index: Id) -> Option { unreachable!("mark_as_transfer_target should only be called on functions"); } /// Invoked when the value `output_key` should be marked as valid in the current revision. /// This occurs because the value for `executor`, which generated it, was marked as valid /// in the current revision. fn mark_validated_output(&self, zalsa: &Zalsa, executor: DatabaseKeyIndex, output_key: Id) { let _ = (zalsa, executor, output_key); unreachable!("only tracked struct and function ingredients can have validatable outputs") } /// Invoked when the value `stale_output` was output by `executor` in a previous /// revision, but was NOT output in the current revision. /// /// This hook is used to clear out the stale value so others cannot read it. fn remove_stale_output(&self, zalsa: &Zalsa, executor: DatabaseKeyIndex, stale_output_key: Id) { let _ = (zalsa, executor, stale_output_key); unreachable!("only tracked struct ingredients can have stale outputs") } /// Returns the [`IngredientIndex`] of this ingredient. fn ingredient_index(&self) -> IngredientIndex; /// Returns true if `reset_for_new_revision` should be called when new revisions start. /// Invoked once when ingredient is added and not after that. fn requires_reset_for_new_revision(&self) -> bool { false } /// Invoked when a new revision is about to start. /// This moment is important because it means that we have an `&mut`-reference to the /// database, and hence any pre-existing `&`-references must have expired. /// Many ingredients, given an `&'db`-reference to the database, /// use unsafe code to return `&'db`-references to internal values. /// The backing memory for those values can only be freed once an `&mut`-reference to the /// database is created. /// /// **Important:** to actually receive resets, the ingredient must set /// [`IngredientRequiresReset::RESET_ON_NEW_REVISION`] to true. fn reset_for_new_revision(&mut self, table: &mut Table) { _ = table; panic!( "Ingredient `{}` set `Ingredient::requires_reset_for_new_revision` to true but does \ not overwrite `Ingredient::reset_for_new_revision`", self.debug_name() ); } fn memo_table_types(&self) -> &Arc; fn memo_table_types_mut(&mut self) -> &mut Arc; fn fmt_index(&self, index: Id, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt_index(self.debug_name(), index, fmt) } // Function ingredient methods /// Tests if the (nested) cycle head `_input` has converged in the most recent iteration. /// /// Returns `false` if the Memo doesn't exist or if called on a non-cycle head. fn cycle_converged(&self, _zalsa: &Zalsa, _input: Id) -> bool { unreachable!( "cycle_converged should only be called on cycle heads and only functions can be cycle heads" ); } /// Updates the iteration count for the (nested) cycle head `_input` to `iteration_count`. /// /// This is a no-op if the memo doesn't exist or if called on a Memo without cycle heads. fn set_cycle_iteration_count( &self, _zalsa: &Zalsa, _input: Id, _iteration_count: IterationCount, ) { unreachable!( "increment_iteration_count should only be called on cycle heads and only functions can be cycle heads" ); } fn finalize_cycle_head(&self, _zalsa: &Zalsa, _input: Id) { unreachable!( "finalize_cycle_head should only be called on cycle heads and only functions can be cycle heads" ); } /// Flattens the dependencies of a query with cycle handling that participates in a cycle. /// /// This query recursively walks the dependency graph of `id` and flattens input dependencies /// on provisional queries into `flattened_input_outputs`. Outputs aren't flattened because /// outputs are owned by the creating query and not the cycle head. /// /// Flattening the dependencies is necessary because the memo's dependency graph only captures /// the dependencies from the last iteration. It also ensures that the dependency graph doesn't /// contain any cycles. /// /// See `cycle_dependency_order_different_entry_queries` and `cycle_left_recursive_query`. fn flatten_cycle_head_dependencies( &self, zalsa: &Zalsa, id: Id, flattened_input_outputs: &mut FxIndexSet, seen: &mut FxHashSet, ); /// What were the inputs (if any) that were used to create the value at `key_index`. fn origin<'db>(&self, zalsa: &'db Zalsa, key_index: Id) -> Option> { let _ = (zalsa, key_index); unreachable!("only function ingredients have origins") } /// What values were accumulated during the creation of the value at `key_index` /// (if any). /// /// # Safety /// /// The passed in database needs to be the same one that the ingredient was created with. #[cfg(feature = "accumulator")] unsafe fn accumulated<'db>( &'db self, db: RawDatabase<'db>, key_index: Id, ) -> ( Option<&'db crate::accumulator::accumulated_map::AccumulatedMap>, crate::accumulator::accumulated_map::InputAccumulatedValues, ) { let _ = (db, key_index); ( None, crate::accumulator::accumulated_map::InputAccumulatedValues::Empty, ) } /// Returns memory usage information about any instances of the ingredient, /// if applicable. #[cfg(feature = "salsa_unstable")] fn memory_usage(&self, _db: &dyn crate::Database) -> Option> { None } /// Whether this ingredient will be persisted with the database. fn is_persistable(&self) -> bool { false } /// Whether there is data to serialize for this ingredient. /// /// If this returns `false`, the ingredient will not be serialized, even if `is_persistable` /// returns `true`. fn should_serialize(&self, _zalsa: &Zalsa) -> bool { false } /// Serialize the ingredient. /// /// This function should invoke the provided callback with a reference to an object implementing [`erased_serde::Serialize`]. /// /// # Safety /// /// While this method takes an immutable reference to the database, it can only be called when a /// the serializer has exclusive access to the database. // See for why this callback signature is necessary, instead // of providing an `erased_serde::Serializer` directly. #[cfg(feature = "persistence")] unsafe fn serialize<'db>( &'db self, _zalsa: &'db Zalsa, _f: &mut dyn FnMut(&dyn erased_serde::Serialize), ) { unimplemented!("called `serialize` on ingredient where `should_serialize` returns `false`") } /// Deserialize the ingredient. #[cfg(feature = "persistence")] fn deserialize( &mut self, _zalsa: &mut Zalsa, _deserializer: &mut dyn erased_serde::Deserializer, ) -> Result<(), erased_serde::Error> { unimplemented!( "called `deserialize` on ingredient where `should_serialize` returns `false`" ) } } impl dyn Ingredient { /// Equivalent to the `downcast` method on `Any`. /// /// Because we do not have dyn-downcasting support, we need this workaround. pub fn assert_type(&self) -> &T { assert_eq!( self.type_id(), TypeId::of::(), "ingredient `{self:?}` is not of type `{}`", std::any::type_name::() ); // SAFETY: We know that the underlying data pointer // refers to a value of type T because of the `TypeId` check above. unsafe { transmute_data_ptr(self) } } /// Equivalent to the `downcast` methods on `Any`. /// /// Because we do not have dyn-downcasting support, we need this workaround. /// /// # Safety /// /// The contained value must be of type `T`. pub unsafe fn assert_type_unchecked(&self) -> &T { debug_assert_eq!( self.type_id(), TypeId::of::(), "ingredient `{self:?}` is not of type `{}`", std::any::type_name::() ); // SAFETY: Guaranteed by caller. unsafe { transmute_data_ptr(self) } } /// Equivalent to the `downcast` method on `Any`. /// /// Because we do not have dyn-downcasting support, we need this workaround. pub fn assert_type_mut(&mut self) -> &mut T { assert_eq!( Any::type_id(self), TypeId::of::(), "ingredient `{self:?}` is not of type `{}`", std::any::type_name::() ); // SAFETY: We know that the underlying data pointer // refers to a value of type T because of the `TypeId` check above. unsafe { transmute_data_mut_ptr(self) } } } /// A helper function to show human readable fmt. pub(crate) fn fmt_index(debug_name: &str, id: Id, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "{debug_name}({id:?})") } #[derive(Debug)] pub enum WaitForResult<'me> { Running(Running<'me>), Available, Cycle { inner: bool }, } salsa-0.26.2/src/ingredient_cache.rs000064400000000000000000000201301046102023000154220ustar 00000000000000pub use imp::IngredientCache; #[cfg(feature = "inventory")] mod imp { use crate::IngredientIndex; use crate::plumbing::Ingredient; use crate::sync::atomic::{self, AtomicU32, Ordering}; use crate::zalsa::Zalsa; use std::marker::PhantomData; /// Caches an ingredient index. /// /// Note that all ingredients are statically registered with `inventory`, so their /// indices should be stable across any databases. pub struct IngredientCache where I: Ingredient, { ingredient_index: AtomicU32, phantom: PhantomData I>, } impl Default for IngredientCache where I: Ingredient, { fn default() -> Self { Self::new() } } impl IngredientCache where I: Ingredient, { const UNINITIALIZED: u32 = u32::MAX; /// Create a new cache pub const fn new() -> Self { Self { ingredient_index: atomic::AtomicU32::new(Self::UNINITIALIZED), phantom: PhantomData, } } /// Get a reference to the ingredient in the database. /// /// If the ingredient index is not already in the cache, it will be loaded and cached. /// /// # Safety /// /// The `IngredientIndex` returned by the closure must reference a valid ingredient of /// type `I` in the provided zalsa database. pub unsafe fn get_or_create<'db>( &self, zalsa: &'db Zalsa, load_index: impl Fn() -> IngredientIndex, ) -> &'db I { let mut ingredient_index = self.ingredient_index.load(Ordering::Acquire); if ingredient_index == Self::UNINITIALIZED { ingredient_index = self.get_or_create_index_slow(load_index).as_u32(); }; // SAFETY: `ingredient_index` is initialized from a valid `IngredientIndex`. let ingredient_index = unsafe { IngredientIndex::new_unchecked(ingredient_index) }; // SAFETY: There are a two cases here: // - The `create_index` closure was called due to the data being uncached. In this // case, the caller guarantees the index is in-bounds and has the correct type. // - The index was cached. While the current database might not be the same database // the ingredient was initially loaded from, the `inventory` feature is enabled, so // ingredient indices are stable across databases. Thus the index is still in-bounds // and has the correct type. unsafe { zalsa .lookup_ingredient_unchecked(ingredient_index) .assert_type_unchecked() } } #[cold] #[inline(never)] fn get_or_create_index_slow( &self, load_index: impl Fn() -> IngredientIndex, ) -> IngredientIndex { let ingredient_index = load_index(); // It doesn't matter if we overwrite any stores, as `create_index` should // always return the same index when the `inventory` feature is enabled. self.ingredient_index .store(ingredient_index.as_u32(), Ordering::Release); ingredient_index } } } #[cfg(not(feature = "inventory"))] mod imp { use crate::IngredientIndex; use crate::nonce::Nonce; use crate::plumbing::Ingredient; use crate::sync::atomic::{AtomicU64, Ordering}; use crate::zalsa::{StorageNonce, Zalsa}; use std::marker::PhantomData; use std::mem; /// Caches an ingredient index. /// /// With manual registration, ingredient indices can vary across databases, /// but we can retain most of the benefit by optimizing for the the case of /// a single database. pub struct IngredientCache where I: Ingredient, { // A packed representation of `Option<(Nonce, IngredientIndex)>`. // // This allows us to replace a lock in favor of an atomic load. This works thanks to `Nonce` // having a niche, which means the entire type can fit into an `AtomicU64`. cached_data: AtomicU64, phantom: PhantomData I>, } impl Default for IngredientCache where I: Ingredient, { fn default() -> Self { Self::new() } } impl IngredientCache where I: Ingredient, { const UNINITIALIZED: u64 = 0; /// Create a new cache pub const fn new() -> Self { Self { cached_data: AtomicU64::new(Self::UNINITIALIZED), phantom: PhantomData, } } /// Get a reference to the ingredient in the database. /// /// If the ingredient is not already in the cache, it will be created. /// /// # Safety /// /// The `IngredientIndex` returned by the closure must reference a valid ingredient of /// type `I` in the provided zalsa database. #[inline(always)] pub unsafe fn get_or_create<'db>( &self, zalsa: &'db Zalsa, create_index: impl Fn() -> IngredientIndex, ) -> &'db I { let index = self.get_or_create_index(zalsa, create_index); // SAFETY: There are a two cases here: // - The `create_index` closure was called due to the data being uncached for the // provided database. In this case, the caller guarantees the index is in-bounds // and has the correct type. // - We verified the index was cached for the same database, by the nonce check. // Thus the initial safety argument still applies. unsafe { zalsa .lookup_ingredient_unchecked(index) .assert_type_unchecked::() } } pub fn get_or_create_index( &self, zalsa: &Zalsa, create_index: impl Fn() -> IngredientIndex, ) -> IngredientIndex { const _: () = assert!( mem::size_of::<(Nonce, IngredientIndex)>() == mem::size_of::() ); let cached_data = self.cached_data.load(Ordering::Acquire); if cached_data == Self::UNINITIALIZED { return self.get_or_create_index_slow(zalsa, create_index); }; // Unpack our `u64` into the nonce and index. // // SAFETY: The lower bits of `cached_data` are initialized from a valid `IngredientIndex`. let index = unsafe { IngredientIndex::new_unchecked(cached_data as u32) }; // SAFETY: We've checked against `UNINITIALIZED` (0) above and so the upper bits must be non-zero. let nonce = crate::nonce::Nonce::::from_u32(unsafe { std::num::NonZeroU32::new_unchecked((cached_data >> u32::BITS) as u32) }); // The data was cached for a different database, we have to ensure the ingredient was // created in ours. if zalsa.nonce() != nonce { return create_index(); } index } #[cold] #[inline(never)] fn get_or_create_index_slow( &self, zalsa: &Zalsa, create_index: impl Fn() -> IngredientIndex, ) -> IngredientIndex { let index = create_index(); let nonce = zalsa.nonce().into_u32().get() as u64; let packed = (nonce << u32::BITS) | (index.as_u32() as u64); debug_assert_ne!(packed, IngredientCache::::UNINITIALIZED); // Discard the result, whether we won over the cache or not doesn't matter. _ = self.cached_data.compare_exchange( IngredientCache::::UNINITIALIZED, packed, Ordering::Release, Ordering::Relaxed, ); // Use our locally computed index regardless of which one was cached. index } } } salsa-0.26.2/src/input/input_field.rs000064400000000000000000000100541046102023000156140ustar 00000000000000use std::fmt; use std::marker::PhantomData; use crate::function::VerifyResult; use crate::hash::{FxHashSet, FxIndexSet}; use crate::ingredient::Ingredient; use crate::input::{Configuration, IngredientImpl, Value}; use crate::sync::Arc; use crate::table::memo::MemoTableTypes; use crate::zalsa::{IngredientIndex, JarKind, Zalsa}; use crate::zalsa_local::QueryEdge; use crate::{DatabaseKeyIndex, Id, Revision}; /// Ingredient used to represent the fields of a `#[salsa::input]`. /// /// These fields can only be mutated by a call to a setter with an `&mut` /// reference to the database, and therefore cannot be mutated during a tracked /// function or in parallel. /// However for on-demand inputs to work the fields must be able to be set via /// a shared reference, so some locking is required. /// Altogether this makes the implementation somewhat simpler than tracked /// structs. pub struct FieldIngredientImpl { index: IngredientIndex, field_index: usize, phantom: PhantomData Value>, } impl FieldIngredientImpl where C: Configuration, { pub(super) fn new(struct_index: IngredientIndex, field_index: usize) -> Self { Self { index: struct_index.successor(field_index), field_index, phantom: PhantomData, } } fn database_key_index(&self, id: Id) -> DatabaseKeyIndex { DatabaseKeyIndex::new(self.index, id) } } impl Ingredient for FieldIngredientImpl where C: Configuration, { fn location(&self) -> &'static crate::ingredient::Location { &C::LOCATION } fn ingredient_index(&self) -> IngredientIndex { self.index } unsafe fn maybe_changed_after( &self, zalsa: &Zalsa, _db: crate::database::RawDatabase<'_>, input: Id, revision: Revision, ) -> VerifyResult { let value = >::data(zalsa, input); VerifyResult::changed_if(value.revisions[self.field_index] > revision) } fn collect_minimum_serialized_edges( &self, _zalsa: &Zalsa, edge: QueryEdge, serialized_edges: &mut FxIndexSet, _visited_edges: &mut FxHashSet, ) { assert!( C::PERSIST, "the inputs of a persistable tracked function must be persistable: `{}` is not persistable", C::DEBUG_NAME ); // Input dependencies are the leaves of the minimum dependency tree. serialized_edges.insert(edge); } fn flatten_cycle_head_dependencies( &self, _zalsa: &Zalsa, id: Id, flattened_input_outputs: &mut FxIndexSet, _seen: &mut FxHashSet, ) { flattened_input_outputs.insert(QueryEdge::input(self.database_key_index(id))); } fn fmt_index(&self, index: crate::Id, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!( fmt, "{input}.{field}({id:?})", input = C::DEBUG_NAME, field = C::FIELD_DEBUG_NAMES[self.field_index], id = index ) } fn debug_name(&self) -> &'static str { C::FIELD_DEBUG_NAMES[self.field_index] } fn jar_kind(&self) -> JarKind { JarKind::Struct } fn memo_table_types(&self) -> &Arc { unreachable!("input fields do not allocate pages") } fn memo_table_types_mut(&mut self) -> &mut Arc { unreachable!("input fields do not allocate pages") } fn is_persistable(&self) -> bool { // Input field dependencies are valid as long as the input is persistable. C::PERSIST } fn should_serialize(&self, _zalsa: &Zalsa) -> bool { // However, they are never serialized directly. false } } impl std::fmt::Debug for FieldIngredientImpl where C: Configuration, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct(std::any::type_name::()) .field("index", &self.index) .finish() } } salsa-0.26.2/src/input/setter.rs000064400000000000000000000033261046102023000146240ustar 00000000000000use std::marker::PhantomData; use crate::input::{Configuration, IngredientImpl}; use crate::{Durability, Runtime}; /// Setter for a field of an input. pub trait Setter: Sized { type FieldTy; fn with_durability(self, durability: Durability) -> Self; fn to(self, value: Self::FieldTy) -> Self::FieldTy; } #[must_use] pub struct SetterImpl<'setter, C: Configuration, S, F> { runtime: &'setter mut Runtime, id: C::Struct, ingredient: &'setter mut IngredientImpl, durability: Option, field_index: usize, setter: S, phantom: PhantomData, } impl<'setter, C, S, F> SetterImpl<'setter, C, S, F> where C: Configuration, S: FnOnce(&mut C::Fields, F) -> F, { pub fn new( runtime: &'setter mut Runtime, id: C::Struct, field_index: usize, ingredient: &'setter mut IngredientImpl, setter: S, ) -> Self { SetterImpl { runtime, id, field_index, ingredient, durability: None, setter, phantom: PhantomData, } } } impl Setter for SetterImpl<'_, C, S, F> where C: Configuration, S: FnOnce(&mut C::Fields, F) -> F, { type FieldTy = F; fn with_durability(mut self, durability: Durability) -> Self { self.durability = Some(durability); self } fn to(self, value: F) -> F { let Self { runtime, id, ingredient, durability, field_index, setter, phantom: _, } = self; ingredient.set_field(runtime, id, field_index, durability, |tuple| { setter(tuple, value) }) } } salsa-0.26.2/src/input/singleton.rs000064400000000000000000000027141046102023000153200ustar 00000000000000use crate::Id; use crate::sync::atomic::{AtomicU64, Ordering}; mod sealed { pub trait Sealed {} } pub trait SingletonChoice: sealed::Sealed + Default { fn with_scope(&self, cb: impl FnOnce() -> Id) -> Id; fn index(&self) -> Option; } pub struct Singleton { index: AtomicU64, } impl sealed::Sealed for Singleton {} impl SingletonChoice for Singleton { fn with_scope(&self, cb: impl FnOnce() -> Id) -> Id { if self.index.load(Ordering::Acquire) != 0 { panic!("singleton struct may not be duplicated"); } let id = cb(); if self .index .compare_exchange(0, id.as_bits(), Ordering::AcqRel, Ordering::Acquire) .is_err() { panic!("singleton struct may not be duplicated"); } id } fn index(&self) -> Option { match self.index.load(Ordering::Acquire) { 0 => None, // SAFETY: Our u64 is derived from an ID and thus safe to convert back. id => Some(unsafe { Id::from_bits_unchecked(id) }), } } } impl Default for Singleton { fn default() -> Self { Self { index: AtomicU64::new(0), } } } #[derive(Default)] pub struct NotSingleton; impl sealed::Sealed for NotSingleton {} impl SingletonChoice for NotSingleton { fn with_scope(&self, cb: impl FnOnce() -> Id) -> Id { cb() } fn index(&self) -> Option { None } } salsa-0.26.2/src/input.rs000064400000000000000000000471501046102023000133210ustar 00000000000000use std::any::{Any, TypeId}; use std::fmt; use std::ops::IndexMut; pub mod input_field; pub mod setter; pub mod singleton; use input_field::FieldIngredientImpl; use crate::function::VerifyResult; use crate::hash::{FxHashSet, FxIndexSet}; use crate::id::{AsId, FromId, FromIdWithDb}; use crate::ingredient::Ingredient; use crate::input::singleton::{Singleton, SingletonChoice}; use crate::key::DatabaseKeyIndex; use crate::plumbing::{self, Jar, ZalsaLocal}; use crate::sync::Arc; use crate::table::memo::{MemoTable, MemoTableTypes}; use crate::table::{Slot, Table}; use crate::zalsa::{IngredientIndex, JarKind, Zalsa}; use crate::zalsa_local::QueryEdge; use crate::{Durability, Id, Revision, Runtime}; pub trait Configuration: Any { const DEBUG_NAME: &'static str; const FIELD_DEBUG_NAMES: &'static [&'static str]; const LOCATION: crate::ingredient::Location; /// Whether this struct should be persisted with the database. const PERSIST: bool; /// The singleton state for this input if any. type Singleton: SingletonChoice + Send + Sync; /// The input struct (which wraps an `Id`) type Struct: FromId + AsId + 'static + Send + Sync; /// A (possibly empty) tuple of the fields for this struct. type Fields: Send + Sync; /// A array of [`Revision`], one per each of the value fields. #[cfg(feature = "persistence")] type Revisions: Send + Sync + fmt::Debug + IndexMut + plumbing::serde::Serialize + for<'de> plumbing::serde::Deserialize<'de>; #[cfg(not(feature = "persistence"))] type Revisions: Send + Sync + fmt::Debug + IndexMut; /// A array of [`Durability`], one per each of the value fields. #[cfg(feature = "persistence")] type Durabilities: Send + Sync + fmt::Debug + IndexMut + plumbing::serde::Serialize + for<'de> plumbing::serde::Deserialize<'de>; #[cfg(not(feature = "persistence"))] type Durabilities: Send + Sync + fmt::Debug + IndexMut; /// Returns the size of any heap allocations in the output value, in bytes. fn heap_size(_value: &Self::Fields) -> Option { None } /// Serialize the fields using `serde`. /// /// Panics if the value is not persistable, i.e. `Configuration::PERSIST` is `false`. fn serialize(value: &Self::Fields, serializer: S) -> Result where S: plumbing::serde::Serializer; /// Deserialize the fields using `serde`. /// /// Panics if the value is not persistable, i.e. `Configuration::PERSIST` is `false`. fn deserialize<'de, D>(deserializer: D) -> Result where D: plumbing::serde::Deserializer<'de>; } pub struct JarImpl { _phantom: std::marker::PhantomData, } impl Default for JarImpl { fn default() -> Self { Self { _phantom: Default::default(), } } } impl Jar for JarImpl { fn create_ingredients( _zalsa: &mut Zalsa, struct_index: crate::zalsa::IngredientIndex, ) -> Vec> { let struct_ingredient: IngredientImpl = IngredientImpl::new(struct_index); std::iter::once(Box::new(struct_ingredient) as _) .chain((0..C::FIELD_DEBUG_NAMES.len()).map(|field_index| { Box::new(>::new(struct_index, field_index)) as _ })) .collect() } fn id_struct_type_id() -> TypeId { TypeId::of::() } } pub struct IngredientImpl { ingredient_index: IngredientIndex, singleton: C::Singleton, memo_table_types: Arc, _phantom: std::marker::PhantomData, } impl IngredientImpl { pub fn new(index: IngredientIndex) -> Self { Self { ingredient_index: index, singleton: Default::default(), memo_table_types: Arc::new(MemoTableTypes::default()), _phantom: std::marker::PhantomData, } } fn data(zalsa: &Zalsa, id: Id) -> &Value { zalsa.table().get(id) } fn data_raw(table: &Table, id: Id) -> *mut Value { table.get_raw(id) } pub fn database_key_index(&self, id: Id) -> DatabaseKeyIndex { DatabaseKeyIndex::new(self.ingredient_index, id) } pub fn new_input( &self, zalsa: &Zalsa, zalsa_local: &ZalsaLocal, fields: C::Fields, revisions: C::Revisions, durabilities: C::Durabilities, ) -> C::Struct { let id = self.singleton.with_scope(|| { zalsa_local .allocate(zalsa, self.ingredient_index, |_| Value:: { fields, revisions, durabilities, // SAFETY: We only ever access the memos of a value that we allocated through // our `MemoTableTypes`. memos: unsafe { MemoTable::new(self.memo_table_types()) }, }) .0 }); FromIdWithDb::from_id(id, zalsa) } /// Change the value of the field `field_index` to a new value. /// /// # Parameters /// /// * `runtime`, the salsa runtiem /// * `id`, id of the input struct /// * `field_index`, index of the field that will be changed /// * `durability`, durability of the new value. If omitted, uses the durability of the previous value. /// * `setter`, function that modifies the fields tuple; should only modify the element for `field_index` pub fn set_field( &mut self, runtime: &mut Runtime, id: C::Struct, field_index: usize, durability: Option, setter: impl FnOnce(&mut C::Fields) -> R, ) -> R { let id: Id = id.as_id(); let data_raw = Self::data_raw(runtime.table(), id); // SAFETY: We hold `&mut` on the runtime so no `&`-references can be active. // Also, we don't access any other data from the table while `r` is active. let data = unsafe { &mut *data_raw }; data.revisions[field_index] = runtime.current_revision(); let field_durability = &mut data.durabilities[field_index]; if *field_durability != Durability::MIN { runtime.report_tracked_write(*field_durability); } *field_durability = durability.unwrap_or(*field_durability); setter(&mut data.fields) } /// Get the singleton input previously created (if any). #[doc(hidden)] pub fn get_singleton_input(&self, zalsa: &Zalsa) -> Option where C: Configuration, { self.singleton .index() .map(|id| FromIdWithDb::from_id(id, zalsa)) } /// Access field of an input. /// Note that this function returns the entire tuple of value fields. /// The caller is responsible for selecting the appropriate element. pub fn field<'db>( &'db self, zalsa: &'db Zalsa, zalsa_local: &'db ZalsaLocal, id: C::Struct, field_index: usize, ) -> &'db C::Fields { let field_ingredient_index = self.ingredient_index.successor(field_index); let id = id.as_id(); let value = Self::data(zalsa, id); let durability = value.durabilities[field_index]; let revision = value.revisions[field_index]; zalsa_local.report_tracked_read_simple( DatabaseKeyIndex::new(field_ingredient_index, id), durability, revision, ); &value.fields } /// Returns all data corresponding to the input struct. pub fn entries<'db>(&'db self, zalsa: &'db Zalsa) -> impl Iterator> { zalsa .table() .slots_of::>() .map(|(id, value)| StructEntry { value, key: self.database_key_index(id), }) } /// Peek at the field values without recording any read dependency. /// Used for debug printouts. pub fn leak_fields<'db>(&'db self, zalsa: &'db Zalsa, id: C::Struct) -> &'db C::Fields { let id = id.as_id(); let value = Self::data(zalsa, id); &value.fields } } /// An input struct entry. pub struct StructEntry<'db, C> where C: Configuration, { value: &'db Value, key: DatabaseKeyIndex, } impl<'db, C> StructEntry<'db, C> where C: Configuration, { /// Returns the `DatabaseKeyIndex` for this entry. pub fn key(&self) -> DatabaseKeyIndex { self.key } /// Returns the input struct. pub fn as_struct(&self) -> C::Struct { FromId::from_id(self.key.key_index()) } #[cfg(feature = "salsa_unstable")] pub fn value(&self) -> &'db Value { self.value } } impl Ingredient for IngredientImpl { fn location(&self) -> &'static crate::ingredient::Location { &C::LOCATION } fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index } unsafe fn maybe_changed_after( &self, _zalsa: &crate::zalsa::Zalsa, _db: crate::database::RawDatabase<'_>, _input: Id, _revision: Revision, ) -> VerifyResult { // Input ingredients are just a counter, they store no data, they are immortal. // Their *fields* are stored in function ingredients elsewhere. panic!("nothing should ever depend on an input struct directly") } fn collect_minimum_serialized_edges( &self, _zalsa: &Zalsa, _edge: QueryEdge, _serialized_edges: &mut FxIndexSet, _visited_edges: &mut FxHashSet, ) { panic!("nothing should ever depend on an input struct directly") } fn flatten_cycle_head_dependencies( &self, _zalsa: &Zalsa, _id: Id, _flattened_input_outputs: &mut FxIndexSet, _seen: &mut FxHashSet, ) { panic!("nothing should ever depend on an input struct directly") } fn debug_name(&self) -> &'static str { C::DEBUG_NAME } fn jar_kind(&self) -> JarKind { JarKind::Struct } fn memo_table_types(&self) -> &Arc { &self.memo_table_types } fn memo_table_types_mut(&mut self) -> &mut Arc { &mut self.memo_table_types } /// Returns memory usage information about any inputs. #[cfg(feature = "salsa_unstable")] fn memory_usage(&self, db: &dyn crate::Database) -> Option> { let memory_usage = self .entries(db.zalsa()) // SAFETY: The memo table belongs to a value that we allocated, so it // has the correct type. .map(|entry| unsafe { entry.value.memory_usage(&self.memo_table_types) }) .collect(); Some(memory_usage) } fn is_persistable(&self) -> bool { C::PERSIST } fn should_serialize(&self, zalsa: &Zalsa) -> bool { C::PERSIST && self.entries(zalsa).next().is_some() } #[cfg(feature = "persistence")] unsafe fn serialize<'db>( &'db self, zalsa: &'db Zalsa, f: &mut dyn FnMut(&dyn erased_serde::Serialize), ) { f(&persistence::SerializeIngredient { zalsa, _ingredient: self, }) } #[cfg(feature = "persistence")] fn deserialize( &mut self, zalsa: &mut Zalsa, deserializer: &mut dyn erased_serde::Deserializer, ) -> Result<(), erased_serde::Error> { let deserialize = persistence::DeserializeIngredient { zalsa, ingredient: self, }; serde::de::DeserializeSeed::deserialize(deserialize, deserializer) } } impl std::fmt::Debug for IngredientImpl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct(std::any::type_name::()) .field("index", &self.ingredient_index) .finish() } } #[derive(Debug)] pub struct Value where C: Configuration, { /// Fields of this input struct. /// /// They can change across revisions, but they do not change within /// a particular revision. fields: C::Fields, /// Revisions of the fields. revisions: C::Revisions, /// Durabilities of the fields. durabilities: C::Durabilities, /// Memos memos: MemoTable, } impl Value where C: Configuration, { /// Fields of this tracked struct. /// /// They can change across revisions, but they do not change within /// a particular revision. #[cfg(feature = "salsa_unstable")] pub fn fields(&self) -> &C::Fields { &self.fields } /// Returns memory usage information about the input. /// /// # Safety /// /// The `MemoTable` must belong to a `Value` of the correct type. #[cfg(feature = "salsa_unstable")] unsafe fn memory_usage(&self, memo_table_types: &MemoTableTypes) -> crate::database::SlotInfo { let heap_size = C::heap_size(&self.fields); // SAFETY: The caller guarantees this is the correct types table. let memos = unsafe { memo_table_types.attach_memos(&self.memos) }; crate::database::SlotInfo { debug_name: C::DEBUG_NAME, size_of_metadata: std::mem::size_of::() - std::mem::size_of::(), size_of_fields: std::mem::size_of::(), heap_size_of_fields: heap_size, memos: memos.memory_usage(), } } } pub trait HasBuilder { type Builder; } // SAFETY: `Value` is our private type branded over the unique configuration `C`. unsafe impl Slot for Value where C: Configuration, { #[inline(always)] unsafe fn memos( this: *const Self, _current_revision: Revision, ) -> *const crate::table::memo::MemoTable { // SAFETY: Caller obligation demands this pointer to be valid. unsafe { &raw const (*this).memos } } #[inline(always)] fn memos_mut(&mut self) -> &mut crate::table::memo::MemoTable { &mut self.memos } } #[cfg(feature = "persistence")] mod persistence { use std::fmt; use serde::ser::{SerializeMap, SerializeStruct}; use serde::{Deserialize, de}; use super::{Configuration, IngredientImpl, Value}; use crate::Id; use crate::input::singleton::SingletonChoice; use crate::plumbing::Ingredient; use crate::table::memo::MemoTable; use crate::zalsa::Zalsa; pub struct SerializeIngredient<'db, C> where C: Configuration, { pub zalsa: &'db Zalsa, pub _ingredient: &'db IngredientImpl, } impl serde::Serialize for SerializeIngredient<'_, C> where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let Self { zalsa, .. } = self; let count = zalsa.table().slots_of::>().count(); let mut map = serializer.serialize_map(Some(count))?; for (id, value) in zalsa.table().slots_of::>() { map.serialize_entry(&id.as_bits(), value)?; } map.end() } } impl serde::Serialize for Value where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut value = serializer.serialize_struct("Value", 3)?; let Value { fields, revisions, durabilities, memos: _, } = self; value.serialize_field("durabilities", &durabilities)?; value.serialize_field("revisions", &revisions)?; value.serialize_field("fields", &SerializeFields::(fields))?; value.end() } } struct SerializeFields<'db, C: Configuration>(&'db C::Fields); impl serde::Serialize for SerializeFields<'_, C> where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { C::serialize(self.0, serializer) } } pub struct DeserializeIngredient<'db, C> where C: Configuration, { pub zalsa: &'db mut Zalsa, pub ingredient: &'db mut IngredientImpl, } impl<'de, C> de::DeserializeSeed<'de> for DeserializeIngredient<'_, C> where C: Configuration, { type Value = (); fn deserialize(self, deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_map(self) } } impl<'de, C> de::Visitor<'de> for DeserializeIngredient<'_, C> where C: Configuration, { type Value = (); fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map") } fn visit_map(self, mut access: M) -> Result where M: de::MapAccess<'de>, { let DeserializeIngredient { zalsa, ingredient } = self; while let Some((id, value)) = access.next_entry::>()? { let id = Id::from_bits(id); let (page_idx, _) = crate::table::split_id(id); let value = Value:: { fields: value.fields.0, revisions: value.revisions, durabilities: value.durabilities, // SAFETY: We only ever access the memos of a value that we allocated through // our `MemoTableTypes`. memos: unsafe { MemoTable::new(ingredient.memo_table_types()) }, }; // Force initialize the relevant page. zalsa.table_mut().force_page::>( page_idx, ingredient.ingredient_index(), ingredient.memo_table_types(), ); // Initialize the slot. // // SAFETY: We have a mutable reference to the database. let allocated_id = ingredient.singleton.with_scope(|| unsafe { zalsa .table() .page(page_idx) .allocate(page_idx, |_| value) .unwrap_or_else(|_| panic!("serialized an invalid `Id`: {id:?}")) .0 }); assert_eq!( allocated_id, id, "values are serialized in allocation order" ); } Ok(()) } } #[derive(Deserialize)] #[serde(rename = "Value")] pub struct DeserializeValue { durabilities: C::Durabilities, revisions: C::Revisions, #[serde(bound = "C: Configuration")] fields: DeserializeFields, } struct DeserializeFields(C::Fields); impl<'de, C> serde::Deserialize<'de> for DeserializeFields where C: Configuration, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { C::deserialize(deserializer) .map(DeserializeFields) .map_err(de::Error::custom) } } } salsa-0.26.2/src/interned.rs000064400000000000000000001577271046102023000140060ustar 00000000000000use std::any::TypeId; use std::borrow::Cow; use std::cell::{Cell, UnsafeCell}; use std::fmt; use std::hash::{BuildHasher, Hash, Hasher}; use std::marker::PhantomData; use std::num::NonZeroUsize; use std::path::{Path, PathBuf}; use crossbeam_utils::CachePadded; use intrusive_collections::{LinkedList, LinkedListLink, UnsafeRef, intrusive_adapter}; use rustc_hash::FxBuildHasher; use crate::durability::Durability; use crate::function::VerifyResult; use crate::hash::{FxHashSet, FxIndexSet}; use crate::id::{AsId, FromId}; use crate::ingredient::Ingredient; use crate::plumbing::{self, Jar, ZalsaLocal}; use crate::revision::AtomicRevision; use crate::sync::{Arc, Mutex, OnceLock}; use crate::table::Slot; use crate::table::memo::{MemoTable, MemoTableTypes, MemoTableWithTypesMut}; use crate::zalsa::{IngredientIndex, JarKind, Zalsa}; use crate::zalsa_local::QueryEdge; use crate::{DatabaseKeyIndex, Event, EventKind, Id, Revision}; /// Trait that defines the key properties of an interned struct. /// /// Implemented by the `#[salsa::interned]` macro when applied to /// a struct. pub trait Configuration: Sized + 'static { const LOCATION: crate::ingredient::Location; const DEBUG_NAME: &'static str; /// Whether this struct should be persisted with the database. const PERSIST: bool; // The minimum number of revisions that must pass before a stale value is garbage collected. #[cfg(test)] const REVISIONS: NonZeroUsize = NonZeroUsize::new(3).unwrap(); #[cfg(not(test))] // More aggressive garbage collection by default when testing. const REVISIONS: NonZeroUsize = NonZeroUsize::new(1).unwrap(); /// The fields of the struct being interned. type Fields<'db>: InternedData; /// The end user struct type Struct<'db>: Copy + FromId + AsId; /// Returns the size of any heap allocations in the output value, in bytes. fn heap_size(_value: &Self::Fields<'_>) -> Option { None } /// Serialize the fields using `serde`. /// /// Panics if the value is not persistable, i.e. `Configuration::PERSIST` is `false`. fn serialize(value: &Self::Fields<'_>, serializer: S) -> Result where S: plumbing::serde::Serializer; /// Deserialize the fields using `serde`. /// /// Panics if the value is not persistable, i.e. `Configuration::PERSIST` is `false`. fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: plumbing::serde::Deserializer<'de>; } pub trait InternedData: Sized + Eq + Hash + Clone + Sync + Send {} impl InternedData for T {} pub struct JarImpl { phantom: PhantomData, } /// The interned ingredient hashes values of type `C::Fields` to produce an `Id`. /// /// It used to store interned structs but also to store the ID fields of a tracked struct. /// Interned values are garbage collected and their memory reused based on an LRU heuristic. pub struct IngredientImpl { /// Index of this ingredient in the database (used to construct database-IDs, etc). ingredient_index: IngredientIndex, /// A hasher for the sharded ID maps. hasher: FxBuildHasher, /// A shift used to determine the shard for a given hash. shift: u32, /// Sharded data that can only be accessed through a lock. shards: Box<[CachePadded>>]>, /// A queue of recent revisions in which values were interned. revision_queue: RevisionQueue, memo_table_types: Arc, _marker: PhantomData C>, } struct IngredientShard { /// Maps from data to the existing interned ID for that data. /// /// This doesn't hold the fields themselves to save memory, instead it points /// to the slot ID. key_map: hashbrown::HashTable, /// An intrusive linked list for LRU. lru: LinkedList>, } impl Default for IngredientShard { fn default() -> Self { Self { lru: LinkedList::default(), key_map: hashbrown::HashTable::new(), } } } // SAFETY: `LinkedListLink` is `!Sync`, however, the linked list is only accessed through the // ingredient lock, and values are only ever linked to a single list on the ingredient. unsafe impl Sync for Value {} intrusive_adapter!(ValueAdapter = UnsafeRef>: Value { link: LinkedListLink } where C: Configuration); /// Struct storing the interned fields. pub struct Value where C: Configuration, { /// The index of the shard containing this value. shard: u16, /// An intrusive linked list for LRU. link: LinkedListLink, /// The interned fields for this value. /// /// These are valid for read-only access as long as the lock is held /// or the value has been validated in the current revision. fields: UnsafeCell>, /// Memos attached to this interned value. /// /// This is valid for read-only access as long as the lock is held /// or the value has been validated in the current revision. memos: UnsafeCell, /// Data that can only be accessed while holding the lock for the /// `key_map` shard containing the value ID. shared: UnsafeCell, } /// Shared value data can only be read through the lock. #[repr(Rust, packed)] // Allow `durability` to be stored in the padding of the outer `Value` struct. #[derive(Clone, Copy)] struct ValueShared { /// The interned ID for this value. /// /// Storing this on the value itself is necessary to identify slots /// from the LRU list, as well as keep track of the generation. /// /// Values that are reused increment the ID generation, as if they had /// allocated a new slot. This eliminates the need for dependency edges /// on queries that *read* from an interned value, as any memos dependent /// on the previous value will not match the new ID. /// /// However, reusing a slot invalidates the previous ID, so dependency edges /// on queries that *create* an interned value are still required to ensure /// the value is re-interned with a new ID. id: Id, /// The revision the value was most-recently interned in. last_interned_at: Revision, /// The minimum durability of all inputs consumed by the creator /// query prior to creating this interned struct. If any of those /// inputs changes, then the creator query may create this struct /// with different values. durability: Durability, } impl ValueShared { /// Returns `true` if this value slot can be reused when interning, and should be added to the LRU. fn is_reusable(&self) -> bool { // Garbage collection is disabled. if C::REVISIONS == IMMORTAL { return false; } // Collecting higher durability values requires invalidating the revision for their // durability (see `Database::synthetic_write`, which requires a mutable reference to // the database) to avoid short-circuiting calls to `maybe_changed_after`. This is // necessary because `maybe_changed_after` for interned values is not "pure"; it updates // the `last_interned_at` field before validating a given value to ensure that it is not // reused after read in the current revision. self.durability == Durability::LOW } } impl Value where C: Configuration, { /// Fields of this interned struct. #[cfg(feature = "salsa_unstable")] pub fn fields(&self) -> &C::Fields<'static> { // SAFETY: The fact that this function is safe is technically unsound. However, interned // values are only exposed if they have been validated in the current revision, which // ensures that they are not reused while being accessed. unsafe { &*self.fields.get() } } /// Returns memory usage information about the interned value. /// /// # Safety /// /// The `MemoTable` must belong to a `Value` of the correct type. Additionally, the /// lock must be held for the shard containing the value. #[cfg(all(not(feature = "shuttle"), feature = "salsa_unstable"))] unsafe fn memory_usage(&self, memo_table_types: &MemoTableTypes) -> crate::database::SlotInfo { let heap_size = C::heap_size(self.fields()); // SAFETY: The caller guarantees we hold the lock for the shard containing the value, so we // have at-least read-only access to the value's memos. let memos = unsafe { &*self.memos.get() }; // SAFETY: The caller guarantees this is the correct types table. let memos = unsafe { memo_table_types.attach_memos(memos) }; crate::database::SlotInfo { debug_name: C::DEBUG_NAME, size_of_metadata: std::mem::size_of::() - std::mem::size_of::>(), size_of_fields: std::mem::size_of::>(), heap_size_of_fields: heap_size, memos: memos.memory_usage(), } } } impl Default for JarImpl { fn default() -> Self { Self { phantom: PhantomData, } } } impl Jar for JarImpl { fn create_ingredients( _zalsa: &mut Zalsa, first_index: IngredientIndex, ) -> Vec> { vec![Box::new(IngredientImpl::::new(first_index)) as _] } fn id_struct_type_id() -> TypeId { TypeId::of::>() } } impl IngredientImpl where C: Configuration, { pub fn new(ingredient_index: IngredientIndex) -> Self { static SHARDS: OnceLock = OnceLock::new(); let shards = *SHARDS.get_or_init(|| { let num_cpus = std::thread::available_parallelism() .map(usize::from) .unwrap_or(1); (num_cpus * 4).next_power_of_two() }); Self { ingredient_index, hasher: FxBuildHasher, memo_table_types: Arc::new(MemoTableTypes::default()), revision_queue: RevisionQueue::default(), shift: usize::BITS - shards.trailing_zeros(), shards: (0..shards).map(|_| Default::default()).collect(), _marker: PhantomData, } } /// Returns the shard for a given hash. /// /// Note that this value is guaranteed to be in-bounds for `self.shards`. #[inline] fn shard(&self, hash: u64) -> usize { // https://github.com/xacrimon/dashmap/blob/366ce7e7872866a06de66eb95002fa6cf2c117a7/src/lib.rs#L421 ((hash as usize) << 7) >> self.shift } /// # Safety /// /// The `from_internal_data` function must be called to restore the correct lifetime /// before access. unsafe fn to_internal_data<'db>(&'db self, data: C::Fields<'db>) -> C::Fields<'static> { // SAFETY: Guaranteed by caller. unsafe { std::mem::transmute(data) } } fn from_internal_data<'db>(data: &'db C::Fields<'static>) -> &'db C::Fields<'db> { // SAFETY: It's sound to go from `Data<'static>` to `Data<'db>`. We shrink the // lifetime here to use a single lifetime in `Lookup::eq(&StructKey<'db>, &C::Data<'db>)` unsafe { std::mem::transmute(data) } } /// Intern data to a unique reference. /// /// If `key` is already interned, returns the existing [`Id`] for the interned data without /// invoking `assemble`. /// /// Otherwise, invokes `assemble` with the given `key` and the [`Id`] to be allocated for this /// interned value. The resulting [`C::Data`] will then be interned. /// /// Note: Using the database within the `assemble` function may result in a deadlock if /// the database ends up trying to intern or allocate a new value. pub fn intern<'db, Key>( &'db self, zalsa: &'db Zalsa, zalsa_local: &'db ZalsaLocal, key: Key, assemble: impl FnOnce(Id, Key) -> C::Fields<'db>, ) -> C::Struct<'db> where Key: Hash, C::Fields<'db>: HashEqLike, { FromId::from_id(self.intern_id(zalsa, zalsa_local, key, assemble)) } /// Intern data to a unique reference. /// /// If `key` is already interned, returns the existing [`Id`] for the interned data without /// invoking `assemble`. /// /// Otherwise, invokes `assemble` with the given `key` and the [`Id`] to be allocated for this /// interned value. The resulting [`C::Data`] will then be interned. /// /// Note: Using the database within the `assemble` function may result in a deadlock if /// the database ends up trying to intern or allocate a new value. pub fn intern_id<'db, Key>( &'db self, zalsa: &'db Zalsa, zalsa_local: &'db ZalsaLocal, key: Key, assemble: impl FnOnce(Id, Key) -> C::Fields<'db>, ) -> crate::Id where Key: Hash, // We'd want the following predicate, but this currently implies `'static` due to a rustc // bug // for<'db> C::Data<'db>: HashEqLike, // so instead we go with this and transmute the lifetime in the `eq` closure C::Fields<'db>: HashEqLike, { // Record the current revision as active. let current_revision = zalsa.current_revision(); self.revision_queue.record(current_revision); // Hash the value before acquiring the lock. let hash = self.hasher.hash_one(&key); let shard_index = self.shard(hash); // SAFETY: `shard_index` is guaranteed to be in-bounds for `self.shards`. let shard = unsafe { &mut *self.shards.get_unchecked(shard_index).lock() }; let found_value = Cell::new(None); // SAFETY: We hold the lock for the shard containing the value. let eq = |id: &_| unsafe { Self::value_eq(*id, &key, zalsa, &found_value) }; // Attempt a fast-path lookup of already interned data. if let Some(&id) = shard.key_map.find(hash, eq) { let value = found_value .get() .expect("found the interned value, so `found_value` should be set"); let index = self.database_key_index(id); // SAFETY: We hold the lock for the shard containing the value. let value_shared = unsafe { &mut *value.shared.get() }; // Validate the value in this revision to avoid reuse. if { value_shared.last_interned_at } < current_revision { value_shared.last_interned_at = current_revision; zalsa.event(&|| { Event::new(EventKind::DidValidateInternedValue { key: index, revision: current_revision, }) }); if value_shared.is_reusable::() { // Move the value to the front of the LRU list. // // SAFETY: We hold the lock for the shard containing the value, and `value` is // a reusable value that was previously interned, so is in the list. unsafe { shard.lru.cursor_mut_from_ptr(value).remove() }; // SAFETY: The value pointer is valid for the lifetime of the database // and never accessed mutably directly. unsafe { shard.lru.push_front(UnsafeRef::from_raw(value)) }; } } if let Some((_, stamp)) = zalsa_local.active_query() { let was_reusable = value_shared.is_reusable::(); // Record the maximum durability across all queries that intern this value. value_shared.durability = std::cmp::max(value_shared.durability, stamp.durability); // If the value is no longer reusable, i.e. the durability increased, remove it // from the LRU. if was_reusable && !value_shared.is_reusable::() { // SAFETY: We hold the lock for the shard containing the value, and `value` // was previously reusable, so is in the list. unsafe { shard.lru.cursor_mut_from_ptr(value).remove() }; } } // Record a dependency on the value. // // See `intern_id_cold` for why we need to use `current_revision` here. Note that just // because this value was previously interned does not mean it was previously interned // by *our query*, so the same considerations apply. zalsa_local.report_tracked_read_simple( index, value_shared.durability, current_revision, ); return value_shared.id; } // Fill up the table for the first few revisions without attempting garbage collection. if !self.revision_queue.is_primed() { return self.intern_id_cold( key, zalsa, zalsa_local, assemble, shard, shard_index, hash, ); } // Otherwise, try to reuse a stale slot. let mut cursor = shard.lru.back_mut(); while let Some(value) = cursor.get() { // SAFETY: We hold the lock for the shard containing the value. let value_shared = unsafe { &mut *value.shared.get() }; // The value must not have been read in the current revision to be collected // soundly, but we also do not want to collect values that have been read recently. // // Note that the list is sorted by LRU, so if the tail of the list is not stale, we // will not find any stale slots. if !self.revision_queue.is_stale(value_shared.last_interned_at) { break; } // We should never reuse a value that was accessed in the current revision. debug_assert!({ value_shared.last_interned_at } < current_revision); // Record the durability of the current query on the interned value. let (durability, last_interned_at) = zalsa_local .active_query() .map(|(_, stamp)| (stamp.durability, current_revision)) // If there is no active query this durability does not actually matter. // `last_interned_at` needs to be `Revision::MAX`, see the `intern_access_in_different_revision` test. .unwrap_or((Durability::MAX, Revision::max())); let old_id = value_shared.id; // Increment the generation of the ID, as if we allocated a new slot. // // If the ID is at its maximum generation, we are forced to leak the slot. let Some(new_id) = value_shared.id.next_generation() else { // Remove the value from the LRU list as we will never be able to // collect it. cursor.remove().unwrap(); // Retry with the previous element. cursor = shard.lru.back_mut(); continue; }; // Mark the slot as reused. *value_shared = ValueShared { id: new_id, durability, last_interned_at, }; let index = self.database_key_index(value_shared.id); // Record a dependency on the new value. // // See `intern_id_cold` for why we need to use `current_revision` here. zalsa_local.report_tracked_read_simple( index, value_shared.durability, current_revision, ); zalsa.event(&|| { Event::new(EventKind::DidReuseInternedValue { key: index, revision: current_revision, }) }); // Remove the value from the LRU list. // // SAFETY: The value pointer is valid for the lifetime of the database. let value = unsafe { &*UnsafeRef::into_raw(cursor.remove().unwrap()) }; // SAFETY: We hold the lock for the shard containing the value, and the // value has not been interned in the current revision, so no references to // it can exist. let old_fields = unsafe { &mut *value.fields.get() }; // Remove the previous value from the ID map. // // Note that while the ID stays the same when a slot is reused, the fields, // and thus the hash, will change, so we need to re-insert the value into the // map. Crucially, we know that the hashes for the old and new fields both map // to the same shard, because we determined the initial shard based on the new // fields and only accessed the LRU list for that shard. let old_hash = self.hasher.hash_one(&*old_fields); shard .key_map .find_entry(old_hash, |found_id: &Id| *found_id == old_id) .expect("interned value in LRU so must be in key_map") .remove(); // Update the fields. // // SAFETY: We call `from_internal_data` to restore the correct lifetime before access. *old_fields = unsafe { self.to_internal_data(assemble(new_id, key)) }; // SAFETY: We hold the lock for the shard containing the value. let hasher = |id: &_| unsafe { self.value_hash(*id, zalsa) }; // Insert the new value into the ID map. shard.key_map.insert_unique(hash, new_id, hasher); // SAFETY: We hold the lock for the shard containing the value, and the // value has not been interned in the current revision, so no references to // it can exist. let memo_table = unsafe { &mut *value.memos.get() }; // Free the memos associated with the previous interned value. // // SAFETY: The memo table belongs to a value that we allocated, so it has the // correct type. unsafe { self.clear_memos(zalsa, memo_table, new_id) }; if value_shared.is_reusable::() { // Move the value to the front of the LRU list. // // SAFETY: The value pointer is valid for the lifetime of the database. // and never accessed mutably directly. shard.lru.push_front(unsafe { UnsafeRef::from_raw(value) }); } return new_id; } // If we could not find any stale slots, we are forced to allocate a new one. self.intern_id_cold(key, zalsa, zalsa_local, assemble, shard, shard_index, hash) } /// The cold path for interning a value, allocating a new slot. /// /// Returns `true` if the current thread interned the value. #[allow(clippy::too_many_arguments)] fn intern_id_cold<'db, Key>( &'db self, key: Key, zalsa: &Zalsa, zalsa_local: &ZalsaLocal, assemble: impl FnOnce(Id, Key) -> C::Fields<'db>, shard: &mut IngredientShard, shard_index: usize, hash: u64, ) -> crate::Id where Key: Hash, C::Fields<'db>: HashEqLike, { let current_revision = zalsa.current_revision(); // Record the durability of the current query on the interned value. let (durability, last_interned_at) = zalsa_local .active_query() .map(|(_, stamp)| (stamp.durability, current_revision)) // If there is no active query this durability does not actually matter. // `last_interned_at` needs to be `Revision::MAX`, see the `intern_access_in_different_revision` test. .unwrap_or((Durability::MAX, Revision::max())); // Allocate the value slot. let (id, value) = zalsa_local.allocate(zalsa, self.ingredient_index, |id| Value:: { shard: shard_index as u16, link: LinkedListLink::new(), // SAFETY: We only ever access the memos of a value that we allocated through // our `MemoTableTypes`. memos: UnsafeCell::new(unsafe { MemoTable::new(self.memo_table_types()) }), // SAFETY: We call `from_internal_data` to restore the correct lifetime before access. fields: UnsafeCell::new(unsafe { self.to_internal_data(assemble(id, key)) }), shared: UnsafeCell::new(ValueShared { id, durability, last_interned_at, }), }); // Insert the newly allocated ID. self.insert_id(id, zalsa, shard, hash, value); let index = self.database_key_index(id); // Record a dependency on the newly interned value. // // Note that the ID is unique to this use of the interned slot, so it seems logical to use // `Revision::start()` here. However, it is possible that the ID we read is different from // the previous execution of this query if the previous slot has been reused. In that case, // the query has changed without a corresponding input changing. Using `current_revision` // for dependencies on interned values encodes the fact that interned IDs are not stable // across revisions. zalsa_local.report_tracked_read_simple(index, durability, current_revision); zalsa.event(&|| { Event::new(EventKind::DidInternValue { key: index, revision: current_revision, }) }); id } /// Inserts a newly interned value ID into the LRU list and key map. fn insert_id( &self, id: Id, zalsa: &Zalsa, shard: &mut IngredientShard, hash: u64, value: &Value, ) { // SAFETY: We hold the lock for the shard containing the value. let value_shared = unsafe { &mut *value.shared.get() }; if value_shared.is_reusable::() { // Add the value to the front of the LRU list. // // SAFETY: The value pointer is valid for the lifetime of the database // and never accessed mutably directly. shard.lru.push_front(unsafe { UnsafeRef::from_raw(value) }); } // SAFETY: We hold the lock for the shard containing the value. let hasher = |id: &_| unsafe { self.value_hash(*id, zalsa) }; // Insert the value into the ID map. shard.key_map.insert_unique(hash, id, hasher); debug_assert_eq!(hash, { let value = zalsa.table().get::>(id); // SAFETY: We hold the lock for the shard containing the value. unsafe { self.hasher.hash_one(&*value.fields.get()) } }); } /// Clears the given memo table. /// /// # Safety /// /// The `MemoTable` must belong to a `Value` of the correct type. pub(crate) unsafe fn clear_memos(&self, zalsa: &Zalsa, memo_table: &mut MemoTable, id: Id) { // SAFETY: The caller guarantees this is the correct types table. let table = unsafe { self.memo_table_types.attach_memos_mut(memo_table) }; // `Database::salsa_event` is a user supplied callback which may panic // in that case we need a drop guard to free the memo table struct TableDropGuard<'a>(MemoTableWithTypesMut<'a>); impl Drop for TableDropGuard<'_> { fn drop(&mut self) { // SAFETY: We have `&mut MemoTable`, so no more references to these memos exist and we are good // to drop them. unsafe { self.0.drop() }; } } let mut table_guard = TableDropGuard(table); // SAFETY: We have `&mut MemoTable`, so no more references to these memos exist and we are good // to drop them. unsafe { table_guard.0.take_memos(|memo_ingredient_index, memo| { let ingredient_index = zalsa.ingredient_index_for_memo(self.ingredient_index, memo_ingredient_index); let executor = DatabaseKeyIndex::new(ingredient_index, id); zalsa.event(&|| Event::new(EventKind::DidDiscard { key: executor })); memo.remove_outputs(zalsa, executor); }) }; std::mem::forget(table_guard); // Reset the table after having dropped any memos. memo_table.reset(); } // Hashes the value by its fields. // // # Safety // // The lock must be held for the shard containing the value. unsafe fn value_hash<'db>(&'db self, id: Id, zalsa: &'db Zalsa) -> u64 { // This closure is only called if the table is resized. So while it's expensive // to lookup all values, it will only happen rarely. let value = zalsa.table().get::>(id); // SAFETY: We hold the lock for the shard containing the value. unsafe { self.hasher.hash_one(&*value.fields.get()) } } // Compares the value by its fields to the given key. // // # Safety // // The lock must be held for the shard containing the value. unsafe fn value_eq<'db, Key>( id: Id, key: &Key, zalsa: &'db Zalsa, found_value: &Cell>>, ) -> bool where C::Fields<'db>: HashEqLike, { let value = zalsa.table().get::>(id); found_value.set(Some(value)); // SAFETY: We hold the lock for the shard containing the value. let fields = unsafe { &*value.fields.get() }; HashEqLike::eq(Self::from_internal_data(fields), key) } /// Returns the database key index for an interned value with the given id. #[inline] pub fn database_key_index(&self, id: Id) -> DatabaseKeyIndex { DatabaseKeyIndex::new(self.ingredient_index, id) } /// Lookup the data for an interned value based on its ID. pub fn data<'db>(&'db self, zalsa: &'db Zalsa, id: Id) -> &'db C::Fields<'db> { let value = zalsa.table().get::>(id); debug_assert!( { let _shard = self.shards[value.shard as usize].lock(); // SAFETY: We hold the lock for the shard containing the value. let value_shared = unsafe { &mut *value.shared.get() }; let last_changed_revision = zalsa.last_changed_revision(value_shared.durability); ({ value_shared.last_interned_at }) >= last_changed_revision }, "Data for `{database_key:?}` was not interned in the latest revision for its durability.", database_key = self.database_key_index(id), ); // SAFETY: Interned values are only exposed if they have been validated in the // current revision, as checked by the assertion above, which ensures that they // are not reused while being accessed. unsafe { Self::from_internal_data(&*value.fields.get()) } } /// Lookup the fields from an interned struct. /// /// Note that this is not "leaking" since no dependency edge is required. pub fn fields<'db>(&'db self, zalsa: &'db Zalsa, s: C::Struct<'db>) -> &'db C::Fields<'db> { self.data(zalsa, AsId::as_id(&s)) } pub fn reset(&mut self, zalsa_mut: &mut Zalsa) { _ = zalsa_mut; for shard in self.shards.iter_mut() { // We can clear the key maps now that we have cancelled all other handles. shard.get_mut().key_map.clear(); } } /// Returns all data corresponding to the interned struct. pub fn entries<'db>(&'db self, zalsa: &'db Zalsa) -> impl Iterator> { // SAFETY: `should_lock` is `true` unsafe { self.entries_inner(true, zalsa) } } /// Returns all data corresponding to the interned struct. /// /// # Safety /// /// If `should_lock` is `false`, the caller *must* hold the locks for all shards /// of the key map. unsafe fn entries_inner<'db>( &'db self, should_lock: bool, zalsa: &'db Zalsa, ) -> impl Iterator> { // TODO: Grab all locks eagerly. zalsa.table().slots_of::>().map(move |(_, value)| { if should_lock { // SAFETY: `value.shard` is guaranteed to be in-bounds for `self.shards`. let _shard = unsafe { self.shards.get_unchecked(value.shard as usize) }.lock(); } // SAFETY: The caller guarantees we hold the lock for the shard containing the value. // // Note that this ID includes the generation, unlike the ID provided by the table. let id = unsafe { (*value.shared.get()).id }; StructEntry { value, key: self.database_key_index(id), } }) } } /// An interned struct entry. pub struct StructEntry<'db, C> where C: Configuration, { #[allow(dead_code)] value: &'db Value, key: DatabaseKeyIndex, } impl<'db, C> StructEntry<'db, C> where C: Configuration, { /// Returns the `DatabaseKeyIndex` for this entry. pub fn key(&self) -> DatabaseKeyIndex { self.key } /// Returns the interned struct. pub fn as_struct(&self) -> C::Struct<'_> { FromId::from_id(self.key.key_index()) } #[cfg(feature = "salsa_unstable")] pub fn value(&self) -> &'db Value { self.value } } impl Ingredient for IngredientImpl where C: Configuration, { fn location(&self) -> &'static crate::ingredient::Location { &C::LOCATION } fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index } unsafe fn maybe_changed_after( &self, zalsa: &crate::zalsa::Zalsa, _db: crate::database::RawDatabase<'_>, input: Id, _revision: Revision, ) -> VerifyResult { // Record the current revision as active. let current_revision = zalsa.current_revision(); self.revision_queue.record(current_revision); let value = zalsa.table().get::>(input); // SAFETY: `value.shard` is guaranteed to be in-bounds for `self.shards`. let _shard = unsafe { self.shards.get_unchecked(value.shard as usize) }.lock(); // SAFETY: We hold the lock for the shard containing the value. let value_shared = unsafe { &mut *value.shared.get() }; // The slot was reused. if value_shared.id.generation() > input.generation() { return VerifyResult::changed(); } // Validate the value for the current revision to avoid reuse. value_shared.last_interned_at = current_revision; zalsa.event(&|| { let index = self.database_key_index(input); Event::new(EventKind::DidValidateInternedValue { key: index, revision: current_revision, }) }); // Any change to an interned value results in a new ID generation. VerifyResult::unchanged() } fn collect_minimum_serialized_edges( &self, _zalsa: &Zalsa, edge: QueryEdge, serialized_edges: &mut FxIndexSet, _visited_edges: &mut FxHashSet, ) { if C::PERSIST && C::REVISIONS != IMMORTAL { // If the interned struct is being persisted, it may be reachable through transitive queries. // Additionally, interned struct dependencies are impure in that garbage collection can // invalidate a dependency without a base input necessarily being updated. Thus, we must // preserve the transitive dependency on the interned struct, if garbage collection is // enabled. serialized_edges.insert(edge); } // Otherwise, the dependency is covered by the base inputs. } fn flatten_cycle_head_dependencies( &self, _zalsa: &Zalsa, id: Id, flattened_input_outputs: &mut FxIndexSet, _seen: &mut FxHashSet, ) { flattened_input_outputs.insert(QueryEdge::input(self.database_key_index(id))); } fn debug_name(&self) -> &'static str { C::DEBUG_NAME } fn jar_kind(&self) -> JarKind { JarKind::Struct } fn memo_table_types(&self) -> &Arc { &self.memo_table_types } fn memo_table_types_mut(&mut self) -> &mut Arc { &mut self.memo_table_types } /// Returns memory usage information about any interned values. #[cfg(all(not(feature = "shuttle"), feature = "salsa_unstable"))] fn memory_usage(&self, db: &dyn crate::Database) -> Option> { use parking_lot::lock_api::RawMutex; for shard in self.shards.iter() { // SAFETY: We do not hold any active mutex guards. unsafe { shard.raw().lock() }; } // SAFETY: We hold the locks for all shards. let entries = unsafe { self.entries_inner(false, db.zalsa()) }; let memory_usage = entries // SAFETY: The memo table belongs to a value that we allocated, so it // has the correct type. Additionally, we are holding the locks for all shards. .map(|entry| unsafe { entry.value.memory_usage(&self.memo_table_types) }) .collect(); for shard in self.shards.iter() { // SAFETY: We acquired the locks for all shards. unsafe { shard.raw().unlock() }; } Some(memory_usage) } fn is_persistable(&self) -> bool { C::PERSIST } fn should_serialize(&self, zalsa: &Zalsa) -> bool { C::PERSIST && self.entries(zalsa).next().is_some() } #[cfg(feature = "persistence")] unsafe fn serialize<'db>( &'db self, zalsa: &'db Zalsa, f: &mut dyn FnMut(&dyn erased_serde::Serialize), ) { f(&persistence::SerializeIngredient { zalsa, ingredient: self, }) } #[cfg(feature = "persistence")] fn deserialize( &mut self, zalsa: &mut Zalsa, deserializer: &mut dyn erased_serde::Deserializer, ) -> Result<(), erased_serde::Error> { let deserialize = persistence::DeserializeIngredient { zalsa, ingredient: self, }; serde::de::DeserializeSeed::deserialize(deserialize, deserializer) } } impl std::fmt::Debug for IngredientImpl where C: Configuration, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct(std::any::type_name::()) .field("index", &self.ingredient_index) .finish() } } // SAFETY: `Value` is our private type branded over the unique configuration `C`. unsafe impl Slot for Value where C: Configuration, { #[inline(always)] unsafe fn memos( this: *const Self, _current_revision: Revision, ) -> *const crate::table::memo::MemoTable { // SAFETY: The fact that we have a pointer to the `Value` means it must // have been interned, and thus validated, in the current revision. // Caller obligation demands this pointer to be valid. unsafe { (*this).memos.get() } } #[inline(always)] fn memos_mut(&mut self) -> &mut crate::table::memo::MemoTable { self.memos.get_mut() } } /// Keep track of revisions in which interned values were read, to determine staleness. /// /// An interned value is considered stale if it has not been read in the past `REVS` /// revisions. However, we only consider revisions in which interned values were actually /// read, as revisions may be created in bursts. struct RevisionQueue { lock: Mutex<()>, // Once `feature(generic_const_exprs)` is stable this can just be an array. revisions: Box<[AtomicRevision]>, _configuration: PhantomData C>, } // `#[salsa::interned(revisions = usize::MAX)]` disables garbage collection. const IMMORTAL: NonZeroUsize = NonZeroUsize::MAX; impl Default for RevisionQueue { fn default() -> RevisionQueue { let revisions = if C::REVISIONS == IMMORTAL { Box::default() } else { (0..C::REVISIONS.get()) .map(|_| AtomicRevision::start()) .collect() }; RevisionQueue { lock: Mutex::new(()), revisions, _configuration: PhantomData, } } } impl RevisionQueue { /// Record the given revision as active. #[inline] fn record(&self, revision: Revision) { // Garbage collection is disabled. if C::REVISIONS == IMMORTAL { return; } // Fast-path: We already recorded this revision. if self.revisions[0].load() >= revision { return; } self.record_cold(revision); } #[cold] fn record_cold(&self, revision: Revision) { let _lock = self.lock.lock(); // Otherwise, update the queue, maintaining sorted order. // // Note that this should only happen once per revision. for i in (1..C::REVISIONS.get()).rev() { self.revisions[i].store(self.revisions[i - 1].load()); } self.revisions[0].store(revision); } /// Returns `true` if the given revision is old enough to be considered stale. #[inline] fn is_stale(&self, revision: Revision) -> bool { // Garbage collection is disabled. if C::REVISIONS == IMMORTAL { return false; } let oldest = self.revisions[C::REVISIONS.get() - 1].load(); // If we have not recorded `REVS` revisions yet, nothing can be stale. if oldest == Revision::start() { return false; } revision < oldest } /// Returns `true` if `C::REVISIONS` revisions have been recorded as active, /// i.e. enough data has been recorded to start garbage collection. #[inline] fn is_primed(&self) -> bool { // Garbage collection is disabled. if C::REVISIONS == IMMORTAL { return false; } self.revisions[C::REVISIONS.get() - 1].load() > Revision::start() } } /// A trait for types that hash and compare like `O`. pub trait HashEqLike { fn hash(&self, h: &mut H); fn eq(&self, data: &O) -> bool; } /// The `Lookup` trait is a more flexible variant on [`std::borrow::Borrow`] /// and [`std::borrow::ToOwned`]. /// /// It is implemented by "some type that can be used as the lookup key for `O`". /// This means that `self` can be hashed and compared for equality with values /// of type `O` without actually creating an owned value. It `self` needs to be interned, /// it can be converted into an equivalent value of type `O`. /// /// The canonical example is `&str: Lookup`. However, this example /// alone can be handled by [`std::borrow::Borrow`][]. In our case, we may have /// multiple keys accumulated into a struct, like `ViewStruct: Lookup<(K1, ...)>`, /// where `struct ViewStruct...>(K1...)`. The `Borrow` trait /// requires that `&(K1...)` be convertible to `&ViewStruct` which just isn't /// possible. `Lookup` instead offers direct `hash` and `eq` methods. pub trait Lookup { fn into_owned(self) -> O; } impl Lookup for T { fn into_owned(self) -> T { self } } impl HashEqLike for T where T: Hash + Eq, { fn hash(&self, h: &mut H) { Hash::hash(self, &mut *h); } fn eq(&self, data: &T) -> bool { self == data } } impl HashEqLike for &T where T: Hash + Eq, { fn hash(&self, h: &mut H) { Hash::hash(*self, &mut *h); } fn eq(&self, data: &T) -> bool { **self == *data } } impl HashEqLike<&T> for T where T: Hash + Eq, { fn hash(&self, h: &mut H) { Hash::hash(self, &mut *h); } fn eq(&self, data: &&T) -> bool { *self == **data } } impl Lookup for &T where T: Clone, { fn into_owned(self) -> T { Clone::clone(self) } } impl<'a, T> HashEqLike<&'a T> for Box where T: ?Sized + Hash + Eq, Box: From<&'a T>, { fn hash(&self, h: &mut H) { Hash::hash(self, &mut *h) } fn eq(&self, data: &&T) -> bool { **self == **data } } impl<'a, T> Lookup> for &'a T where T: ?Sized + Hash + Eq, Box: From<&'a T>, { fn into_owned(self) -> Box { Box::from(self) } } impl<'a, T> HashEqLike<&'a T> for Arc where T: ?Sized + Hash + Eq, Arc: From<&'a T>, { fn hash(&self, h: &mut H) { Hash::hash(&**self, &mut *h) } fn eq(&self, data: &&T) -> bool { **self == **data } } impl<'a, T> Lookup> for &'a T where T: ?Sized + Hash + Eq, Arc: From<&'a T>, { fn into_owned(self) -> Arc { Arc::from(self) } } impl Lookup for &str { fn into_owned(self) -> String { self.to_owned() } } #[cfg(feature = "compact_str")] impl Lookup for &str { fn into_owned(self) -> compact_str::CompactString { compact_str::CompactString::new(self) } } #[cfg(feature = "compact_str")] impl HashEqLike<&str> for compact_str::CompactString { fn hash(&self, h: &mut H) { Hash::hash(self, &mut *h) } fn eq(&self, data: &&str) -> bool { self == *data } } #[cfg(feature = "compact_str")] impl HashEqLike> for compact_str::CompactString { fn hash(&self, h: &mut H) { self.as_str().hash(h); } fn eq(&self, data: &Cow<'_, str>) -> bool { self.as_str() == data.as_ref() } } #[cfg(feature = "compact_str")] impl Lookup for Cow<'_, str> { fn into_owned(self) -> compact_str::CompactString { compact_str::CompactString::new(Cow::into_owned(self)) } } impl HashEqLike<&str> for String { fn hash(&self, h: &mut H) { Hash::hash(self, &mut *h) } fn eq(&self, data: &&str) -> bool { self == *data } } impl> HashEqLike<&[A]> for Vec { fn hash(&self, h: &mut H) { Hash::hash(self, h); } fn eq(&self, data: &&[A]) -> bool { self.len() == data.len() && data.iter().enumerate().all(|(i, a)| &self[i] == a) } } impl + Clone + Lookup, T> Lookup> for &[A] { fn into_owned(self) -> Vec { self.iter().map(|a| Lookup::into_owned(a.clone())).collect() } } impl> HashEqLike<[A; N]> for Vec { fn hash(&self, h: &mut H) { Hash::hash(self, h); } fn eq(&self, data: &[A; N]) -> bool { self.len() == data.len() && data.iter().enumerate().all(|(i, a)| &self[i] == a) } } impl + Clone + Lookup, T> Lookup> for [A; N] { fn into_owned(self) -> Vec { self.into_iter() .map(|a| Lookup::into_owned(a.clone())) .collect() } } impl HashEqLike<&Path> for PathBuf { fn hash(&self, h: &mut H) { Hash::hash(self, h); } fn eq(&self, data: &&Path) -> bool { self == data } } impl Lookup for &Path { fn into_owned(self) -> PathBuf { self.to_owned() } } impl HashEqLike> for T { fn hash(&self, h: &mut H) { Hash::hash(self, h); } fn eq(&self, data: &Cow<'_, T>) -> bool { self == data.as_ref() } } impl Lookup for Cow<'_, T> { fn into_owned(self) -> T { Cow::into_owned(self) } } impl HashEqLike> for String { fn hash(&self, h: &mut H) { self.as_str().hash(h); } fn eq(&self, data: &Cow<'_, str>) -> bool { self.as_str() == data.as_ref() } } impl Lookup for Cow<'_, str> { fn into_owned(self) -> String { Cow::into_owned(self) } } impl HashEqLike> for PathBuf { fn hash(&self, h: &mut H) { self.as_path().hash(h); } fn eq(&self, data: &Cow<'_, Path>) -> bool { self.as_path() == data.as_ref() } } impl Lookup for Cow<'_, Path> { fn into_owned(self) -> PathBuf { Cow::into_owned(self) } } impl HashEqLike> for Box<[T]> { fn hash(&self, h: &mut H) { self.as_ref().hash(h); } fn eq(&self, data: &Cow<'_, [T]>) -> bool { self.as_ref() == data.as_ref() } } impl Lookup> for Cow<'_, [T]> { fn into_owned(self) -> Box<[T]> { Cow::into_owned(self).into_boxed_slice() } } impl HashEqLike> for Vec { fn hash(&self, h: &mut H) { self.as_slice().hash(h); } fn eq(&self, data: &Cow<'_, [T]>) -> bool { self.as_slice() == data.as_ref() } } impl Lookup> for Cow<'_, [T]> { fn into_owned(self) -> Vec { Cow::into_owned(self) } } #[cfg(feature = "persistence")] mod persistence { use std::cell::UnsafeCell; use std::fmt; use std::hash::BuildHasher; use intrusive_collections::LinkedListLink; use serde::ser::{SerializeMap, SerializeStruct}; use serde::{Deserialize, de}; use super::{Configuration, IngredientImpl, Value, ValueShared}; use crate::plumbing::Ingredient; use crate::table::memo::MemoTable; use crate::zalsa::Zalsa; use crate::{Durability, Id, Revision}; pub struct SerializeIngredient<'db, C> where C: Configuration, { pub zalsa: &'db Zalsa, pub ingredient: &'db IngredientImpl, } impl serde::Serialize for SerializeIngredient<'_, C> where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let Self { zalsa, ingredient } = *self; let count = ingredient .shards .iter() .map(|shard| shard.lock().key_map.len()) .sum(); let mut map = serializer.serialize_map(Some(count))?; for (_, value) in zalsa.table().slots_of::>() { // SAFETY: The safety invariant of `Ingredient::serialize` ensures we have exclusive access // to the database. let id = unsafe { (*value.shared.get()).id }; map.serialize_entry(&id.as_bits(), value)?; } map.end() } } impl serde::Serialize for Value where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut value = serializer.serialize_struct("Value,", 3)?; let Value { fields, shared, shard: _, link: _, memos: _, } = self; // SAFETY: The safety invariant of `Ingredient::serialize` ensures we have exclusive access // to the database. let fields = unsafe { &*fields.get() }; // SAFETY: The safety invariant of `Ingredient::serialize` ensures we have exclusive access // to the database. let ValueShared { durability, last_interned_at, id: _, } = unsafe { *shared.get() }; value.serialize_field("durability", &durability)?; value.serialize_field("last_interned_at", &last_interned_at)?; value.serialize_field("fields", &SerializeFields::(fields))?; value.end() } } struct SerializeFields<'db, C: Configuration>(&'db C::Fields<'static>); impl serde::Serialize for SerializeFields<'_, C> where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { C::serialize(self.0, serializer) } } pub struct DeserializeIngredient<'db, C> where C: Configuration, { pub zalsa: &'db mut Zalsa, pub ingredient: &'db mut IngredientImpl, } impl<'de, C> de::DeserializeSeed<'de> for DeserializeIngredient<'_, C> where C: Configuration, { type Value = (); fn deserialize(self, deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_map(self) } } impl<'de, C> de::Visitor<'de> for DeserializeIngredient<'_, C> where C: Configuration, { type Value = (); fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map") } fn visit_map(self, mut access: M) -> Result where M: de::MapAccess<'de>, { let DeserializeIngredient { zalsa, ingredient } = self; while let Some((id, value)) = access.next_entry::>()? { let id = Id::from_bits(id); let (page_idx, _) = crate::table::split_id(id); // Determine the value shard. let hash = ingredient.hasher.hash_one(&value.fields.0); let shard_index = ingredient.shard(hash); // SAFETY: `shard_index` is guaranteed to be in-bounds for `self.shards`. let shard = unsafe { &mut *ingredient.shards.get_unchecked(shard_index).lock() }; let value = Value:: { shard: shard_index as u16, link: LinkedListLink::new(), // SAFETY: We only ever access the memos of a value that we allocated through // our `MemoTableTypes`. memos: UnsafeCell::new(unsafe { MemoTable::new(ingredient.memo_table_types()) }), fields: UnsafeCell::new(value.fields.0), shared: UnsafeCell::new(ValueShared { id, durability: value.durability, last_interned_at: value.last_interned_at, }), }; // Force initialize the relevant page. zalsa.table_mut().force_page::>( page_idx, ingredient.ingredient_index(), ingredient.memo_table_types(), ); // Initialize the slot. // // SAFETY: We have a mutable reference to the database. let (allocated_id, value) = unsafe { zalsa .table() .page(page_idx) .allocate(page_idx, |_| value) .unwrap_or_else(|_| panic!("serialized an invalid `Id`: {id:?}")) }; assert_eq!( allocated_id.index(), id.index(), "values are serialized in allocation order" ); // Insert the newly allocated ID into our ingredient. ingredient.insert_id(id, zalsa, shard, hash, value); } Ok(()) } } #[derive(Deserialize)] #[serde(rename = "Value")] pub struct DeserializeValue { durability: Durability, last_interned_at: Revision, #[serde(bound = "C: Configuration")] fields: DeserializeFields, } struct DeserializeFields(C::Fields<'static>); impl<'de, C> serde::Deserialize<'de> for DeserializeFields where C: Configuration, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { C::deserialize(deserializer) .map(DeserializeFields) .map_err(de::Error::custom) } } } salsa-0.26.2/src/key.rs000064400000000000000000000063301046102023000127450ustar 00000000000000use std::fmt; use crate::Id; use crate::function::VerifyResult; use crate::zalsa::{IngredientIndex, Zalsa}; // ANCHOR: DatabaseKeyIndex /// An integer that uniquely identifies a particular query instance within the /// database. Used to track input and output dependencies between queries. Fully /// ordered and equatable but those orderings are arbitrary, and meant to be used /// only for inserting into maps and the like. #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct DatabaseKeyIndex { key_index: Id, ingredient_index: IngredientIndex, } // ANCHOR_END: DatabaseKeyIndex impl DatabaseKeyIndex { #[inline] pub(crate) const fn new(ingredient_index: IngredientIndex, key_index: Id) -> Self { Self { key_index, ingredient_index, } } pub const fn ingredient_index(self) -> IngredientIndex { self.ingredient_index } pub const fn key_index(self) -> Id { self.key_index } pub(crate) fn maybe_changed_after( &self, db: crate::database::RawDatabase<'_>, zalsa: &Zalsa, last_verified_at: crate::Revision, ) -> VerifyResult { // SAFETY: The `db` belongs to the ingredient unsafe { // here, `db` has to be either the correct type already, or a subtype (as far as trait // hierarchy is concerned) zalsa .lookup_ingredient(self.ingredient_index()) .maybe_changed_after(zalsa, db, self.key_index(), last_verified_at) } } pub(crate) fn remove_stale_output(&self, zalsa: &Zalsa, executor: DatabaseKeyIndex) { zalsa .lookup_ingredient(self.ingredient_index()) .remove_stale_output(zalsa, executor, self.key_index()) } pub(crate) fn mark_validated_output( &self, zalsa: &Zalsa, database_key_index: DatabaseKeyIndex, ) { zalsa .lookup_ingredient(self.ingredient_index()) .mark_validated_output(zalsa, database_key_index, self.key_index()) } } #[cfg(feature = "persistence")] impl serde::Serialize for DatabaseKeyIndex { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serde::Serialize::serialize(&(self.key_index, self.ingredient_index), serializer) } } #[cfg(feature = "persistence")] impl<'de> serde::Deserialize<'de> for DatabaseKeyIndex { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let (key_index, ingredient_index) = serde::Deserialize::deserialize(deserializer)?; Ok(DatabaseKeyIndex { key_index, ingredient_index, }) } } impl fmt::Debug for DatabaseKeyIndex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { crate::attach::with_attached_database(|db| { let ingredient = db.zalsa().lookup_ingredient(self.ingredient_index()); ingredient.fmt_index(self.key_index(), f) }) .unwrap_or_else(|| { f.debug_tuple("DatabaseKeyIndex") .field(&self.ingredient_index()) .field(&self.key_index()) .finish() }) } } salsa-0.26.2/src/lib.rs000064400000000000000000000126131046102023000127240ustar 00000000000000#![deny(clippy::undocumented_unsafe_blocks)] #![forbid(unsafe_op_in_unsafe_fn)] #[cfg(feature = "accumulator")] mod accumulator; mod active_query; mod attach; mod cancelled; mod cycle; mod database; mod database_impl; mod durability; mod event; mod function; mod hash; mod id; mod ingredient; mod ingredient_cache; mod input; mod interned; mod key; mod memo_ingredient_indices; mod return_mode; mod revision; mod runtime; mod salsa_struct; mod storage; mod sync; mod table; mod tracing; mod tracked_struct; mod update; mod views; mod zalsa; mod zalsa_local; #[cfg(not(feature = "inventory"))] mod nonce; #[cfg(feature = "macros")] pub use salsa_macros::{Supertype, Update, accumulator, db, input, interned, tracked}; #[cfg(feature = "salsa_unstable")] pub use self::database::IngredientInfo; #[cfg(feature = "accumulator")] pub use self::accumulator::Accumulator; pub use self::active_query::Backtrace; pub use self::cancelled::Cancelled; pub use self::cycle::Cycle; pub use self::database::Database; pub use self::database_impl::DatabaseImpl; pub use self::durability::Durability; pub use self::event::{Event, EventKind}; pub use self::id::Id; pub use self::input::setter::Setter; pub use self::key::DatabaseKeyIndex; pub use self::return_mode::SalsaAsDeref; pub use self::return_mode::SalsaAsRef; pub use self::revision::Revision; pub use self::runtime::Runtime; pub use self::storage::{Storage, StorageHandle}; pub use self::update::Update; pub use self::zalsa::IngredientIndex; pub use self::zalsa_local::CancellationToken; pub use crate::attach::{attach, attach_allow_change, with_attached_database}; pub use crate::interned::{HashEqLike, Lookup}; pub mod prelude { #[cfg(feature = "accumulator")] pub use crate::accumulator::Accumulator; pub use crate::{Database, Setter}; } /// Internal names used by salsa macros. /// /// # WARNING /// /// The contents of this module are NOT subject to semver. #[doc(hidden)] pub mod plumbing { pub use std::any::TypeId; pub use std::option::Option::{self, None, Some}; pub use typeid::ConstTypeId; #[cfg(feature = "accumulator")] pub use salsa_macro_rules::setup_accumulator_impl; pub use salsa_macro_rules::{ gate_accumulated, macro_if, maybe_default, maybe_default_tt, return_mode_expression, return_mode_ty, setup_input_struct, setup_interned_struct, setup_tracked_assoc_fn_body, setup_tracked_fn, setup_tracked_method_body, setup_tracked_struct, unexpected_cycle_initial, unexpected_cycle_recovery, }; #[cfg(feature = "accumulator")] pub use crate::accumulator::Accumulator; pub use crate::attach::{attach, with_attached_database}; pub use crate::cycle::CycleRecoveryStrategy; pub use crate::database::{Database, current_revision}; pub use crate::durability::Durability; pub use crate::id::{AsId, FromId, FromIdWithDb, Id}; pub use crate::ingredient::{Ingredient, Jar, Location}; pub use crate::ingredient_cache::IngredientCache; pub use crate::interned::{HashEqLike, Lookup}; pub use crate::key::DatabaseKeyIndex; pub use crate::memo_ingredient_indices::{ IngredientIndices, MemoIngredientIndices, MemoIngredientMap, MemoIngredientSingletonIndex, NewMemoIngredientIndices, }; pub use crate::revision::{AtomicRevision, Revision}; pub use crate::runtime::{Runtime, Stamp, stamp}; pub use crate::salsa_struct::{SalsaStructInDb, assert_supertype_no_overlap}; pub use crate::storage::{HasStorage, Storage}; pub use crate::table::memo::MemoTableWithTypes; pub use crate::tracked_struct::TrackedStructInDb; pub use crate::update::helper::{Dispatch as UpdateDispatch, Fallback as UpdateFallback}; pub use crate::update::{Update, always_update}; pub use crate::views::DatabaseDownCaster; pub use crate::zalsa::{ ErasedJar, HasJar, IngredientIndex, JarKind, Zalsa, ZalsaDatabase, register_jar, transmute_data_ptr, views, }; pub use crate::zalsa_local::ZalsaLocal; #[cfg(feature = "persistence")] pub use serde; // A stub for `serde` used when persistence is disabled. // // We provide dummy types to avoid detecting features during macro expansion. #[cfg(not(feature = "persistence"))] pub mod serde { pub trait Serializer { type Ok; type Error; } pub trait Deserializer<'de> { type Ok; type Error; } } #[cfg(feature = "accumulator")] pub mod accumulator { pub use crate::accumulator::{IngredientImpl, JarImpl}; } pub mod input { pub use crate::input::input_field::FieldIngredientImpl; pub use crate::input::setter::SetterImpl; pub use crate::input::singleton::{NotSingleton, Singleton}; pub use crate::input::{Configuration, HasBuilder, IngredientImpl, JarImpl, Value}; } pub mod interned { pub use crate::interned::{Configuration, IngredientImpl, JarImpl, Value}; } pub mod function { pub use crate::function::Configuration; pub use crate::function::IngredientImpl; pub use crate::function::Memo; pub use crate::function::{EvictionPolicy, HasCapacity, Lru, NoopEviction}; pub use crate::table::memo::MemoEntryType; } pub mod tracked_struct { pub use crate::tracked_struct::tracked_field::FieldIngredientImpl; pub use crate::tracked_struct::{Configuration, IngredientImpl, JarImpl, Value}; } } salsa-0.26.2/src/memo_ingredient_indices.rs000064400000000000000000000150441046102023000170220ustar 00000000000000use crate::sync::Arc; use crate::table::memo::{MemoEntryType, MemoTableTypes}; use crate::zalsa::{MemoIngredientIndex, Zalsa}; use crate::{Id, IngredientIndex}; /// An ingredient has an [ingredient index][IngredientIndex]. However, Salsa also supports /// enums of salsa structs (and other salsa enums), and those don't have a constant ingredient index, /// because they are not ingredients by themselves but rather composed of them. However, an enum can /// be viewed as a *set* of [`IngredientIndex`], where each instance of the enum can belong /// to one, potentially different, index. This is what this type represents: a set of /// `IngredientIndex`. #[derive(Clone)] pub struct IngredientIndices { indices: Box<[IngredientIndex]>, } impl From for IngredientIndices { #[inline] fn from(value: IngredientIndex) -> Self { Self { indices: Box::new([value]), } } } impl IngredientIndices { #[inline] pub fn empty() -> Self { Self { indices: Box::default(), } } pub fn merge(iter: impl IntoIterator) -> Self { let mut indices = Vec::new(); for index in iter { indices.extend(index.indices); } indices.sort_unstable(); indices.dedup(); Self { indices: indices.into_boxed_slice(), } } pub fn iter(&self) -> impl Iterator + '_ { self.indices.iter().copied() } } pub trait NewMemoIngredientIndices { /// # Safety /// /// The memo types must be correct. unsafe fn create( zalsa: &mut Zalsa, struct_indices: IngredientIndices, ingredient: IngredientIndex, memo_type: MemoEntryType, intern_ingredient_memo_types: Option<&mut Arc>, ) -> Self; } impl NewMemoIngredientIndices for MemoIngredientIndices { /// # Safety /// /// The memo types must be correct. unsafe fn create( zalsa: &mut Zalsa, struct_indices: IngredientIndices, ingredient: IngredientIndex, memo_type: MemoEntryType, _intern_ingredient_memo_types: Option<&mut Arc>, ) -> Self { debug_assert!( _intern_ingredient_memo_types.is_none(), "intern ingredient can only have a singleton memo ingredient" ); let Some(&last) = struct_indices.indices.last() else { unreachable!("Attempting to construct struct memo mapping for non tracked function?") }; let mut indices = Vec::new(); indices.resize( (last.as_u32() as usize) + 1, MemoIngredientIndex::from_usize((u32::MAX - 1) as usize), ); for &struct_ingredient in &struct_indices.indices { let memo_ingredient_index = zalsa.next_memo_ingredient_index(struct_ingredient, ingredient); indices[struct_ingredient.as_u32() as usize] = memo_ingredient_index; let (struct_ingredient, _) = zalsa.lookup_ingredient_mut(struct_ingredient); let memo_types = Arc::get_mut(struct_ingredient.memo_table_types_mut()) .expect("memo tables are not shared until database initialization is complete"); memo_types.set(memo_ingredient_index, memo_type); } MemoIngredientIndices { indices: indices.into_boxed_slice(), } } } /// This type is to [`MemoIngredientIndex`] what [`IngredientIndices`] is to [`IngredientIndex`]: /// since enums can contain different ingredient indices, they can also have different memo indices, /// so we need to keep track of them. /// /// This acts a map from [`IngredientIndex`] to [`MemoIngredientIndex`] but implemented /// via a slice for fast lookups, trading memory for speed. With these changes, lookups are `O(1)` /// instead of `O(n)`. /// /// A database tends to have few ingredients (i), less function ingredients and even less /// function ingredients targeting `#[derive(Supertype)]` enums (e). /// While this is bounded as `O(i * e)` memory usage, the average case is significantly smaller: a /// function ingredient targeting enums only stores a slice whose length corresponds to the largest /// ingredient index's _value_. For example, if we have the ingredient indices `[2, 6, 17]`, then we /// will allocate a slice whose length is `17 + 1`. /// /// Assuming a heavy example scenario of 1000 ingredients (500 of which are function ingredients, 100 /// of which are enum targeting functions) this would come out to a maximum possibly memory usage of /// 4bytes * 1000 * 100 ~= 0.38MB which is negligible. pub struct MemoIngredientIndices { indices: Box<[MemoIngredientIndex]>, } impl MemoIngredientMap for MemoIngredientIndices { #[inline(always)] fn get_zalsa_id(&self, zalsa: &Zalsa, id: Id) -> MemoIngredientIndex { self.get(zalsa.ingredient_index(id)) } #[inline(always)] fn get(&self, index: IngredientIndex) -> MemoIngredientIndex { self.indices[index.as_u32() as usize] } } #[derive(Debug)] pub struct MemoIngredientSingletonIndex(MemoIngredientIndex); impl MemoIngredientMap for MemoIngredientSingletonIndex { #[inline(always)] fn get_zalsa_id(&self, _: &Zalsa, _: Id) -> MemoIngredientIndex { self.0 } #[inline(always)] fn get(&self, _: IngredientIndex) -> MemoIngredientIndex { self.0 } } impl NewMemoIngredientIndices for MemoIngredientSingletonIndex { #[inline] unsafe fn create( zalsa: &mut Zalsa, indices: IngredientIndices, ingredient: IngredientIndex, memo_type: MemoEntryType, intern_ingredient_memo_types: Option<&mut Arc>, ) -> Self { let &[struct_ingredient] = &*indices.indices else { unreachable!("Attempting to construct struct memo mapping from enum?") }; let memo_ingredient_index = zalsa.next_memo_ingredient_index(struct_ingredient, ingredient); let memo_types = intern_ingredient_memo_types.unwrap_or_else(|| { let (struct_ingredient, _) = zalsa.lookup_ingredient_mut(struct_ingredient); struct_ingredient.memo_table_types_mut() }); Arc::get_mut(memo_types) .expect("memo tables are not shared until database initialization is complete") .set(memo_ingredient_index, memo_type); Self(memo_ingredient_index) } } pub trait MemoIngredientMap: Send + Sync { fn get_zalsa_id(&self, zalsa: &Zalsa, id: Id) -> MemoIngredientIndex; fn get(&self, index: IngredientIndex) -> MemoIngredientIndex; } salsa-0.26.2/src/nonce.rs000064400000000000000000000024201046102023000132530ustar 00000000000000use crate::sync::atomic::{AtomicU32, Ordering}; use std::marker::PhantomData; use std::num::NonZeroU32; /// A type to generate nonces. Store it in a static and each nonce it produces will be unique from other nonces. /// The type parameter `T` just serves to distinguish different kinds of nonces. pub(crate) struct NonceGenerator { value: AtomicU32, phantom: PhantomData, } /// A "nonce" is a value that gets created exactly once. /// We use it to mark the database storage so we can be sure we're seeing the same database. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Nonce(NonZeroU32, PhantomData); impl NonceGenerator { pub(crate) const fn new() -> Self { Self { // start at 1 so we can detect rollover more easily value: AtomicU32::new(1), phantom: PhantomData, } } pub(crate) fn nonce(&self) -> Nonce { let value = self.value.fetch_add(1, Ordering::Relaxed); assert!(value != 0, "nonce rolled over"); Nonce(NonZeroU32::new(value).unwrap(), self.phantom) } } impl Nonce { pub(crate) fn into_u32(self) -> NonZeroU32 { self.0 } pub(crate) fn from_u32(u32: NonZeroU32) -> Self { Self(u32, PhantomData) } } salsa-0.26.2/src/return_mode.rs000064400000000000000000000033521046102023000145010ustar 00000000000000//! User-implementable salsa traits for refining the return type via `returns(as_ref)` and `returns(as_deref)`. use std::ops::Deref; /// Used to determine the return type and value for tracked fields and functions annotated with `returns(as_ref)`. pub trait SalsaAsRef { // The type returned by tracked fields and functions annotated with `returns(as_ref)`. type AsRef<'a> where Self: 'a; // The value returned by tracked fields and functions annotated with `returns(as_ref)`. fn as_ref(&self) -> Self::AsRef<'_>; } impl SalsaAsRef for Option { type AsRef<'a> = Option<&'a T> where Self: 'a; fn as_ref(&self) -> Self::AsRef<'_> { self.as_ref() } } impl SalsaAsRef for Result { type AsRef<'a> = Result<&'a T, &'a E> where Self: 'a; fn as_ref(&self) -> Self::AsRef<'_> { self.as_ref() } } /// Used to determine the return type and value for tracked fields and functions annotated with `returns(as_deref)`. pub trait SalsaAsDeref { // The type returned by tracked fields and functions annotated with `returns(as_deref)`. type AsDeref<'a> where Self: 'a; // The value returned by tracked fields and functions annotated with `returns(as_deref)`. fn as_deref(&self) -> Self::AsDeref<'_>; } impl SalsaAsDeref for Option { type AsDeref<'a> = Option<&'a T::Target> where Self: 'a; fn as_deref(&self) -> Self::AsDeref<'_> { self.as_deref() } } impl SalsaAsDeref for Result { type AsDeref<'a> = Result<&'a T::Target, &'a E> where Self: 'a; fn as_deref(&self) -> Self::AsDeref<'_> { self.as_deref() } } salsa-0.26.2/src/revision.rs000064400000000000000000000152601046102023000140150ustar 00000000000000use std::num::NonZeroUsize; use crate::sync::atomic::{AtomicUsize, Ordering}; /// Value of the initial revision, as a u64. We don't use 0 /// because we want to use a `NonZeroUsize`. const START: usize = 1; /// A unique identifier for the current version of the database. /// /// Each time an input is changed, the revision number is incremented. /// `Revision` is used internally to track which values may need to be /// recomputed, but is not something you should have to interact with /// directly as a user of salsa. #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[repr(transparent)] pub struct Revision { generation: NonZeroUsize, } impl Revision { #[inline] pub const fn max() -> Self { Self::from(usize::MAX) } #[inline] pub(crate) const fn start() -> Self { Self { // SAFETY: `START` is non-zero. generation: unsafe { NonZeroUsize::new_unchecked(START) }, } } #[inline] pub(crate) const fn from(g: usize) -> Self { Self { generation: NonZeroUsize::new(g).unwrap(), } } #[inline] pub(crate) fn from_opt(g: usize) -> Option { NonZeroUsize::new(g).map(|generation| Self { generation }) } #[inline] pub(crate) fn next(self) -> Revision { Self::from(self.generation.get() + 1) } #[inline] pub(crate) const fn as_usize(self) -> usize { self.generation.get() } } #[cfg(feature = "persistence")] impl serde::Serialize for Revision { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serde::Serialize::serialize(&self.as_usize(), serializer) } } #[cfg(feature = "persistence")] impl<'de> serde::Deserialize<'de> for Revision { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { serde::Deserialize::deserialize(deserializer).map(|generation| Self { generation }) } } impl std::fmt::Debug for Revision { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(fmt, "R{}", self.generation) } } #[derive(Debug)] pub struct AtomicRevision { data: AtomicUsize, } #[cfg(feature = "persistence")] impl serde::Serialize for AtomicRevision { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serde::Serialize::serialize(&self.data.load(Ordering::Relaxed), serializer) } } #[cfg(feature = "persistence")] impl<'de> serde::Deserialize<'de> for AtomicRevision { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { serde::Deserialize::deserialize(deserializer).map(|data| Self { data: AtomicUsize::new(data), }) } } impl From for AtomicRevision { fn from(value: Revision) -> Self { Self { data: AtomicUsize::new(value.as_usize()), } } } impl AtomicRevision { pub const fn start() -> Self { Self { data: AtomicUsize::new(START), } } pub const fn new(revision: Revision) -> Self { Self { data: AtomicUsize::new(revision.as_usize()), } } pub fn load(&self) -> Revision { Revision { // SAFETY: We know that the value is non-zero because we only ever store `START` which 1, or a // Revision which is guaranteed to be non-zero. generation: unsafe { NonZeroUsize::new_unchecked(self.data.load(Ordering::Acquire)) }, } } pub fn store(&self, r: Revision) { self.data.store(r.as_usize(), Ordering::Release); } pub fn get_mut(&mut self) -> &mut Revision { // SAFETY: `Revision` is repr(transparent) over `NonZeroUsize`, we only ever store `NonZeroUsize` values, thus making this cast valid. unsafe { &mut *(self.data.get_mut() as *mut usize).cast::() } } } impl Clone for AtomicRevision { fn clone(&self) -> Self { Self { data: AtomicUsize::new(self.load().as_usize()), } } } #[derive(Debug)] pub(crate) struct OptionalAtomicRevision { data: AtomicUsize, } #[cfg(feature = "persistence")] impl serde::Serialize for OptionalAtomicRevision { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serde::Serialize::serialize(&self.data.load(Ordering::Relaxed), serializer) } } #[cfg(feature = "persistence")] impl<'de> serde::Deserialize<'de> for OptionalAtomicRevision { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { serde::Deserialize::deserialize(deserializer).map(|data| Self { data: AtomicUsize::new(data), }) } } impl From for OptionalAtomicRevision { fn from(value: Revision) -> Self { Self { data: AtomicUsize::new(value.as_usize()), } } } impl OptionalAtomicRevision { pub(crate) fn new(revision: Option) -> Self { Self { data: AtomicUsize::new(revision.map_or(0, |r| r.as_usize())), } } pub(crate) fn load(&self) -> Option { Revision::from_opt(self.data.load(Ordering::Acquire)) } pub(crate) fn swap(&self, val: Option) -> Option { Revision::from_opt( self.data .swap(val.map_or(0, |r| r.as_usize()), Ordering::AcqRel), ) } pub(crate) fn compare_exchange( &self, current: Option, new: Option, ) -> Result, Option> { self.data .compare_exchange( current.map_or(0, |r| r.as_usize()), new.map_or(0, |r| r.as_usize()), Ordering::AcqRel, Ordering::Acquire, ) .map(Revision::from_opt) .map_err(Revision::from_opt) } } #[cfg(test)] mod tests { use super::*; #[test] fn optional_atomic_revision() { let val = OptionalAtomicRevision::new(Some(Revision::start())); assert_eq!(val.load(), Some(Revision::start())); assert_eq!(val.swap(None), Some(Revision::start())); assert_eq!(val.load(), None); assert_eq!(val.swap(Some(Revision::start())), None); assert_eq!(val.load(), Some(Revision::start())); assert_eq!( val.compare_exchange(Some(Revision::start()), None), Ok(Some(Revision::start())) ); assert_eq!( val.compare_exchange(Some(Revision::start()), None), Err(None) ); } } salsa-0.26.2/src/runtime/dependency_graph.rs000064400000000000000000000537201046102023000171440ustar 00000000000000use std::pin::Pin; use rustc_hash::FxHashMap; use smallvec::SmallVec; use crate::function::{SyncGuard, SyncOwner}; use crate::key::DatabaseKeyIndex; use crate::runtime::WaitResult; use crate::runtime::dependency_graph::edge::EdgeCondvar; use crate::sync::MutexGuard; use crate::sync::thread::ThreadId; use crate::tracing; type QueryDependents = FxHashMap>; type TransferredDependents = FxHashMap>; #[derive(Debug, Default)] pub(super) struct DependencyGraph { /// A `(K -> V)` pair in this map indicates that the runtime /// `K` is blocked on some query executing in the runtime `V`. /// This encodes a graph that must be acyclic (or else deadlock /// will result). edges: Edges, /// Encodes the `ThreadId` that are blocked waiting for the result /// of a given query. query_dependents: QueryDependents, /// When a key K completes which had dependent queries Qs blocked on it, /// it stores its `WaitResult` here. As they wake up, each query Q in Qs will /// come here to fetch their results. wait_results: FxHashMap, /// A `K -> Q` pair indicates that the query `K`'s lock is now owned by the query /// `Q`. It's important that `transferred` always forms a tree (must be acyclic), /// or else deadlock will result. transferred: FxHashMap, /// A `K -> [Q]` pair indicates that the query `K` owns the locks of /// `Q`. This is the reverse mapping of `transferred` to allow efficient unlocking /// of all dependent queries when `K` completes. transferred_dependents: TransferredDependents, } impl DependencyGraph { /// True if `from_id` depends on `to_id`. /// /// (i.e., there is a path from `from_id` to `to_id` in the graph.) pub(super) fn depends_on(&self, from_id: ThreadId, to_id: ThreadId) -> bool { self.edges.depends_on(from_id, to_id) } /// Modifies the graph so that `from_id` is blocked /// on `database_key`, which is being computed by /// `to_id`. /// /// For this to be reasonable, the lock on the /// results table for `database_key` must be held. /// This ensures that computing `database_key` doesn't /// complete before `block_on` executes. /// /// Preconditions: /// * No path from `to_id` to `from_id` /// (i.e., `me.depends_on(to_id, from_id)` is false) /// * `held_mutex` is a read lock (or stronger) on `database_key` pub(super) fn block_on( mut me: MutexGuard<'_, Self>, from_id: ThreadId, database_key: DatabaseKeyIndex, to_id: ThreadId, query_mutex_guard: QueryMutexGuard, ) -> WaitResult { let cvar = std::pin::pin!(EdgeCondvar::default()); let cvar = cvar.as_ref(); // SAFETY: We are blocking until the result is removed from `DependencyGraph::wait_results` // at which point the `edge` won't signal the condvar anymore. // As such we are keeping the cond var alive until the reference in the edge drops. unsafe { me.add_edge(from_id, database_key, to_id, cvar) }; // Release the mutex that prevents `database_key` // from completing, now that the edge has been added. drop(query_mutex_guard); loop { if let Some(result) = me.wait_results.remove(&from_id) { debug_assert!(!me.edges.contains_key(&from_id)); return result; } me = cvar.wait(me); } } /// Helper for `block_on`: performs actual graph modification /// to add a dependency edge from `from_id` to `to_id`, which is /// computing `database_key`. /// /// # Safety /// /// The caller needs to keep the referent of `cvar` alive until the corresponding /// [`Self::wait_results`] entry has been inserted. unsafe fn add_edge( &mut self, from_id: ThreadId, database_key: DatabaseKeyIndex, to_id: ThreadId, cvar: Pin<&EdgeCondvar>, ) { assert_ne!(from_id, to_id); debug_assert!(!self.edges.contains_key(&from_id)); debug_assert!(!self.depends_on(to_id, from_id)); // SAFETY: The caller is responsible for ensuring that the `EdgeGuard` outlives the `Edge`. let edge = unsafe { edge::Edge::new(to_id, cvar) }; self.edges.insert(from_id, edge); self.query_dependents .entry(database_key) .or_default() .push(from_id); } /// Invoked when runtime `to_id` completes executing /// `database_key`. pub(super) fn unblock_runtimes_blocked_on( &mut self, database_key: DatabaseKeyIndex, wait_result: WaitResult, ) { let dependents = self .query_dependents .remove(&database_key) .unwrap_or_default(); for from_id in dependents { self.unblock_runtime(from_id, wait_result); } } /// Unblock the runtime with the given id with the given wait-result. /// This will cause it resume execution (though it will have to grab /// the lock on this data structure first, to recover the wait result). fn unblock_runtime(&mut self, id: ThreadId, wait_result: WaitResult) { let edge = self.edges.remove(&id).expect("not blocked"); self.wait_results.insert(id, wait_result); // Now that we have inserted the `wait_results`, // notify the thread. edge.notify(); } /// Invoked when the query `database_key` completes and it owns the locks of other queries /// (the queries transferred their locks to `database_key`). pub(super) fn unblock_runtimes_blocked_on_transferred_queries_owned_by( &mut self, database_key: DatabaseKeyIndex, wait_result: WaitResult, ) { fn unblock_recursive( me: &mut DependencyGraph, query: DatabaseKeyIndex, wait_result: WaitResult, ) { me.transferred.remove(&query); for query in me.transferred_dependents.remove(&query).unwrap_or_default() { me.unblock_runtimes_blocked_on(query, wait_result); unblock_recursive(me, query, wait_result); } } // If `database_key` is `c` and it has been transferred to `b` earlier, remove its entry. tracing::trace!( "unblock_runtimes_blocked_on_transferred_queries_owned_by({database_key:?}" ); if let Some((_, owner)) = self.transferred.remove(&database_key) { // If this query previously transferred its lock ownership to another query, remove // it from that queries dependents as it is now completing. self.transferred_dependents .get_mut(&owner) .unwrap() .remove(&database_key); } unblock_recursive(self, database_key, wait_result); } pub(super) fn undo_transfer_lock(&mut self, database_key: DatabaseKeyIndex) { if let Some((_, owner)) = self.transferred.remove(&database_key) { self.transferred_dependents .get_mut(&owner) .unwrap() .remove(&database_key); } } /// Recursively resolves the thread id that currently owns the lock for `database_key`. /// /// Returns `None` if `database_key` hasn't (or has since then been released) transferred its lock /// and the thread id must be looked up in the `SyncTable` instead. pub(super) fn thread_id_of_transferred_query( &self, database_key: DatabaseKeyIndex, skip_over: Option, ) -> Option { let &(mut resolved_thread, owner) = self.transferred.get(&database_key)?; let mut current_owner = owner; while let Some(&(next_thread, next_key)) = self.transferred.get(¤t_owner) { current_owner = next_key; // Ignore the `skip_over` key. E.g. if we have `a -> b -> c` and we want to resolve `a` but are transferring `b` to `c`, then // we don't want to resolve `a` to the owner of `c`. But for `a -> c -> b`, we want resolve `a` to the owner of `c` and not `b` // (because `b` will be owned by `a`). if Some(next_key) == skip_over { continue; } resolved_thread = next_thread; } Some(resolved_thread) } /// Modifies the graph so that the lock on `query` (currently owned by `current_thread`) is /// transferred to `new_owner` (which is owned by `new_owner_id`). /// /// Note, this function will block if `new_owner` runs on a different thread, unless `new_owner` is blocked /// on current thread after transferring the query ownership. /// /// Returns `true` if the transfer blocked on `new_owner` (in which case it might be necessary to refetch any previously computed memos). pub(super) fn transfer_lock( mut me: MutexGuard, query: DatabaseKeyIndex, current_thread: ThreadId, new_owner: DatabaseKeyIndex, new_owner_id: SyncOwner, guard: SyncGuard, ) -> bool { let dg = &mut *me; let new_owner_thread = match new_owner_id { SyncOwner::Thread(thread) => thread, SyncOwner::Transferred => { // Skip over `query` to skip over any existing mapping from `new_owner` to `query` that may // exist from previous transfers. dg.thread_id_of_transferred_query(new_owner, Some(query)) .expect("new owner should be blocked on `query`") } }; debug_assert!( new_owner_thread == current_thread || dg.depends_on(new_owner_thread, current_thread), "new owner {new_owner:?} ({new_owner_thread:?}) must be blocked on {query:?} ({current_thread:?})" ); let thread_changed = match dg.transferred.entry(query) { std::collections::hash_map::Entry::Vacant(entry) => { // Transfer `c -> b` and there's no existing entry for `c`. entry.insert((new_owner_thread, new_owner)); current_thread != new_owner_thread } std::collections::hash_map::Entry::Occupied(mut entry) => { // If we transfer to the same owner as before, return immediately as this is a no-op. if entry.get() == &(new_owner_thread, new_owner) { return false; } // `Transfer `c -> b` after a previous `c -> d` mapping. // Update the owner and remove the query from the old owner's dependents. let &(old_owner_thread, old_owner) = entry.get(); // For the example below, remove `d` from `b`'s dependents.` dg.transferred_dependents .get_mut(&old_owner) .unwrap() .remove(&query); entry.insert((new_owner_thread, new_owner)); // If we have `c -> a -> d` and we now insert a mapping `d -> c`, rewrite the mapping to // `d -> c -> a` to avoid cycles. // // Or, starting with `e -> c -> a -> d -> b` insert `d -> c`. We need to rewrite the tree to // ``` // e -> c -> a -> b // d / // ``` // // A cycle between transfers can occur when a later iteration has a different outer most query than // a previous iteration. The second iteration then hits `cycle_initial` for a different head, (e.g. for `c` where it previously was `d`). let mut last_segment = dg.transferred.entry(new_owner); while let std::collections::hash_map::Entry::Occupied(mut entry) = last_segment { let source = *entry.key(); let next_target = entry.get().1; // If it's `a -> d`, remove `a -> d` and insert an edge from `a -> b` if next_target == query { tracing::trace!( "Remap edge {source:?} -> {next_target:?} to {source:?} -> {old_owner:?} to prevent a cycle", ); // Remove `a` from the dependents of `d` and remove the mapping from `a -> d`. dg.transferred_dependents .get_mut(&query) .unwrap() .remove(&source); // if the old mapping was `c -> d` and we now insert `d -> c`, remove `c -> d` if old_owner == new_owner { entry.remove(); } else { // otherwise (when `d` pointed to some other query, e.g. `b` in the example), // add an edge from `a` to `b` entry.insert((old_owner_thread, old_owner)); dg.transferred_dependents .get_mut(&old_owner) .unwrap() .push(source); } break; } last_segment = dg.transferred.entry(next_target); } // We simply assume here that the thread has changed because we'd have to walk the entire // transferred chaine of `old_owner` to know if the thread has changed. This won't save us much // compared to just updating all dependent threads. true } }; // Register `c` as a dependent of `b`. let all_dependents = dg.transferred_dependents.entry(new_owner).or_default(); debug_assert!(!all_dependents.contains(&new_owner)); all_dependents.push(query); if thread_changed { tracing::debug!("Unblocking new owner of transfer target {new_owner:?}"); dg.unblock_transfer_target(query, new_owner_thread); dg.update_transferred_edges(query, new_owner_thread); // Block on the new owner, unless new owner is blocked on this query. // This is necessary to avoid a race between `fetch` completing and `provisional_retry` blocking on the // first cycle head. if current_thread != new_owner_thread && !dg.depends_on(new_owner_thread, current_thread) { crate::tracing::info!( "block_on: thread {current_thread:?} is blocking on {new_owner:?} in thread {new_owner_thread:?}", ); Self::block_on(me, current_thread, new_owner, new_owner_thread, guard); return true; } } false } /// Finds the one query in the dependents of the `source_query` (the one that is transferred to a new owner) /// on which the `new_owner_id` thread blocks on and unblocks it, to ensure progress. fn unblock_transfer_target(&mut self, source_query: DatabaseKeyIndex, new_owner_id: ThreadId) { /// Finds the thread that's currently blocking the `new_owner_id` thread. /// /// Returns `Some` if there's such a thread where the first element is the query /// that the thread is blocked on (key into `query_dependents`) and the second element /// is the index in the list of blocked threads (index into the `query_dependents` value) for that query. fn find_blocked_thread( me: &DependencyGraph, query: DatabaseKeyIndex, new_owner_id: ThreadId, ) -> Option<(DatabaseKeyIndex, usize)> { if let Some(blocked_threads) = me.query_dependents.get(&query) { for (i, id) in blocked_threads.iter().copied().enumerate() { if id == new_owner_id || me.edges.depends_on(new_owner_id, id) { return Some((query, i)); } } } me.transferred_dependents .get(&query) .iter() .copied() .flatten() .find_map(|dependent| find_blocked_thread(me, *dependent, new_owner_id)) } if let Some((query, query_dependents_index)) = find_blocked_thread(self, source_query, new_owner_id) { let blocked_threads = self.query_dependents.get_mut(&query).unwrap(); let thread_id = blocked_threads.swap_remove(query_dependents_index); if blocked_threads.is_empty() { self.query_dependents.remove(&query); } self.unblock_runtime(thread_id, WaitResult::Completed); } } fn update_transferred_edges(&mut self, query: DatabaseKeyIndex, new_owner_thread: ThreadId) { fn update_transferred_edges( edges: &mut Edges, query_dependents: &QueryDependents, transferred_dependents: &TransferredDependents, query: DatabaseKeyIndex, new_owner_thread: ThreadId, ) { tracing::trace!("update_transferred_edges({query:?}"); if let Some(dependents) = query_dependents.get(&query) { for dependent in dependents.iter() { let edge = edges.get_mut(dependent).unwrap(); tracing::trace!( "Rewrite edge from {:?} to {new_owner_thread:?}", edge.blocked_on_id ); edge.blocked_on_id = new_owner_thread; debug_assert!( !edges.depends_on(new_owner_thread, *dependent), "Circular reference between blocked edges: {:#?}", edges ); } }; if let Some(dependents) = transferred_dependents.get(&query) { for dependent in dependents { update_transferred_edges( edges, query_dependents, transferred_dependents, *dependent, new_owner_thread, ) } } } update_transferred_edges( &mut self.edges, &self.query_dependents, &self.transferred_dependents, query, new_owner_thread, ) } } #[derive(Debug, Default)] struct Edges(FxHashMap); impl Edges { fn depends_on(&self, from_id: ThreadId, to_id: ThreadId) -> bool { let mut p = from_id; while let Some(q) = self.0.get(&p).map(|edge| edge.blocked_on_id) { if q == to_id { return true; } p = q; } p == to_id } fn get_mut(&mut self, id: &ThreadId) -> Option<&mut edge::Edge> { self.0.get_mut(id) } fn contains_key(&self, id: &ThreadId) -> bool { self.0.contains_key(id) } fn insert(&mut self, id: ThreadId, edge: edge::Edge) { self.0.insert(id, edge); } fn remove(&mut self, id: &ThreadId) -> Option { self.0.remove(id) } } #[derive(Debug)] struct SmallSet(SmallVec<[T; N]>); impl SmallSet where T: PartialEq, { const fn new() -> Self { Self(SmallVec::new_const()) } fn push(&mut self, value: T) { debug_assert!(!self.0.contains(&value)); self.0.push(value); } fn contains(&self, value: &T) -> bool { self.0.contains(value) } fn remove(&mut self, value: &T) -> bool { if let Some(index) = self.0.iter().position(|x| x == value) { self.0.swap_remove(index); true } else { false } } fn iter(&self) -> std::slice::Iter<'_, T> { self.0.iter() } } impl IntoIterator for SmallSet { type Item = T; type IntoIter = smallvec::IntoIter<[T; N]>; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl<'a, T, const N: usize> IntoIterator for &'a SmallSet where T: PartialEq, { type Item = &'a T; type IntoIter = std::slice::Iter<'a, T>; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl Default for SmallSet where T: PartialEq, { fn default() -> Self { Self::new() } } mod edge { use crate::sync::thread::ThreadId; use crate::sync::{Condvar, MutexGuard}; use std::pin::Pin; #[derive(Default, Debug)] pub(super) struct EdgeCondvar { condvar: Condvar, _phantom_pin: std::marker::PhantomPinned, } impl EdgeCondvar { #[inline] pub(super) fn wait<'a, T>(&self, mutex_guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> { self.condvar.wait(mutex_guard) } } #[derive(Debug)] pub(super) struct Edge { pub(super) blocked_on_id: ThreadId, /// Signalled whenever a query with dependents completes. /// Allows those dependents to check if they are ready to unblock. /// `condvar: unsafe<'stack_frame> Pin<&'stack_frame Condvar>` condvar: Pin<&'static EdgeCondvar>, } impl Edge { /// # SAFETY /// /// The caller must ensure that the [`EdgeCondvar`] is kept alive until the [`Edge`] is dropped. pub(super) unsafe fn new(blocked_on_id: ThreadId, condvar: Pin<&EdgeCondvar>) -> Self { Self { blocked_on_id, // SAFETY: The caller is responsible for ensuring that the `EdgeCondvar` outlives the `Edge`. condvar: unsafe { std::mem::transmute::, Pin<&'static EdgeCondvar>>(condvar) }, } } #[inline] pub(super) fn notify(self) { self.condvar.condvar.notify_one(); } } } salsa-0.26.2/src/runtime.rs000064400000000000000000000342601046102023000136430ustar 00000000000000use self::dependency_graph::DependencyGraph; use crate::durability::Durability; use crate::function::{SyncGuard, SyncOwner}; use crate::key::DatabaseKeyIndex; use crate::sync::Mutex; use crate::sync::atomic::{AtomicBool, Ordering}; use crate::sync::thread::{self, ThreadId}; use crate::table::Table; use crate::zalsa::Zalsa; use crate::{Cancelled, Event, EventKind, Revision}; mod dependency_graph; #[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] pub struct Runtime { /// Set to true when the current revision has been cancelled. /// This is done when we an input is being changed. The flag /// is set back to false once the input has been changed. #[cfg_attr(feature = "persistence", serde(skip))] revision_cancelled: AtomicBool, /// Stores the "last change" revision for values of each duration. /// This vector is always of length at least 1 (for Durability 0) /// but its total length depends on the number of durations. The /// element at index 0 is special as it represents the "current /// revision". In general, we have the invariant that revisions /// in here are *declining* -- that is, `revisions[i] >= /// revisions[i + 1]`, for all `i`. This is because when you /// modify a value with durability D, that implies that values /// with durability less than D may have changed too. revisions: [Revision; Durability::LEN], /// The dependency graph tracks which runtimes are blocked on one /// another, waiting for queries to terminate. #[cfg_attr(feature = "persistence", serde(skip))] dependency_graph: Mutex, /// Data for instances #[cfg_attr(feature = "persistence", serde(skip))] table: Table, } #[derive(Copy, Clone, Debug)] pub(super) enum WaitResult { Completed, Panicked, Cancelled, } #[derive(Debug)] pub(crate) enum BlockResult<'me> { /// The query is running on another thread. Running(Running<'me>), /// Blocking resulted in a cycle. /// /// The lock is hold by the current thread or there's another thread that is waiting on the current thread, /// and blocking this thread on the other thread would result in a deadlock/cycle. Cycle, } pub(crate) enum BlockTransferredResult<'me> { /// The current thread is the owner of the transferred query /// and it can claim it if it wants to. ImTheOwner, /// The query is owned/running on another thread. OwnedBy(Box>), /// The query has transferred its ownership to another query previously but that query has /// since then completed and released the lock. Released, } pub(super) struct BlockOnTransferredOwner<'me> { dg: crate::sync::MutexGuard<'me, DependencyGraph>, /// The query that we're trying to claim. database_key: DatabaseKeyIndex, /// The thread that currently owns the lock for the transferred query. other_id: ThreadId, /// The current thread that is trying to claim the transferred query. thread_id: ThreadId, } impl<'me> BlockOnTransferredOwner<'me> { /// Block on the other thread to complete the computation. pub(super) fn block(self, query_mutex_guard: SyncGuard<'me>) -> BlockResult<'me> { // Cycle in the same thread. if self.thread_id == self.other_id { return BlockResult::Cycle; } if self.dg.depends_on(self.other_id, self.thread_id) { crate::tracing::debug!( "block_on: cycle detected for {:?} in thread {thread_id:?} on {:?}", self.database_key, self.other_id, thread_id = self.thread_id ); return BlockResult::Cycle; } BlockResult::Running(Running(Box::new(BlockedOnInner { dg: self.dg, query_mutex_guard, database_key: self.database_key, other_id: self.other_id, thread_id: self.thread_id, }))) } } pub struct Running<'me>(Box>); struct BlockedOnInner<'me> { dg: crate::sync::MutexGuard<'me, DependencyGraph>, query_mutex_guard: SyncGuard<'me>, database_key: DatabaseKeyIndex, other_id: ThreadId, thread_id: ThreadId, } impl Running<'_> { /// Blocks on the other thread to complete the computation. /// /// Returns `true` if the computation was successful, and `false` if the other thread was locally cancelled. /// /// # Panics /// /// If the other thread panics, this function will panic as well. #[must_use] pub(crate) fn block_on(self, zalsa: &Zalsa) -> bool { let BlockedOnInner { dg, query_mutex_guard, database_key, other_id, thread_id, } = *self.0; zalsa.event(&|| { Event::new(EventKind::WillBlockOn { other_thread_id: other_id, database_key, }) }); crate::tracing::info!( "block_on: thread {thread_id:?} is blocking on {database_key:?} in thread {other_id:?}", ); let result = DependencyGraph::block_on(dg, thread_id, database_key, other_id, query_mutex_guard); match result { WaitResult::Panicked => { // If the other thread panicked, then we consider this thread // cancelled. The assumption is that the panic will be detected // by the other thread and responded to appropriately. Cancelled::PropagatedPanic.throw() } WaitResult::Cancelled => false, WaitResult::Completed => true, } } } impl std::fmt::Debug for Running<'_> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fmt.debug_struct("Running") .field("database_key", &self.0.database_key) .field("other_id", &self.0.other_id) .field("thread_id", &self.0.thread_id) .finish() } } #[derive(Copy, Clone, Debug)] pub struct Stamp { pub durability: Durability, pub changed_at: Revision, } pub fn stamp(revision: Revision, durability: Durability) -> Stamp { Stamp { durability, changed_at: revision, } } impl Default for Runtime { fn default() -> Self { Runtime { revisions: [Revision::start(); Durability::LEN], revision_cancelled: Default::default(), dependency_graph: Default::default(), table: Default::default(), } } } impl std::fmt::Debug for Runtime { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fmt.debug_struct("Runtime") .field("revisions", &self.revisions) .field("revision_cancelled", &self.revision_cancelled) .field("dependency_graph", &self.dependency_graph) .finish() } } impl Runtime { #[inline] pub(crate) fn current_revision(&self) -> Revision { self.revisions[0] } /// Reports that an input with durability `durability` changed. /// This will update the 'last changed at' values for every durability /// less than or equal to `durability` to the current revision. pub(crate) fn report_tracked_write(&mut self, durability: Durability) { let new_revision = self.current_revision(); self.revisions[1..=durability.index()].fill(new_revision); } /// The revision in which values with durability `d` may have last /// changed. For D0, this is just the current revision. But for /// higher levels of durability, this value may lag behind the /// current revision. If we encounter a value of durability Di, /// then, we can check this function to get a "bound" on when the /// value may have changed, which allows us to skip walking its /// dependencies. #[inline] pub(crate) fn last_changed_revision(&self, d: Durability) -> Revision { self.revisions[d.index()] } pub(crate) fn load_cancellation_flag(&self) -> bool { self.revision_cancelled.load(Ordering::Acquire) } pub(crate) fn set_cancellation_flag(&self) { crate::tracing::trace!("set_cancellation_flag"); self.revision_cancelled.store(true, Ordering::Release); } pub(crate) fn reset_cancellation_flag(&mut self) { *self.revision_cancelled.get_mut() = false; } /// Returns the [`Table`] used to store the value of salsa structs #[inline] pub(crate) fn table(&self) -> &Table { &self.table } pub(crate) fn table_mut(&mut self) -> &mut Table { &mut self.table } /// Increments the "current revision" counter and clears /// the cancellation flag. /// /// This should only be done by the storage when the state is "quiescent". pub(crate) fn new_revision(&mut self) -> Revision { let r_old = self.current_revision(); let r_new = r_old.next(); self.revisions[0] = r_new; crate::tracing::info!("new_revision: {r_old:?} -> {r_new:?}"); r_new } /// Block until `other_id` completes executing `database_key`, or return `BlockResult::Cycle` /// immediately in case of a cycle. /// /// `query_mutex_guard` is the guard for the current query's state; /// it will be dropped after we have successfully registered the /// dependency. /// /// # Propagating panics /// /// If the thread `other_id` panics, then our thread is considered /// cancelled, so this function will panic with a `Cancelled` value. pub(crate) fn block<'a>( &'a self, database_key: DatabaseKeyIndex, other_id: ThreadId, query_mutex_guard: SyncGuard<'a>, ) -> BlockResult<'a> { let thread_id = thread::current().id(); // Cycle in the same thread. if thread_id == other_id { return BlockResult::Cycle; } let dg = self.dependency_graph.lock(); if dg.depends_on(other_id, thread_id) { crate::tracing::debug!( "block_on: cycle detected for {database_key:?} in thread {thread_id:?} on {other_id:?}" ); return BlockResult::Cycle; } BlockResult::Running(Running(Box::new(BlockedOnInner { dg, query_mutex_guard, database_key, other_id, thread_id, }))) } /// Tries to claim ownership of a transferred query where `thread_id` is the current thread and `query` /// is the query (that had its ownership transferred) to claim. /// /// For this operation to be reasonable, the caller must ensure that the sync table lock on `query` is not released /// before this operation completes. pub(super) fn block_transferred( &self, query: DatabaseKeyIndex, current_id: ThreadId, ) -> BlockTransferredResult<'_> { let dg = self.dependency_graph.lock(); let owner_thread = dg.thread_id_of_transferred_query(query, None); let Some(owner_thread_id) = owner_thread else { // The query transferred its ownership but the owner has since then released the lock. return BlockTransferredResult::Released; }; if owner_thread_id == current_id || dg.depends_on(owner_thread_id, current_id) { BlockTransferredResult::ImTheOwner } else { // Lock is owned by another thread, wait for it to be released. BlockTransferredResult::OwnedBy(Box::new(BlockOnTransferredOwner { dg, database_key: query, other_id: owner_thread_id, thread_id: current_id, })) } } /// Invoked when this runtime completed computing `database_key` with /// the given result `wait_result`. /// This function unblocks any dependent queries and allows them /// to continue executing. pub(crate) fn unblock_queries_blocked_on( &self, database_key: DatabaseKeyIndex, wait_result: WaitResult, ) { self.dependency_graph .lock() .unblock_runtimes_blocked_on(database_key, wait_result); } /// Unblocks all transferred queries that are owned by `database_key` recursively. /// /// Invoked when a query completes that has been marked as transfer target (it has /// queries that transferred their lock ownership to it) with the given `wait_result`. /// /// This function unblocks any dependent queries and allows them to continue executing. The /// query `database_key` is not unblocked by this function. #[cold] pub(crate) fn unblock_transferred_queries_owned_by( &self, database_key: DatabaseKeyIndex, wait_result: WaitResult, ) { self.dependency_graph .lock() .unblock_runtimes_blocked_on_transferred_queries_owned_by(database_key, wait_result); } /// Removes the ownership transfer of `query`'s lock if it exists. /// /// If `query` has transferred its lock ownership to another query, this function will remove that transfer, /// so that `query` now owns its lock again. #[cold] pub(super) fn undo_transfer_lock(&self, query: DatabaseKeyIndex) { self.dependency_graph.lock().undo_transfer_lock(query); } /// Transfers ownership of the lock for `query` to `new_owner_key`. /// /// For this operation to be reasonable, the caller must ensure that the sync table lock on `query` is not released /// and that `new_owner_key` is currently blocked on `query`. Otherwise, `new_owner_key` might /// complete before the lock is transferred, leaving `query` locked forever. pub(super) fn transfer_lock( &self, query: DatabaseKeyIndex, new_owner_key: DatabaseKeyIndex, new_owner_id: SyncOwner, guard: SyncGuard, ) -> bool { let dg = self.dependency_graph.lock(); DependencyGraph::transfer_lock( dg, query, thread::current().id(), new_owner_key, new_owner_id, guard, ) } #[cfg(feature = "persistence")] pub(crate) fn deserialize_from(&mut self, other: &mut Runtime) { // The only field that is serialized is `revisions`. self.revisions = other.revisions; } } salsa-0.26.2/src/salsa_struct.rs000064400000000000000000000107461046102023000146720ustar 00000000000000use std::any::TypeId; use crate::memo_ingredient_indices::{IngredientIndices, MemoIngredientMap}; use crate::table::memo::MemoTableWithTypes; use crate::zalsa::Zalsa; use crate::{DatabaseKeyIndex, Id, Revision}; pub trait SalsaStructInDb: Sized { type MemoIngredientMap: MemoIngredientMap; /// The type IDs of all concrete (leaf) salsa struct types that this type can contain. /// /// For concrete salsa structs (input/tracked/interned), this is a single-element slice /// containing the struct's own type ID. /// /// For supertype enums, this is the concatenation of all variants' leaf type IDs, /// enabling transitive overlap detection. const LEAF_TYPE_IDS: &'static [typeid::ConstTypeId]; /// Lookup or create ingredient indices. /// /// Note that this method does *not* create the ingredients themselves, this is handled by /// [`crate::zalsa::JarEntry::get_or_create`]. This method only creates /// or looks up the indices corresponding to the ingredients. /// /// While implementors of this trait may call [`crate::zalsa::JarEntry::get_or_create`] /// to create the ingredient, they aren't required to. For example, supertypes recursively /// call [`crate::zalsa::JarEntry::get_or_create`] for their variants and combine them. fn lookup_ingredient_index(zalsa: &Zalsa) -> IngredientIndices; /// Returns the IDs of any instances of this struct in the database. fn entries(zalsa: &Zalsa) -> impl Iterator + '_; /// Plumbing to support nested salsa supertypes. /// /// In the example below, there are two supertypes: `InnerEnum` and `OuterEnum`, /// where the former is a supertype of `Input` and `Interned1` and the latter /// is a supertype of `InnerEnum` and `Interned2`. /// /// ```ignore /// #[salsa::input] /// struct Input {} /// /// #[salsa::interned] /// struct Interned1 {} /// /// #[salsa::interned] /// struct Interned2 {} /// /// #[derive(Debug, salsa::Enum)] /// enum InnerEnum { /// Input(Input), /// Interned1(Interned1), /// } /// /// #[derive(Debug, salsa::Enum)] /// enum OuterEnum { /// InnerEnum(InnerEnum), /// Interned2(Interned2), /// } /// ``` /// /// Imagine `OuterEnum` got a [`salsa::Id`][Id] and it wants to know which variant it belongs to. /// /// `OuterEnum` cannot ask each variant "what is your ingredient index?" and compare because `InnerEnum` /// has *multiple*, possible ingredient indices. Alternatively, `OuterEnum` could ask eaach variant /// "is this value yours?" and then invoke [`FromId`][crate::id::FromId] with the correct variant, /// but this duplicates work: now, `InnerEnum` will have to repeat this check-and-cast for *its* /// variants. /// /// Instead, the implementor keeps track of the [`std::any::TypeId`] of the ID struct, and ask each /// variant to "cast" to it. If it succeeds, `cast` returns that value; if not, we /// go to the next variant. /// /// Why `TypeId` and not `IngredientIndex`? Because it's cheaper and easier: the `TypeId` is readily /// available at compile time, while the `IngredientIndex` requires a runtime lookup. fn cast(id: Id, type_id: TypeId) -> Option; /// Return the memo table associated with `id`. /// /// # Safety /// /// The parameter `current_revision` must be the current revision of the owner of database /// owning this table. unsafe fn memo_table( zalsa: &Zalsa, id: Id, current_revision: Revision, ) -> MemoTableWithTypes<'_>; } /// Asserts that no two variants of a supertype enum transitively contain the same concrete /// salsa type. Called once at runtime from the generated `lookup_ingredient_index`. pub fn assert_supertype_no_overlap( enum_name: &str, variant_leaves: &[&[typeid::ConstTypeId]], variant_names: &[&str], ) { for i in 0..variant_leaves.len() { for j in (i + 1)..variant_leaves.len() { for a in variant_leaves[i] { for b in variant_leaves[j] { assert!( a != b, "supertype enum `{enum_name}` has overlapping variants: \ `{}` and `{}` (transitively) contain the same concrete salsa type", variant_names[i], variant_names[j], ); } } } } } salsa-0.26.2/src/storage.rs000064400000000000000000000207761046102023000136330ustar 00000000000000//! Public API facades for the implementation details of [`Zalsa`] and [`ZalsaLocal`]. use std::marker::PhantomData; use std::panic::RefUnwindSafe; use crate::sync::{Arc, Condvar, Mutex}; use crate::zalsa::{ErasedJar, HasJar, Zalsa, ZalsaDatabase}; use crate::zalsa_local::{self, ZalsaLocal}; use crate::{Database, Event, EventKind}; /// A handle to non-local database state. pub struct StorageHandle { // Note: Drop order is important, zalsa_impl needs to drop before coordinate /// Reference to the database. zalsa_impl: Arc, // Note: Drop order is important, coordinate needs to drop after zalsa_impl /// Coordination data for cancellation of other handles when `zalsa_mut` is called. /// This could be stored in Zalsa but it makes things marginally cleaner to keep it separate. coordinate: CoordinateDrop, /// We store references to `Db` phantom: PhantomData Db>, } impl Clone for StorageHandle { fn clone(&self) -> Self { *self.coordinate.clones.lock() += 1; Self { zalsa_impl: self.zalsa_impl.clone(), coordinate: CoordinateDrop(Arc::clone(&self.coordinate)), phantom: PhantomData, } } } impl Default for StorageHandle { fn default() -> Self { Self::new(None) } } impl StorageHandle { pub fn new(event_callback: Option>) -> Self { Self::with_jars(event_callback, Vec::new()) } fn with_jars( event_callback: Option>, jars: Vec, ) -> Self { Self { zalsa_impl: Arc::new(Zalsa::new::(event_callback, jars)), coordinate: CoordinateDrop(Arc::new(Coordinate { clones: Mutex::new(1), cvar: Default::default(), })), phantom: PhantomData, } } pub fn into_storage(self) -> Storage { Storage { handle: self, zalsa_local: ZalsaLocal::new(), } } } /// Access the "storage" of a Salsa database: this is an internal plumbing trait /// automatically implemented by `#[salsa::db]` applied to a struct. /// /// # Safety /// /// The `storage` and `storage_mut` fields must both return a reference to the same /// storage field which must be owned by `self`. pub unsafe trait HasStorage: Database + Sized { fn storage(&self) -> &Storage; fn storage_mut(&mut self) -> &mut Storage; } /// Concrete implementation of the [`Database`] trait with local state that can be used to drive computations. pub struct Storage { handle: StorageHandle, /// Per-thread state zalsa_local: zalsa_local::ZalsaLocal, } impl Drop for Storage { fn drop(&mut self) { self.zalsa_local .record_unfilled_pages(self.handle.zalsa_impl.table()); } } struct Coordinate { /// Counter of the number of clones of actor. Begins at 1. /// Incremented when cloned, decremented when dropped. clones: Mutex, cvar: Condvar, } // We cannot panic while holding a lock to `clones: Mutex` and therefore we cannot enter an // inconsistent state. impl RefUnwindSafe for Coordinate {} impl Default for Storage { fn default() -> Self { Self::new(None) } } impl Storage { /// Create a new database storage. /// /// The `event_callback` function is invoked by the salsa runtime at various points during execution. pub fn new(event_callback: Option>) -> Self { Self { handle: StorageHandle::new(event_callback), zalsa_local: ZalsaLocal::new(), } } /// Returns a builder for database storage. pub fn builder() -> StorageBuilder { StorageBuilder::default() } /// Convert this instance of [`Storage`] into a [`StorageHandle`]. /// /// This will discard the local state of this [`Storage`], thereby returning a value that /// is both [`Sync`] and [`std::panic::UnwindSafe`]. pub fn into_zalsa_handle(mut self) -> StorageHandle { self.zalsa_local .record_unfilled_pages(self.handle.zalsa_impl.table()); let Self { handle, zalsa_local, } = &mut self; // Avoid rust's annoying destructure prevention rules for `Drop` types // SAFETY: We forget `Self` afterwards to discard the original copy, and the destructure // above makes sure we won't forget to take into account newly added fields. let handle = unsafe { std::ptr::read(handle) }; // SAFETY: We forget `Self` afterwards to discard the original copy, and the destructure // above makes sure we won't forget to take into account newly added fields. unsafe { std::ptr::drop_in_place(zalsa_local) }; std::mem::forget::(self); handle } // ANCHOR: cancel_other_workers /// Sets cancellation flag and blocks until all other workers with access /// to this storage have completed. /// /// This could deadlock if there is a single worker with two handles to the /// same database! /// /// Needs to be paired with a call to `reset_cancellation_flag`. fn cancel_others(&mut self) -> &mut Zalsa { debug_assert!( self.zalsa_local .try_with_query_stack(|stack| stack.is_empty()) == Some(true), "attempted to cancel within query computation, this is a deadlock" ); self.handle.zalsa_impl.runtime().set_cancellation_flag(); self.handle .zalsa_impl .event(&|| Event::new(EventKind::DidSetCancellationFlag)); let mut clones = self.handle.coordinate.clones.lock(); while *clones != 1 { clones = self.handle.coordinate.cvar.wait(clones); } // The ref count on the `Arc` should now be 1 let zalsa = Arc::get_mut(&mut self.handle.zalsa_impl).unwrap(); // cancellation is done, so reset the flag zalsa.runtime_mut().reset_cancellation_flag(); zalsa } // ANCHOR_END: cancel_other_workers } /// A builder for a [`Storage`] instance. /// /// This type can be created with the [`Storage::builder`] function. pub struct StorageBuilder { jars: Vec, event_callback: Option>, _db: PhantomData, } impl Default for StorageBuilder { fn default() -> Self { Self { jars: Vec::new(), event_callback: None, _db: PhantomData, } } } impl StorageBuilder { /// Set a callback for salsa events. /// /// The `event_callback` function will be invoked by the salsa runtime at various points during execution. pub fn event_callback( mut self, callback: Box, ) -> Self { self.event_callback = Some(callback); self } /// Manually register an ingredient. /// /// Manual ingredient registration is necessary when the `inventory` feature is disabled. pub fn ingredient(mut self) -> Self { self.jars.push(ErasedJar::erase::()); self } /// Construct the [`Storage`] using the provided builder options. pub fn build(self) -> Storage { Storage { handle: StorageHandle::with_jars(self.event_callback, self.jars), zalsa_local: ZalsaLocal::new(), } } } #[allow(clippy::undocumented_unsafe_blocks)] // TODO(#697) document safety unsafe impl ZalsaDatabase for T { #[inline(always)] fn zalsa(&self) -> &Zalsa { &self.storage().handle.zalsa_impl } fn zalsa_mut(&mut self) -> &mut Zalsa { self.storage_mut().cancel_others() } #[inline(always)] fn zalsa_local(&self) -> &ZalsaLocal { &self.storage().zalsa_local } } impl Clone for Storage { fn clone(&self) -> Self { Self { handle: self.handle.clone(), zalsa_local: ZalsaLocal::new(), } } } struct CoordinateDrop(Arc); impl std::ops::Deref for CoordinateDrop { type Target = Arc; fn deref(&self) -> &Self::Target { &self.0 } } impl Drop for CoordinateDrop { fn drop(&mut self) { *self.0.clones.lock() -= 1; self.0.cvar.notify_all(); } } salsa-0.26.2/src/sync.rs000064400000000000000000000102621046102023000131300ustar 00000000000000pub use shim::*; #[cfg(feature = "shuttle")] pub mod shim { pub use shuttle::sync::*; pub use shuttle::{thread, thread_local}; /// A wrapper around shuttle's `Mutex` to mirror parking-lot's API. #[derive(Default, Debug)] pub struct Mutex(shuttle::sync::Mutex); impl Mutex { pub const fn new(value: T) -> Mutex { Mutex(shuttle::sync::Mutex::new(value)) } pub fn lock(&self) -> MutexGuard<'_, T> { self.0.lock().unwrap() } pub fn get_mut(&mut self) -> &mut T { self.0.get_mut().unwrap() } } /// A wrapper around shuttle's `Condvar` to mirror parking-lot's API. #[derive(Default, Debug)] pub struct Condvar(shuttle::sync::Condvar); impl Condvar { // We cannot match parking-lot identically because shuttle's version takes ownership of the `MutexGuard`. pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> { self.0.wait(guard).unwrap() } pub fn notify_one(&self) { self.0.notify_one(); } pub fn notify_all(&self) { self.0.notify_all(); } } use std::cell::UnsafeCell; use std::mem::MaybeUninit; /// A polyfill for `std::sync::OnceLock`. pub struct OnceLock(Mutex, UnsafeCell>); impl Default for OnceLock { fn default() -> Self { OnceLock::new() } } impl OnceLock { pub const fn new() -> OnceLock { OnceLock(Mutex::new(false), UnsafeCell::new(MaybeUninit::uninit())) } pub fn get(&self) -> Option<&T> { let initialized = self.0.lock(); if *initialized { // SAFETY: The value is initialized and write-once. Some(unsafe { (*self.1.get()).assume_init_ref() }) } else { None } } pub fn get_or_init(&self, f: F) -> &T where F: FnOnce() -> T, { let _ = self.set_with(f); self.get().unwrap() } fn set_with(&self, f: F) -> Result<(), F> where F: FnOnce() -> T, { let mut initialized = self.0.lock(); if *initialized { return Err(f); } // SAFETY: We hold the lock. unsafe { self.1.get().write(MaybeUninit::new(f())) } *initialized = true; Ok(()) } } impl From for OnceLock { fn from(value: T) -> OnceLock { OnceLock(Mutex::new(true), UnsafeCell::new(MaybeUninit::new(value))) } } // SAFETY: Mirroring `std::sync::OnceLock`. unsafe impl Send for OnceLock {} // SAFETY: Mirroring `std::sync::OnceLock`. unsafe impl Sync for OnceLock {} } #[cfg(not(feature = "shuttle"))] pub mod shim { pub use parking_lot::{Mutex, MutexGuard}; pub use std::sync::*; pub use std::{thread, thread_local}; pub mod atomic { pub use portable_atomic::AtomicU64; pub use std::sync::atomic::*; } /// A wrapper around parking-lot's `Condvar` to mirror shuttle's API. pub struct Condvar(parking_lot::Condvar); // this is not derived because it confuses rust-analyzer ... https://github.com/rust-lang/rust-analyzer/issues/19755 #[allow(clippy::derivable_impls)] impl Default for Condvar { fn default() -> Self { Self(Default::default()) } } // this is not derived because it confuses rust-analyzer ... https://github.com/rust-lang/rust-analyzer/issues/19755 impl std::fmt::Debug for Condvar { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Condvar").field(&self.0).finish() } } impl Condvar { pub fn wait<'a, T>(&self, mut guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> { self.0.wait(&mut guard); guard } pub fn notify_one(&self) { self.0.notify_one(); } pub fn notify_all(&self) { self.0.notify_all(); } } } salsa-0.26.2/src/table/memo.rs000064400000000000000000000277361046102023000142160ustar 00000000000000use std::any::{Any, TypeId}; use std::fmt::Debug; use std::mem; use std::ptr::{self, NonNull}; use crate::DatabaseKeyIndex; use crate::sync::atomic::{AtomicPtr, Ordering}; use crate::zalsa::MemoIngredientIndex; use crate::zalsa::Zalsa; /// The "memo table" stores the memoized results of tracked function calls. /// Every tracked function must take a salsa struct as its first argument /// and memo tables are attached to those salsa structs as auxiliary data. pub struct MemoTable { memos: Box<[MemoEntry]>, } impl MemoTable { /// Create a `MemoTable` with slots for memos from the provided `MemoTableTypes`. /// /// # Safety /// /// The created memo table must only be accessed with the same `MemoTableTypes`. pub unsafe fn new(types: &MemoTableTypes) -> Self { // Note that the safety invariant guarantees that any indices in-bounds for // this table are also in-bounds for its `MemoTableTypes`, as `MemoTableTypes` // is append-only. Self { memos: (0..types.len()).map(|_| MemoEntry::default()).collect(), } } /// Reset any memos in the table. /// /// Note that the memo entries should be freed manually before calling this function. pub fn reset(&mut self) { for memo in &mut self.memos { *memo = MemoEntry::default(); } } } pub trait Memo: Any + Send + Sync { /// Removes the outputs that were created when this query ran. This includes /// tracked structs and specified queries. fn remove_outputs(&self, zalsa: &Zalsa, executor: DatabaseKeyIndex); /// Returns memory usage information about the memoized value. #[cfg(feature = "salsa_unstable")] fn memory_usage(&self) -> crate::database::MemoInfo; } /// Data for a memoized entry. /// This is a type-erased `Box`, where `M` is the type of memo associated /// with that particular ingredient index. /// /// # Implementation note /// /// Every entry is associated with some ingredient that has been added to the database. /// That ingredient has a fixed type of values that it produces etc. /// Therefore, once a given entry goes from `Empty` to `Full`, /// the type-id associated with that entry should never change. /// /// We take advantage of this and use an `AtomicPtr` to store the actual memo. /// This allows us to store into the memo-entry without acquiring a write-lock. /// However, using `AtomicPtr` means we cannot use a `Box` or any other wide pointer. /// Therefore, we hide the type by transmuting to `DummyMemo`; but we must then be very careful /// when freeing `MemoEntryData` values to transmute things back. See the `Drop` impl for /// [`MemoEntry`][] for details. #[derive(Default, Debug)] struct MemoEntry { /// An [`AtomicPtr`][] to a `Box` for the erased memo type `M` atomic_memo: AtomicPtr, } #[derive(Clone, Copy, Debug)] pub struct MemoEntryType { /// The `type_id` of the erased memo type `M` type_id: TypeId, /// A type-coercion function for the erased memo type `M` to_dyn_fn: fn(NonNull) -> NonNull, } impl MemoEntryType { fn to_dummy(memo: NonNull) -> NonNull { memo.cast() } unsafe fn from_dummy(memo: NonNull) -> NonNull { memo.cast() } const fn to_dyn_fn() -> fn(NonNull) -> NonNull { let f: fn(NonNull) -> NonNull = |x| x; // SAFETY: `M: Sized` and `DummyMemo: Sized`, as such they are ABI compatible behind a // `NonNull` making it safe to do type erasure. unsafe { mem::transmute::< fn(NonNull) -> NonNull, fn(NonNull) -> NonNull, >(f) } } #[inline] pub fn of() -> Self { Self { type_id: TypeId::of::(), to_dyn_fn: Self::to_dyn_fn::(), } } } /// Dummy placeholder type that we use when erasing the memo type `M` in [`MemoEntryData`][]. #[derive(Debug)] struct DummyMemo; impl Memo for DummyMemo { fn remove_outputs(&self, _zalsa: &Zalsa, _executor: DatabaseKeyIndex) {} #[cfg(feature = "salsa_unstable")] fn memory_usage(&self) -> crate::database::MemoInfo { crate::database::MemoInfo { debug_name: "dummy", output: crate::database::SlotInfo { debug_name: "dummy", size_of_metadata: 0, size_of_fields: 0, heap_size_of_fields: None, memos: Vec::new(), }, } } } #[derive(Default)] pub struct MemoTableTypes { types: Vec, } impl MemoTableTypes { pub(crate) fn set( &mut self, memo_ingredient_index: MemoIngredientIndex, memo_type: MemoEntryType, ) { self.types .insert(memo_ingredient_index.as_usize(), memo_type); } pub fn len(&self) -> usize { self.types.len() } /// # Safety /// /// The types table must be the correct one of `memos`. #[inline] pub(crate) unsafe fn attach_memos<'a>( &'a self, memos: &'a MemoTable, ) -> MemoTableWithTypes<'a> { MemoTableWithTypes { types: self, memos } } /// # Safety /// /// The types table must be the correct one of `memos`. #[inline] pub(crate) unsafe fn attach_memos_mut<'a>( &'a self, memos: &'a mut MemoTable, ) -> MemoTableWithTypesMut<'a> { MemoTableWithTypesMut { types: self, memos } } } pub struct MemoTableWithTypes<'a> { types: &'a MemoTableTypes, memos: &'a MemoTable, } impl MemoTableWithTypes<'_> { pub(crate) fn insert( self, memo_ingredient_index: MemoIngredientIndex, memo: NonNull, ) -> Option> { let MemoEntry { atomic_memo } = self.memos.memos.get(memo_ingredient_index.as_usize())?; // SAFETY: Any indices that are in-bounds for the `MemoTable` are also in-bounds for its // corresponding `MemoTableTypes`, by construction. let type_ = unsafe { self.types .types .get_unchecked(memo_ingredient_index.as_usize()) }; // Verify that the we are casting to the correct type. if type_.type_id != TypeId::of::() { type_assert_failed(memo_ingredient_index); } let old_memo = atomic_memo.swap(MemoEntryType::to_dummy(memo).as_ptr(), Ordering::AcqRel); // SAFETY: We asserted that the type is correct above. NonNull::new(old_memo).map(|old_memo| unsafe { MemoEntryType::from_dummy(old_memo) }) } /// Returns a pointer to the memo at the given index, if one has been inserted. #[inline] pub(crate) fn get( self, memo_ingredient_index: MemoIngredientIndex, ) -> Option> { let MemoEntry { atomic_memo } = self.memos.memos.get(memo_ingredient_index.as_usize())?; // SAFETY: Any indices that are in-bounds for the `MemoTable` are also in-bounds for its // corresponding `MemoTableTypes`, by construction. let type_ = unsafe { self.types .types .get_unchecked(memo_ingredient_index.as_usize()) }; // Verify that the we are casting to the correct type. if type_.type_id != TypeId::of::() { type_assert_failed(memo_ingredient_index); } NonNull::new(atomic_memo.load(Ordering::Acquire)) // SAFETY: We asserted that the type is correct above. .map(|memo| unsafe { MemoEntryType::from_dummy(memo) }) } #[cfg(feature = "salsa_unstable")] pub(crate) fn memory_usage(&self) -> Vec { let mut memory_usage = Vec::new(); for (index, memo) in self.memos.memos.iter().enumerate() { let Some(memo) = NonNull::new(memo.atomic_memo.load(Ordering::Acquire)) else { continue; }; let Some(type_) = self.types.types.get(index) else { continue; }; // SAFETY: The `TypeId` is asserted in `insert()`. let dyn_memo: &dyn Memo = unsafe { (type_.to_dyn_fn)(memo).as_ref() }; memory_usage.push(dyn_memo.memory_usage()); } memory_usage } } pub(crate) struct MemoTableWithTypesMut<'a> { types: &'a MemoTableTypes, memos: &'a mut MemoTable, } impl MemoTableWithTypesMut<'_> { /// Calls `f` on the memo at `memo_ingredient_index`. /// /// If the memo is not present, `f` is not called. pub(crate) fn map_memo( self, memo_ingredient_index: MemoIngredientIndex, f: impl FnOnce(&mut M), ) { let Some(MemoEntry { atomic_memo }) = self.memos.memos.get_mut(memo_ingredient_index.as_usize()) else { return; }; // SAFETY: Any indices that are in-bounds for the `MemoTable` are also in-bounds for its // corresponding `MemoTableTypes`, by construction. let type_ = unsafe { self.types .types .get_unchecked(memo_ingredient_index.as_usize()) }; // Verify that the we are casting to the correct type. if type_.type_id != TypeId::of::() { type_assert_failed(memo_ingredient_index); } let Some(memo) = NonNull::new(*atomic_memo.get_mut()) else { return; }; // SAFETY: We asserted that the type is correct above. f(unsafe { MemoEntryType::from_dummy(memo).as_mut() }); } /// To drop an entry, we need its type, so we don't implement `Drop`, and instead have this method. /// /// Note that calling this multiple times is safe, dropping an uninitialized entry is a no-op. /// /// # Safety /// /// The caller needs to make sure to not call this function until no more references into /// the database exist as there may be outstanding borrows into the pointer contents. #[inline] pub unsafe fn drop(&mut self) { let types = self.types.types.iter(); for (type_, memo) in std::iter::zip(types, &mut self.memos.memos) { // SAFETY: The types match as per our constructor invariant. unsafe { memo.take(type_) }; } } /// # Safety /// /// The caller needs to make sure to not call this function until no more references into /// the database exist as there may be outstanding borrows into the pointer contents. pub(crate) unsafe fn take_memos( &mut self, mut f: impl FnMut(MemoIngredientIndex, Box), ) { self.memos .memos .iter_mut() .zip(self.types.types.iter()) .enumerate() .filter_map(|(index, (memo, type_))| { // SAFETY: The types match as per our constructor invariant. let memo = unsafe { memo.take(type_)? }; Some((MemoIngredientIndex::from_usize(index), memo)) }) .for_each(|(index, memo)| f(index, memo)); } } /// This function is explicitly outlined to avoid debug machinery in the hot-path. #[cold] #[inline(never)] fn type_assert_failed(memo_ingredient_index: MemoIngredientIndex) -> ! { panic!("inconsistent type-id for `{memo_ingredient_index:?}`") } impl MemoEntry { /// # Safety /// /// The type must match. #[inline] unsafe fn take(&mut self, type_: &MemoEntryType) -> Option> { let memo = mem::replace(self.atomic_memo.get_mut(), ptr::null_mut()); let memo = NonNull::new(memo)?; // SAFETY: Our preconditions. Some(unsafe { Box::from_raw((type_.to_dyn_fn)(memo).as_ptr()) }) } } impl Drop for DummyMemo { fn drop(&mut self) { unreachable!("should never get here") } } impl std::fmt::Debug for MemoTable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("MemoTable").finish_non_exhaustive() } } salsa-0.26.2/src/table.rs000064400000000000000000000444411046102023000132510ustar 00000000000000use std::alloc::Layout; use std::any::{Any, TypeId}; use std::cell::UnsafeCell; use std::marker::PhantomData; use std::mem::{self, MaybeUninit}; use std::ptr::{self, NonNull}; use std::slice; use memo::MemoTable; use rustc_hash::FxHashMap; use crate::sync::atomic::{AtomicUsize, Ordering}; use crate::sync::{Arc, Mutex}; use crate::table::memo::{MemoTableTypes, MemoTableWithTypes, MemoTableWithTypesMut}; use crate::{Id, IngredientIndex, Revision}; pub(crate) mod memo; const PAGE_LEN_BITS: usize = 10; const PAGE_LEN_MASK: usize = PAGE_LEN - 1; const PAGE_LEN: usize = 1 << PAGE_LEN_BITS; const MAX_PAGES: usize = 1 << (u32::BITS as usize - PAGE_LEN_BITS); /// A typed [`Page`] view. pub(crate) struct PageView<'p, T: Slot>(&'p Page, PhantomData<&'p T>); pub struct Table { pages: boxcar::Vec, /// Map from ingredient to non-full pages that are up for grabs non_full_pages: Mutex>>, } /// # Safety /// /// Implementors of this trait need to make sure that their type is unique with respect to /// their owning ingredient as the allocation strategy relies on this. pub unsafe trait Slot: Any + Send + Sync { /// Access the [`MemoTable`][] for this slot. /// /// # Safety condition /// /// The current revision MUST be the current revision of the database containing this slot. unsafe fn memos(slot: *const Self, current_revision: Revision) -> *const MemoTable; /// Mutably access the [`MemoTable`] for this slot. fn memos_mut(&mut self) -> &mut MemoTable; } /// [Slot::memos] type SlotMemosFnErased = unsafe fn(*const (), current_revision: Revision) -> *const MemoTable; /// [Slot::memos] type SlotMemosFn = unsafe fn(*const T, current_revision: Revision) -> *const MemoTable; /// [Slot::memos_mut] type SlotMemosMutFnErased = unsafe fn(*mut ()) -> *mut MemoTable; /// [Slot::memos_mut] type SlotMemosMutFn = fn(&mut T) -> &mut MemoTable; struct SlotVTable { layout: Layout, /// [`Slot`] methods memos: SlotMemosFnErased, memos_mut: SlotMemosMutFnErased, /// The type name of what is stored as entries in data. type_name: fn() -> &'static str, /// A drop impl to call when the own page drops /// SAFETY: The caller is required to supply a valid pointer to a `Box>`, and /// the correct initialized length and memo types. drop_impl: unsafe fn(data: *mut (), initialized: usize, memo_types: &MemoTableTypes), } impl SlotVTable { const fn of() -> &'static Self { const { &Self { drop_impl: |data, initialized, memo_types| { // SAFETY: The caller is required to provide a valid data pointer. let data = unsafe { Box::from_raw(data.cast::>()) }; for i in 0..initialized { let item = data[i].get().cast::(); // SAFETY: The caller is required to provide a valid initialized length. unsafe { memo_types.attach_memos_mut((*item).memos_mut()).drop(); ptr::drop_in_place(item); } } }, layout: Layout::new::(), type_name: std::any::type_name::, // SAFETY: The signatures are ABI-compatible. memos: unsafe { mem::transmute::, SlotMemosFnErased>(T::memos) }, // SAFETY: The signatures are ABI-compatible. memos_mut: unsafe { mem::transmute::, SlotMemosMutFnErased>(T::memos_mut) }, } } } } type PageDataEntry = UnsafeCell>; type PageData = [PageDataEntry; PAGE_LEN]; struct Page { /// The ingredient for elements on this page. ingredient: IngredientIndex, /// Number of elements of `data` that are initialized. allocated: AtomicUsize, /// The potentially uninitialized data of this page. As we initialize new entries, we increment `allocated`. /// This is a box allocated `PageData` data: NonNull<()>, /// A vtable for the slot type stored in this page. slot_vtable: &'static SlotVTable, /// The type id of what is stored as entries in data. // FIXME: Move this into SlotVTable once const stable slot_type_id: TypeId, memo_types: Arc, } // SAFETY: `Page` is `Send` as we make sure to only ever store `Slot` types in it which // requires `Send`.` unsafe impl Send for Page /* where for M: Send */ {} // SAFETY: `Page` is `Sync` as we make sure to only ever store `Slot` types in it which // requires `Sync`.` unsafe impl Sync for Page /* where for M: Sync */ {} #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct PageIndex(usize); impl PageIndex { #[inline] fn new(idx: usize) -> Self { debug_assert!(idx < MAX_PAGES); Self(idx) } #[allow(dead_code)] pub fn as_usize(&self) -> usize { self.0 } } #[derive(Copy, Clone, Debug)] pub struct SlotIndex(usize); impl SlotIndex { #[inline] fn new(idx: usize) -> Self { debug_assert!(idx < PAGE_LEN); Self(idx) } } impl Default for Table { fn default() -> Self { Self { pages: boxcar::Vec::new(), non_full_pages: Default::default(), } } } impl Table { /// Returns the [`IngredientIndex`] for an [`Id`]. #[inline] pub fn ingredient_index(&self, id: Id) -> IngredientIndex { let (page_idx, _) = split_id(id); self.pages[page_idx.0].ingredient } /// Get a reference to the data for `id`, which must have been allocated from this table with type `T`. /// /// # Panics /// /// If `id` is out of bounds or the does not have the type `T`. pub(crate) fn get(&self, id: Id) -> &T { let (page, slot) = split_id(id); let page_ref = self.page::(page); &page_ref.data()[slot.0] } /// Get a raw pointer to the data for `id`, which must have been allocated from this table. /// /// # Panics /// /// If `id` is out of bounds or the does not have the type `T`. /// /// # Safety /// /// See [`Page::get_raw`][]. pub(crate) fn get_raw(&self, id: Id) -> *mut T { let (page, slot) = split_id(id); let page_ref = self.page::(page); page_ref.page_data()[slot.0].get().cast::() } /// Returns the number of pages that have been allocated. pub fn page_count(&self) -> usize { self.pages.count() } /// Gets a reference to the page which has slots of type `T` /// /// # Panics /// /// If `page` is out of bounds or the type `T` is incorrect. #[inline] pub(crate) fn page(&self, page: PageIndex) -> PageView<'_, T> { self.pages[page.0].assert_type::() } /// Force initialize the page at the given index. /// /// If the page at the provided index was created using `push_uninit_page`, it /// will be initialized using the provided ingredient data. /// /// Otherwise, the page will be allocated. /// /// # Panics /// /// If `page` is out of bounds or the type `T` is incorrect. #[inline] #[allow(dead_code)] pub(crate) fn force_page( &mut self, page_idx: PageIndex, ingredient: IngredientIndex, memo_types: &Arc, ) { let page = self.pages.get_mut(page_idx.0); match page { Some(page) => { // Initialize the page if was created using `push_uninit_page`. if page.slot_type_id == TypeId::of::() { *page = Page::new::(ingredient, memo_types.clone()); } // Ensure the page has the correct type. page.assert_type::(); } None => { // Create dummy pages until we reach the page we want. while self.page_count() < page_idx.as_usize() { // We make sure not to claim any intermediary pages for ourselves, as they may // be required by a different ingredient when it is deserialized. self.push_uninit_page(); } let allocated_idx = self.push_page::(ingredient, memo_types.clone()); assert_eq!( allocated_idx, page_idx, "allocated index does not match requested index" ); } }; } /// Allocate a new page for the given ingredient and with slots of type `T` #[inline] pub(crate) fn push_page( &self, ingredient: IngredientIndex, memo_types: Arc, ) -> PageIndex { PageIndex::new(self.pages.push(Page::new::(ingredient, memo_types))) } /// Allocate an uninitialized page. #[inline] #[allow(dead_code)] pub(crate) fn push_uninit_page(&self) -> PageIndex { // Note that `DummySlot` is a ZST, so the memory wasted by any pages of ingredients // that were not serialized should be negligible. PageIndex::new(self.pages.push(Page::new::( IngredientIndex::new(0), Arc::new(MemoTableTypes::default()), ))) } /// Get the memo table associated with `id` for the concrete type `T`. /// /// # Safety /// /// The parameter `current_revision` must be the current revision of the database /// owning this table. /// /// # Panics /// /// If `page` is out of bounds or the type `T` is incorrect. pub unsafe fn memos( &self, id: Id, current_revision: Revision, ) -> MemoTableWithTypes<'_> { let (page, slot) = split_id(id); let page = self.pages[page.0].assert_type::(); let slot = &page.data()[slot.0]; // SAFETY: The caller is required to pass the `current_revision`. let memos = unsafe { &*T::memos(slot, current_revision) }; // SAFETY: The `Page` keeps the correct memo types. unsafe { page.0.memo_types.attach_memos(memos) } } /// Get the memo table associated with `id`. /// /// Unlike `Table::memos`, this does not require a concrete type, and instead uses dynamic /// dispatch. /// /// # Safety /// /// The parameter `current_revision` must be the current revision of the owner of database /// owning this table. pub unsafe fn dyn_memos(&self, id: Id, current_revision: Revision) -> MemoTableWithTypes<'_> { let (page, slot) = split_id(id); let page = &self.pages[page.0]; // SAFETY: We supply a proper slot pointer and the caller is required to pass the `current_revision`. let memos = unsafe { &*(page.slot_vtable.memos)(page.get(slot), current_revision) }; // SAFETY: The `Page` keeps the correct memo types. unsafe { page.memo_types.attach_memos(memos) } } /// Get the memo table associated with `id` pub(crate) fn memos_mut(&mut self, id: Id) -> MemoTableWithTypesMut<'_> { let (page, slot) = split_id(id); let page_index = page.0; let page = self .pages .get_mut(page_index) .unwrap_or_else(|| panic!("index `{page_index}` is uninitialized")); // SAFETY: We supply a proper slot pointer and the caller is required to pass the `current_revision`. let memos = unsafe { &mut *(page.slot_vtable.memos_mut)(page.get(slot)) }; // SAFETY: The `Page` keeps the correct memo types. unsafe { page.memo_types.attach_memos_mut(memos) } } pub(crate) fn slots_of(&self) -> impl Iterator + '_ { self.pages .iter() .filter_map(|(page_index, page)| Some((page_index, page.cast_type::()?))) .flat_map(move |(page_index, view)| { view.data() .iter() .enumerate() .map(move |(slot_index, value)| { let id = make_id(PageIndex::new(page_index), SlotIndex::new(slot_index)); (id, value) }) }) } #[cold] #[inline(never)] pub(crate) fn fetch_or_push_page( &self, ingredient: IngredientIndex, memo_types: impl FnOnce() -> Arc, ) -> PageIndex { if let Some(page) = self .non_full_pages .lock() .get_mut(&ingredient) .and_then(Vec::pop) { return page; } self.push_page::(ingredient, memo_types()) } pub(crate) fn record_unfilled_page(&self, ingredient: IngredientIndex, page: PageIndex) { self.non_full_pages .lock() .entry(ingredient) .or_default() .push(page); } } impl<'db, T: Slot> PageView<'db, T> { #[inline] fn page_data(&self) -> &'db [PageDataEntry] { let len = self.0.allocated.load(Ordering::Acquire); // SAFETY: `len` is the initialized length of the page unsafe { slice::from_raw_parts(self.0.data.cast::>().as_ptr(), len) } } #[inline] fn data(&self) -> &'db [T] { let len = self.0.allocated.load(Ordering::Acquire); // SAFETY: `len` is the initialized length of the page unsafe { slice::from_raw_parts(self.0.data.cast::().as_ptr(), len) } } /// Allocate a value in this page. /// /// # Safety /// /// The caller must be the unique writer to this page, i.e. `allocate` cannot be called /// concurrently by multiple threads. Concurrent readers however, are fine. #[inline] pub(crate) unsafe fn allocate(&self, page: PageIndex, value: V) -> Result<(Id, &'db T), V> where V: FnOnce(Id) -> T, { let index = self.0.allocated.load(Ordering::Acquire); if index >= PAGE_LEN { return Err(value); } // Initialize entry `index` let id = make_id(page, SlotIndex::new(index)); let data = self.0.data.cast::>(); // SAFETY: `index` is also guaranteed to be in bounds as per the check above. let entry = unsafe { &*data.as_ptr().add(index) }; // SAFETY: The caller guarantees we are the unique writer, and readers will not attempt to // access this index until we have updated the length. unsafe { (*entry.get()).write(value(id)) }; // SAFETY: We just initialized the value above. let value = unsafe { (*entry.get()).assume_init_ref() }; // Update the length now that we have initialized the value. self.0.allocated.store(index + 1, Ordering::Release); Ok((id, value)) } } impl Page { #[inline] fn new(ingredient: IngredientIndex, memo_types: Arc) -> Self { #[cfg(not(feature = "shuttle"))] let data: Box> = Box::new([const { UnsafeCell::new(MaybeUninit::uninit()) }; PAGE_LEN]); #[cfg(feature = "shuttle")] let data = { // Avoid stack overflows when using larger shuttle types. let data = (0..PAGE_LEN) .map(|_| UnsafeCell::new(MaybeUninit::uninit())) .collect::]>>(); let data: *mut [PageDataEntry] = Box::into_raw(data); // SAFETY: `*mut PageDataEntry` and `*mut [PageDataEntry; N]` have the same layout. unsafe { Box::from_raw(data.cast::>().cast::>()) } }; Self { ingredient, memo_types, slot_vtable: SlotVTable::of::(), slot_type_id: TypeId::of::(), allocated: AtomicUsize::new(0), data: NonNull::from(Box::leak(data)).cast::<()>(), } } /// Retrieves the pointer for the given slot. /// /// # Panics /// /// If slot is out of bounds fn get(&self, slot: SlotIndex) -> *mut () { let len = self.allocated.load(Ordering::Acquire); assert!( slot.0 < len, "out of bounds access `{slot:?}` (maximum slot `{len}`)" ); // SAFETY: We have checked that the resulting pointer will be within bounds. unsafe { self.data .as_ptr() .byte_add(slot.0 * self.slot_vtable.layout.size()) } } #[inline] fn assert_type(&self) -> PageView<'_, T> { if self.slot_type_id != TypeId::of::() { type_assert_failed::(self); } PageView(self, PhantomData) } fn cast_type(&self) -> Option> { if self.slot_type_id == TypeId::of::() { Some(PageView(self, PhantomData)) } else { None } } } /// This function is explicitly outlined to avoid debug machinery in the hot-path. #[cold] #[inline(never)] fn type_assert_failed(page: &Page) -> ! { panic!( "page has slot type `{:?}` but `{:?}` was expected", (page.slot_vtable.type_name)(), std::any::type_name::(), ) } impl Drop for Page { fn drop(&mut self) { let len = *self.allocated.get_mut(); // SAFETY: We supply the data pointer and the initialized length unsafe { (self.slot_vtable.drop_impl)(self.data.as_ptr(), len, &self.memo_types) }; } } /// A placeholder type representing the slots of an uninitialized `Page`. struct DummySlot; // SAFETY: The `DummySlot type is private. unsafe impl Slot for DummySlot { unsafe fn memos(_: *const Self, _: Revision) -> *const MemoTable { unreachable!() } fn memos_mut(&mut self) -> &mut MemoTable { unreachable!() } } fn make_id(page: PageIndex, slot: SlotIndex) -> Id { let page = page.0 as u32; let slot = slot.0 as u32; // SAFETY: `slot` is guaranteed to be small enough that the resulting Id won't be bigger than `Id::MAX_U32` unsafe { Id::from_index((page << PAGE_LEN_BITS) | slot) } } #[inline] pub fn split_id(id: Id) -> (PageIndex, SlotIndex) { let index = id.index() as usize; let slot = index & PAGE_LEN_MASK; let page = index >> PAGE_LEN_BITS; (PageIndex::new(page), SlotIndex::new(slot)) } salsa-0.26.2/src/tracing.rs000064400000000000000000000027721046102023000136120ustar 00000000000000//! Wrappers around `tracing` macros that avoid inlining debug machinery into the hot path, //! as tracing events are typically only enabled for debugging purposes. macro_rules! trace { ($($x:tt)*) => { crate::tracing::event!(TRACE, $($x)*) }; } macro_rules! warn_event { ($($x:tt)*) => { crate::tracing::event!(WARN, $($x)*) }; } macro_rules! info { ($($x:tt)*) => { crate::tracing::event!(INFO, $($x)*) }; } macro_rules! debug { ($($x:tt)*) => { crate::tracing::event!(DEBUG, $($x)*) }; } macro_rules! debug_span { ($($x:tt)*) => { crate::tracing::span!(DEBUG, $($x)*) }; } #[expect(unused_macros)] macro_rules! info_span { ($($x:tt)*) => { crate::tracing::span!(INFO, $($x)*) }; } macro_rules! event { ($level:ident, $($x:tt)*) => {{ let event = { #[cold] #[inline(never)] || { ::tracing::event!(::tracing::Level::$level, $($x)*) } }; if ::tracing::enabled!(::tracing::Level::$level) { event(); } }}; } macro_rules! span { ($level:ident, $($x:tt)*) => {{ let span = { #[cold] #[inline(never)] || { ::tracing::span!(::tracing::Level::$level, $($x)*) } }; if ::tracing::enabled!(::tracing::Level::$level) { span() } else { ::tracing::Span::none() } }}; } #[expect(unused_imports)] pub(crate) use {debug, debug_span, event, info, info_span, span, trace, warn_event as warn}; salsa-0.26.2/src/tracked_struct/tracked_field.rs000064400000000000000000000105061046102023000177560ustar 00000000000000use std::marker::PhantomData; use crate::function::VerifyResult; use crate::hash::{FxHashSet, FxIndexSet}; use crate::ingredient::Ingredient; use crate::sync::Arc; use crate::table::memo::MemoTableTypes; use crate::tracked_struct::{Configuration, Value}; use crate::zalsa::{IngredientIndex, JarKind, Zalsa}; use crate::zalsa_local::QueryEdge; use crate::{DatabaseKeyIndex, Id}; /// Created for each tracked struct. /// /// This ingredient only stores the "id" fields. /// It is a kind of "dressed up" interner; /// the active query + values of id fields are hashed to create the tracked struct id. /// The value fields are stored in [`crate::function::IngredientImpl`] instances keyed by the tracked struct id. /// Unlike normal interners, tracked struct indices can be deleted and reused aggressively: /// when a tracked function re-executes, /// any tracked structs that it created before but did not create this time can be deleted. pub struct FieldIngredientImpl where C: Configuration, { /// Index of this ingredient in the database (used to construct database-ids, etc). ingredient_index: IngredientIndex, /// The index of this field on the tracked struct relative to all other tracked fields. field_index: usize, phantom: PhantomData Value>, } impl FieldIngredientImpl where C: Configuration, { pub(super) fn new(field_index: usize, ingredient_index: IngredientIndex) -> Self { Self { field_index, ingredient_index, phantom: PhantomData, } } fn database_key_index(&self, id: Id) -> DatabaseKeyIndex { DatabaseKeyIndex::new(self.ingredient_index, id) } } impl Ingredient for FieldIngredientImpl where C: Configuration, { fn location(&self) -> &'static crate::ingredient::Location { &C::LOCATION } fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index } unsafe fn maybe_changed_after( &self, zalsa: &crate::zalsa::Zalsa, _db: crate::database::RawDatabase<'_>, input: Id, revision: crate::Revision, ) -> VerifyResult { let data = >::data_raw(zalsa.table(), input); let field_changed_at = unsafe { (&(*data).revisions)[self.field_index].load() }; VerifyResult::changed_if(field_changed_at > revision) } fn collect_minimum_serialized_edges( &self, _zalsa: &Zalsa, _edge: QueryEdge, _serialized_edges: &mut FxIndexSet, _visited_edges: &mut FxHashSet, ) { // Tracked fields do not have transitive dependencies, and their dependencies are covered by // the base inputs. } fn flatten_cycle_head_dependencies( &self, _zalsa: &Zalsa, id: Id, flattened_input_outputs: &mut FxIndexSet, _seen: &mut FxHashSet, ) { flattened_input_outputs.insert(QueryEdge::input(self.database_key_index(id))); } fn fmt_index(&self, index: crate::Id, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( fmt, "{}.{}({:?})", C::DEBUG_NAME, C::TRACKED_FIELD_NAMES[self.field_index], index ) } fn debug_name(&self) -> &'static str { C::TRACKED_FIELD_NAMES[self.field_index] } fn jar_kind(&self) -> JarKind { JarKind::Struct } fn memo_table_types(&self) -> &Arc { unreachable!("tracked field does not allocate pages") } fn memo_table_types_mut(&mut self) -> &mut Arc { unreachable!("tracked field does not allocate pages") } fn is_persistable(&self) -> bool { // Tracked field dependencies are valid as long as the tracked struct is persistable. C::PERSIST } fn should_serialize(&self, _zalsa: &Zalsa) -> bool { // However, they are never serialized directly. false } } impl std::fmt::Debug for FieldIngredientImpl where C: Configuration, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct(std::any::type_name::()) .field("ingredient_index", &self.ingredient_index) .field("field_index", &self.field_index) .finish() } } salsa-0.26.2/src/tracked_struct.rs000064400000000000000000001504151046102023000152020ustar 00000000000000#![allow(clippy::undocumented_unsafe_blocks)] // TODO(#697) document safety use std::any::TypeId; use std::hash::Hash; use std::marker::PhantomData; use std::ops::Index; use std::{fmt, iter, mem}; use crossbeam_queue::SegQueue; use hashbrown::hash_table::Entry; use thin_vec::ThinVec; use tracked_field::FieldIngredientImpl; use crate::function::VerifyResult; use crate::hash::{FxHashSet, FxIndexSet}; use crate::id::{AsId, FromId}; use crate::ingredient::{Ingredient, Jar}; use crate::key::DatabaseKeyIndex; use crate::plumbing::{self, ZalsaLocal}; use crate::revision::{AtomicRevision, OptionalAtomicRevision}; use crate::runtime::Stamp; use crate::salsa_struct::SalsaStructInDb; use crate::sync::Arc; use crate::table::memo::{MemoTable, MemoTableTypes, MemoTableWithTypesMut}; use crate::table::{Slot, Table}; use crate::zalsa::{IngredientIndex, JarKind, Zalsa}; use crate::zalsa_local::QueryEdge; use crate::{Durability, Event, EventKind, Id, Revision}; pub mod tracked_field; // ANCHOR: Configuration /// Trait that defines the key properties of a tracked struct. /// /// Implemented by the `#[salsa::tracked]` macro when applied /// to a struct. pub trait Configuration: Sized + 'static { const LOCATION: crate::ingredient::Location; /// The debug name of the tracked struct. const DEBUG_NAME: &'static str; /// The debug names of any tracked fields. const TRACKED_FIELD_NAMES: &'static [&'static str]; /// The relative indices of any tracked fields. const TRACKED_FIELD_INDICES: &'static [usize]; /// Whether this struct should be persisted with the database. const PERSIST: bool; /// A (possibly empty) tuple of the fields for this struct. type Fields<'db>: Send + Sync; /// A array of [`AtomicRevision`][] values, one per each of the tracked value fields. /// When a struct is re-recreated in a new revision, the corresponding /// entries for each field are updated to the new revision if their /// values have changed (or if the field is marked as `#[no_eq]`). #[cfg(feature = "persistence")] type Revisions: Send + Sync + Index + plumbing::serde::Serialize + for<'de> plumbing::serde::Deserialize<'de>; #[cfg(not(feature = "persistence"))] type Revisions: Send + Sync + Index; type Struct<'db>: Copy + FromId + AsId; fn untracked_fields(fields: &Self::Fields<'_>) -> impl Hash; /// Create a new value revision array where each element is set to `current_revision`. fn new_revisions(current_revision: Revision) -> Self::Revisions; /// Update the field data and, if the value has changed, /// the appropriate entry in the `revisions` array (tracked fields only). /// /// Returns `true` if any untracked field was updated and /// the struct should be considered re-created. /// /// # Safety /// /// Requires the same conditions as the `maybe_update` /// method on [the `Update` trait](`crate::update::Update`). /// /// In short, requires that `old_fields` be a pointer into /// storage from a previous revision. /// It must meet its validity invariant. /// Owned content must meet safety invariant. /// `*mut` here is not strictly needed; /// it is used to signal that the content /// is not guaranteed to recursively meet /// its safety invariant and /// hence this must be dereferenced with caution. /// /// Ensures that `old_fields` is fully updated and valid /// after it returns and that `revisions` has been updated /// for any field that changed. unsafe fn update_fields<'db>( current_revision: Revision, revisions: &Self::Revisions, old_fields: *mut Self::Fields<'db>, new_fields: Self::Fields<'db>, ) -> bool; /// Returns the size of any heap allocations in the output value, in bytes. fn heap_size(_value: &Self::Fields<'_>) -> Option { None } /// Serialize the fields using `serde`. /// /// Panics if the value is not persistable, i.e. `Configuration::PERSIST` is `false`. fn serialize(value: &Self::Fields<'_>, serializer: S) -> Result where S: plumbing::serde::Serializer; /// Deserialize the fields using `serde`. /// /// Panics if the value is not persistable, i.e. `Configuration::PERSIST` is `false`. fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: plumbing::serde::Deserializer<'de>; } // ANCHOR_END: Configuration pub struct JarImpl where C: Configuration, { phantom: PhantomData, } impl Default for JarImpl { fn default() -> Self { Self { phantom: Default::default(), } } } impl Jar for JarImpl { fn create_ingredients( _zalsa: &mut Zalsa, struct_index: crate::zalsa::IngredientIndex, ) -> Vec> { let struct_ingredient = >::new(struct_index); let tracked_field_ingredients = C::TRACKED_FIELD_INDICES .iter() .copied() .map(|tracked_index| { Box::new(>::new( tracked_index, struct_index.successor(tracked_index), )) as _ }); iter::once(Box::new(struct_ingredient) as _) .chain(tracked_field_ingredients) .collect() } fn id_struct_type_id() -> TypeId { TypeId::of::>() } } pub trait TrackedStructInDb: SalsaStructInDb { /// Converts the identifier for this tracked struct into a `DatabaseKeyIndex`. fn database_key_index(zalsa: &Zalsa, id: Id) -> DatabaseKeyIndex; } /// Created for each tracked struct. /// /// This ingredient only stores the "id" fields. It is a kind of "dressed up" interner; /// the active query + values of id fields are hashed to create the tracked /// struct id. The value fields are stored in [`crate::function::IngredientImpl`] /// instances keyed by the tracked struct id. /// /// Unlike normal interned values, tracked struct indices can be deleted and reused aggressively /// without dependency edges on the creating query. When a tracked function is collected, /// any tracked structs it created can be deleted. Additionally, when a tracked function /// re-executes but does not create a tracked struct that was previously created, it can /// be deleted. No dependency edge is required as the lifetime of a tracked struct is tied /// directly to the query that created it. pub struct IngredientImpl where C: Configuration, { /// Our index in the database. ingredient_index: IngredientIndex, /// Phantom data: we fetch `Value` out from `Table` phantom: PhantomData Value>, /// Store freed ids free_list: SegQueue, memo_table_types: Arc, } /// Defines the identity of a tracked struct. /// This is the key to a hashmap that is (initially) /// stored in the [`ActiveQuery`](`crate::active_query::ActiveQuery`) /// struct and later moved to the [`Memo`](`crate::function::memo::Memo`). #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] #[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] pub(crate) struct Identity { // Conceptually, this contains an `IdentityHash`, but using `IdentityHash` directly will grow the size // of this struct struct by a `std::mem::size_of::()` due to unusable padding. To avoid this increase // in size, we inline the fields of `IdentityHash`. /// Index of the tracked struct ingredient. ingredient_index: IngredientIndex, /// Hash of the id fields. hash: u64, /// The unique disambiguator assigned within the active query /// to distinguish distinct tracked structs with the same identity_hash. disambiguator: Disambiguator, } impl Identity { pub(crate) fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index } } /// Stores the data that (almost) uniquely identifies a tracked struct. /// /// This includes the ingredient index of that struct type plus the hash of its untracked /// fields. This is mapped to a disambiguator -- a value that starts as 0 but increments /// each round, allowing for multiple tracked structs with the same hash and `IngredientIndex` /// created within the query to each have a unique ID. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] pub struct IdentityHash { /// Index of the tracked struct ingredient. ingredient_index: IngredientIndex, /// Hash of the id fields. hash: u64, } /// A map from tracked struct [`Identity`] to their final [`Id`]. #[derive(Default, Debug)] pub(crate) struct IdentityMap { // We use a `HashTable` here as our key contains its own hash (`Identity::hash`), // so we do the hash wrangling ourselves. table: hashbrown::HashTable, } impl IdentityMap { /// Seeds the identity map with the IDs from a previous revision. pub(crate) fn seed(&mut self, source: &[(Identity, Id)]) { for &(key, id) in source { self.insert_entry(key, id, false); } } // Mark all tracked structs in the map as created by the current query. pub(crate) fn mark_all_active(&mut self, items: impl IntoIterator) { for (key, id) in items { self.insert_entry(key, id, true); } } /// Insert a tracked struct identity into the map with the given ID. pub(crate) fn insert(&mut self, key: Identity, id: Id) -> Option { self.insert_entry(key, id, true) } fn insert_entry(&mut self, key: Identity, id: Id, active: bool) -> Option { let entry = self.table.entry( key.hash, |entry| entry.identity == key, |entry| entry.identity.hash, ); match entry { Entry::Vacant(entry) => { entry.insert(TrackedEntry { identity: key, id, active, }); None } Entry::Occupied(mut entry) => { let tracked = entry.get_mut(); tracked.active = active; Some(std::mem::replace(&mut tracked.id, id)) } } } /// Reuses an existing identity if it already exists in the map, marking it as active. /// /// Returns the existing ID, or `None` if no ID for the given identity exists. pub(crate) fn reuse(&mut self, key: &Identity) -> Option { self.table .find_mut(key.hash, |entry| key == &entry.identity) .map(|entry| { entry.active = true; entry.id }) } /// Returns `true` if the given tracked struct key was created in the current query execution. pub(crate) fn is_active(&self, key: DatabaseKeyIndex) -> bool { self.table .iter() .find(|entry| { entry.id == key.key_index() && entry.identity.ingredient_index() == key.ingredient_index() }) .is_some_and(|entry| entry.active) } /// Drains the [`IdentityMap`] into a tuple of active and stale tracked structs. /// /// The first entry contains the identity and IDs of any tracked structs that were /// created by the current execution of the query, while the second entry contains any /// tracked structs that were created in a previous execution but not the current one. #[expect(clippy::type_complexity)] pub(crate) fn drain(&mut self) -> (ThinVec<(Identity, Id)>, Vec<(Identity, Id)>) { if self.table.is_empty() { return (ThinVec::new(), Vec::new()); } let mut stale = Vec::new(); let mut active = ThinVec::with_capacity(self.table.len()); for entry in self.table.drain() { if entry.active { active.push((entry.identity, entry.id)); } else { stale.push((entry.identity, entry.id)); } } // Removing a stale tracked struct ID shows up in the event logs, so make sure // the order is stable here. stale.sort_unstable_by(|a, b| { (a.0.ingredient_index(), a.1).cmp(&(b.0.ingredient_index(), b.1)) }); (active, stale) } pub(crate) fn is_empty(&self) -> bool { self.table.is_empty() } pub(crate) fn clear(&mut self) { self.table.clear() } } /// A tracked struct entry stored in an [`IdentityMap`]. #[derive(Debug)] struct TrackedEntry { /// The identity of the tracked struct. identity: Identity, /// The current ID of the tracked struct. id: Id, /// Whether or not this tracked struct was created by the current query. /// /// Entries where `active` is `false` represent tracked structs that were created /// by a previous execution of the query, but not in the current one, and hence can /// be collected. active: bool, } // ANCHOR: ValueStruct pub struct Value where C: Configuration, { /// The revision when this tracked struct was last updated. /// This field also acts as a kind of "lock" over the `value` field. Once it is equal /// to `Some(current_revision)`, the fields are locked and /// cannot change further. This makes it safe to give out `&`-references /// so long as they do not live longer than the current revision /// (which is assured by tying their lifetime to the lifetime of an `&`-ref /// to the database). /// /// The struct is updated from an older revision `R0` to the current revision `R1` /// when the struct is first accessed in `R1`, whether that be because the original /// query re-created the struct (i.e., by user calling `Struct::new`) or because /// the struct was read from. (Structs may not be recreated in the new revision if /// the inputs to the query have not changed.) /// /// When re-creating the struct, the field is temporarily set to `None`. /// This is signal that there is an active `&mut` modifying the other fields: /// even reading from those fields in that situation would create UB. /// This `None` value should never be observable by users unless they have /// leaked a reference across threads somehow. updated_at: OptionalAtomicRevision, /// The durability minimum durability of all inputs consumed /// by the creator query prior to creating this tracked struct. /// If any of those inputs changes, then the creator query may /// create this struct with different values. durability: Durability, /// The revision information for each field: when did this field last change. /// When tracked structs are re-created, this revision may be updated to the /// current revision if the value is different. revisions: C::Revisions, /// Fields of this tracked struct. They can change across revisions, /// but they do not change within a particular revision. /// /// TODO: Consider whether we need a more explicit aliasing barrier or whether /// this should be restructured (e.g., with a nested struct for `fields` + `memos`) /// to make the aliasing guarantees more obvious. See PR #741 for prior discussion. fields: C::Fields<'static>, /// Memo table storing the results of query functions etc. /*unsafe */ memos: MemoTable, } // ANCHOR_END: ValueStruct #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] #[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "persistence", serde(transparent))] pub struct Disambiguator(u32); #[derive(Default, Debug)] pub(crate) struct DisambiguatorMap { // we use a non-hasher hashmap here as our key contains its own hash (in a sense) // so we use the raw entry api instead to avoid the overhead of hashing unnecessarily map: hashbrown::HashMap, } impl DisambiguatorMap { pub(crate) fn disambiguate(&mut self, key: IdentityHash) -> Disambiguator { use hashbrown::hash_map::RawEntryMut; let entry = self.map.raw_entry_mut().from_hash(key.hash, |k| *k == key); let disambiguator = match entry { RawEntryMut::Occupied(occupied) => occupied.into_mut(), RawEntryMut::Vacant(vacant) => { vacant .insert_with_hasher(key.hash, key, Disambiguator(0), |k| k.hash) .1 } }; let result = *disambiguator; disambiguator.0 += 1; result } pub(crate) fn seed<'a>(&mut self, identities: impl Iterator) { for identity in identities { self.disambiguate(IdentityHash { ingredient_index: identity.ingredient_index, hash: identity.hash, }); } } pub(crate) fn clear(&mut self) { self.map.clear() } pub fn is_empty(&self) -> bool { self.map.is_empty() } } impl IngredientImpl where C: Configuration, { /// Create a tracked struct ingredient. Generated by the `#[tracked]` macro, /// not meant to be called directly by end-users. fn new(index: IngredientIndex) -> Self { Self { ingredient_index: index, phantom: PhantomData, free_list: Default::default(), memo_table_types: Arc::new(MemoTableTypes::default()), } } /// Returns the database key index for a tracked struct with the given id. pub fn database_key_index(&self, id: Id) -> DatabaseKeyIndex { DatabaseKeyIndex::new(self.ingredient_index, id) } pub fn new_struct<'db>( &'db self, zalsa: &'db Zalsa, zalsa_local: &'db ZalsaLocal, mut fields: C::Fields<'db>, ) -> C::Struct<'db> { let identity_hash = IdentityHash { ingredient_index: self.ingredient_index, hash: crate::hash::hash(&C::untracked_fields(&fields)), }; let (current_deps, disambiguator) = zalsa_local.disambiguate(identity_hash); let identity = Identity { hash: identity_hash.hash, ingredient_index: identity_hash.ingredient_index, disambiguator, }; if let Some(id) = zalsa_local.tracked_struct_id(&identity) { // The struct already exists in the intern map. let index = self.database_key_index(id); crate::tracing::trace!("Reuse tracked struct {id:?}", id = index); // SAFETY: The `id` was present in the interned map, so the value must be initialized. let update_result = unsafe { self.update(zalsa, id, ¤t_deps, fields) }; fields = match update_result { // Overwrite the previous ID if we are reusing the old slot with new fields. Ok(updated_id) if updated_id != id => { zalsa_local.store_tracked_struct_id(identity, updated_id); return FromId::from_id(updated_id); } // The id has not changed. Ok(id) => return FromId::from_id(id), // Failed to perform the update, we are forced to allocate a new slot. Err(fields) => fields, }; } // We failed to perform the update, or this is a new tracked struct, so allocate a new entry // in the struct map. let id = self.allocate(zalsa, zalsa_local, ¤t_deps, fields); let key = self.database_key_index(id); crate::tracing::trace!("Allocated new tracked struct {key:?}"); zalsa_local.store_tracked_struct_id(identity, id); FromId::from_id(id) } fn allocate<'db>( &'db self, zalsa: &'db Zalsa, zalsa_local: &'db ZalsaLocal, current_deps: &Stamp, fields: C::Fields<'db>, ) -> Id { let current_revision = zalsa.current_revision(); let value = |_| Value { updated_at: OptionalAtomicRevision::new(Some(current_revision)), durability: current_deps.durability, revisions: C::new_revisions(current_deps.changed_at), // SAFETY: We just erase the lifetime fields: unsafe { mem::transmute::, C::Fields<'static>>(fields) }, // SAFETY: We only ever access the memos of a value that we allocated through // our `MemoTableTypes`. memos: unsafe { MemoTable::new(self.memo_table_types()) }, }; while let Some(id) = self.free_list.pop() { // Increment the ID generation before reusing it, as if we have allocated a new // slot in the table. // // If the generation would overflow, we are forced to leak the slot. Note that this // shouldn't be a problem in general as sufficient bits are reserved for the generation. let Some(id) = id.next_generation() else { crate::tracing::info!( "leaking tracked struct {:?} due to generation overflow", self.database_key_index(id) ); continue; }; // SAFETY: `data_raw` is a live-unaliased pointer let data_raw = unsafe { &mut *Self::data_raw(zalsa.table(), id) }; debug_assert!( data_raw.updated_at.load().is_none(), "free list entry for `{id:?}` does not have `None` for `updated_at`" ); // Overwrite the free-list entry. Use `*foo = ` because the entry // has been previously initialized and we want to free the old contents. *data_raw = value(id); return id; } let (id, _) = zalsa_local.allocate::>(zalsa, self.ingredient_index, value); id } /// Get mutable access to the data for `id` -- this holds a write lock for the duration /// of the returned value. /// /// # Panics /// /// * If the value is not present in the map. /// * If the value is already updated in this revision. /// /// # Safety /// /// The value at the given `id` must be initialized. unsafe fn update<'db>( &'db self, zalsa: &'db Zalsa, mut id: Id, current_deps: &Stamp, fields: C::Fields<'db>, ) -> Result> { let data_raw = Self::data_raw(zalsa.table(), id); // The protocol is: // // * When we begin updating, we store `None` in the `updated_at` field // * When completed, we store `Some(current_revision)` in `updated_at` // // No matter what mischief users get up to, it should be impossible for us to // observe `None` in `updated_at`. The `id` should only be associated with one // query and that query can only be running in one thread at a time. // // We *can* observe `Some(current_revision)` however, which means that this // tracked struct is already updated for this revision in two ways. // In that case we should not modify or touch it because there may be // `&`-references to its contents floating around. // // Observing `Some(current_revision)` can happen in two scenarios: // - leaks, see tests\preverify-struct-with-leaked-data.rs and tests\preverify-struct-with-leaked-data-2.rs // - or the following scenario (FIXME verify this? There is no test that covers this behavior): // // * Revision 1: // * Tracked function F creates tracked struct S // * F reads input I // * Revision 2: I is changed, F is re-executed // // When F is re-executed in rev 2, we first try to validate F's inputs/outputs, // which is the list [output: S, input: I]. As no inputs have changed by the time // we reach S, we mark it as verified. But then input I is seen to have changed, // and so we re-execute F. Note that we *know* that S will have the same value // (barring program bugs). // // Further complicating things: it is possible that F calls F2 // and gives it (e.g.) S as one of its arguments. Validating F2 may cause F2 to // re-execute which means that it may indeed have read from S's fields // during the current revision and thus obtained an `&` reference to those fields // that is still live. { // SAFETY: `updated_at` is never exclusively borrowed, so borrowing it is sound let last_updated_at = unsafe { (*data_raw).updated_at.load() }; assert!( last_updated_at.is_some(), "two concurrent writers to {id:?}, should not be possible" ); // The value is already read-locked, but we can reuse it safely as per above. if last_updated_at == Some(zalsa.current_revision()) { return Ok(id); } // Updating the fields may make it necessary to increment the generation of the ID. In // the unlikely case that the ID is already at its maximum generation, we are forced to leak // the previous slot and allocate a new value. if id.generation() == u32::MAX { crate::tracing::info!( "leaking tracked struct {:?} due to generation overflow", self.database_key_index(id) ); return Err(fields); } // Acquire the write-lock. This can only fail if there is a parallel thread // reading from this same `id`, which can only happen if the user has leaked it. // Tsk tsk. // SAFETY: `updated_at` is never exclusively borrowed, so borrowing it is sound let swapped = unsafe { (*data_raw).updated_at.swap(None) }; if last_updated_at != swapped { panic!( "failed to acquire write lock, id `{id:?}` \ must have been leaked across threads" ); } } // SAFETY: We have now *claimed* mutable access to the `value` field by swapping in `None`, // any attempt to read concurrently will panic so it is safe to take exclusive references. let old_fields = unsafe { &raw mut (*data_raw).fields }.cast::>(); // SAFETY: `revisions` contains `AtomicRevision` values which can be safely accessed // concurrently with `tracked_field::maybe_changed_after`. let revisions = unsafe { &(*data_raw).revisions }; // SAFETY: We assert that the pointer to `data.revisions` // is a pointer into the database referencing a value // from a previous revision. As such, it continues to meet // its validity invariant and any owned content also continues // to meet its safety invariant. let untracked_update = unsafe { C::update_fields(current_deps.changed_at, revisions, old_fields, fields) }; if untracked_update { // Consider this a new tracked-struct when any non-tracked field got updated. // This should be rare and only ever happen if there's a hash collision. // // Note that we hold the lock and have exclusive access to the tracked struct data, // so there should be no live instances of IDs from the previous generation. We clear // the memos and return a new ID here as if we have allocated a new slot. // SAFETY: We have acquired the write lock by swapping `None` into `updated_at` let memo_table = unsafe { &mut (*data_raw).memos }; // SAFETY: The memo table belongs to a value that we allocated, so it has the // correct type. unsafe { self.clear_memos(zalsa, memo_table, id) }; id = id .next_generation() .expect("already verified that generation is not maximum"); } let durability = unsafe { &mut (*data_raw).durability }; if current_deps.durability < *durability { let new_revisions = C::new_revisions(current_deps.changed_at); for i in 0..C::TRACKED_FIELD_INDICES.len() { revisions[i].store(new_revisions[i].load()); } } *durability = current_deps.durability; // SAFETY: `updated_at` is never exclusively borrowed, so borrowing it is sound // release the lock let swapped_out = unsafe { (*data_raw).updated_at.swap(Some(zalsa.current_revision())) }; assert!( swapped_out.is_none(), "two concurrent writers to {id:?}, should not be possible" ); Ok(id) } /// Fetch the data for a given id created by this ingredient from the table, /// -giving it the appropriate type. fn data_raw(table: &Table, id: Id) -> *mut Value { table.get_raw(id) } /// # Safety /// /// `data` must be a valid pointer to a `Value` unsafe fn lock_fields( &self, data: *const Value, current_revision: Revision, ) -> &C::Fields<'_> { // SAFETY: `data` is a valid pointer acquire_read_lock(unsafe { &(*data).updated_at }, current_revision); // SAFETY: We have acquired a read lock, so `values` is not aliased unsafe { mem::transmute::<&C::Fields<'static>, &C::Fields<'_>>(&(*data).fields) } } /// Deletes the given entities. This is used after a query `Q` executes and we can compare /// the entities `E_now` that it produced in this revision vs the entities /// `E_prev` it produced in the last revision. Any missing entities `E_prev - E_new` can be /// deleted. /// /// # Warning /// /// Using this method on an entity id that MAY be used in the current revision will lead to /// unspecified results (but not UB). See [`InternedIngredient::delete_index`] for more /// discussion and important considerations. pub(crate) fn delete_entity(&self, zalsa: &Zalsa, id: Id) { zalsa.event(&|| { Event::new(crate::EventKind::DidDiscard { key: self.database_key_index(id), }) }); let data = Self::data_raw(zalsa.table(), id); // We want to set `updated_at` to `None`, signalling that other field values // cannot be read. The current value should be `Some(R0)` for some older revision. match unsafe { (*data).updated_at.swap(None) } { None => { panic!("cannot delete write-locked id `{id:?}`; value leaked across threads"); } Some(r) if r == zalsa.current_revision() => panic!( "cannot delete read-locked id `{id:?}`; value leaked across threads or user functions not deterministic" ), Some(_) => (), } // SAFETY: We have acquired the write lock by swapping `None` into `updated_at` let memo_table = unsafe { &mut (*data).memos }; // SAFETY: The memo table belongs to a value that we allocated, so it // has the correct type. unsafe { self.clear_memos(zalsa, memo_table, id) }; // now that all cleanup has occurred, make available for re-use self.free_list.push(id); } /// Clears the given memo table. /// /// # Safety /// /// The `MemoTable` must belong to a `Value` of the correct type. pub(crate) unsafe fn clear_memos(&self, zalsa: &Zalsa, memo_table: &mut MemoTable, id: Id) { // SAFETY: The caller guarantees this is the correct types table. let table = unsafe { self.memo_table_types.attach_memos_mut(memo_table) }; // `Database::salsa_event` is a user supplied callback which may panic // in that case we need a drop guard to free the memo table struct TableDropGuard<'a>(MemoTableWithTypesMut<'a>); impl Drop for TableDropGuard<'_> { fn drop(&mut self) { // SAFETY: We have `&mut MemoTable`, so no more references to these memos exist and we are good // to drop them. unsafe { self.0.drop() }; } } let mut table_guard = TableDropGuard(table); // SAFETY: We have `&mut MemoTable`, so no more references to these memos exist and we are good // to drop them. unsafe { table_guard.0.take_memos(|memo_ingredient_index, memo| { let ingredient_index = zalsa.ingredient_index_for_memo(self.ingredient_index, memo_ingredient_index); let executor = DatabaseKeyIndex::new(ingredient_index, id); zalsa.event(&|| Event::new(EventKind::DidDiscard { key: executor })); memo.remove_outputs(zalsa, executor); }) }; mem::forget(table_guard); // Reset the table after having dropped any memos. memo_table.reset(); } /// Return reference to the field data ignoring dependency tracking. /// Used for debugging. pub fn leak_fields<'db>( &'db self, zalsa: &'db Zalsa, s: C::Struct<'db>, ) -> &'db C::Fields<'db> { let id = AsId::as_id(&s); let data = Self::data_raw(zalsa.table(), id); // SAFETY: `data` is a valid pointer acquired from the table. unsafe { self.lock_fields(data, zalsa.current_revision()) } } /// Access to this tracked field. /// /// Note that this function returns the entire tuple of value fields. /// The caller is responsible for selecting the appropriate element. pub fn tracked_field<'db>( &'db self, zalsa: &'db Zalsa, zalsa_local: &'db ZalsaLocal, s: C::Struct<'db>, relative_tracked_index: usize, ) -> &'db C::Fields<'db> { let id = AsId::as_id(&s); let field_ingredient_index = self.ingredient_index.successor(relative_tracked_index); let data = Self::data_raw(zalsa.table(), id); // SAFETY: `data` is a valid pointer acquired from the table. let fields = unsafe { self.lock_fields(data, zalsa.current_revision()) }; // SAFETY: We just acquired the read lock (in `Self::fields`), so accessing `revisions` will not be able to alias let field_changed_at = unsafe { (&(*data).revisions)[relative_tracked_index].load() }; // SAFETY: We just acquired the read lock (in `Self::fields`), so accessing `durability` will not be able to alias zalsa_local.report_tracked_read_simple( DatabaseKeyIndex::new(field_ingredient_index, id), unsafe { (*data).durability }, field_changed_at, ); fields } /// Access to this untracked field. /// /// Note that this function returns the entire tuple of value fields. /// The caller is responsible for selecting the appropriate element. pub fn untracked_field<'db>( &'db self, zalsa: &'db Zalsa, s: C::Struct<'db>, ) -> &'db C::Fields<'db> { let id = AsId::as_id(&s); let data = Self::data_raw(zalsa.table(), id); // Note that we do not need to add a dependency on the tracked struct // as IDs that are reused increment their generation, invalidating any // dependent queries directly. // SAFETY: `data` is a valid pointer acquired from the table. unsafe { self.lock_fields(data, zalsa.current_revision()) } } /// Returns all data corresponding to the tracked struct. pub fn entries<'db>(&'db self, zalsa: &'db Zalsa) -> impl Iterator> { zalsa .table() .slots_of::>() .map(|(id, value)| StructEntry { value, key: self.database_key_index(id), }) } } /// A tracked struct entry. pub struct StructEntry<'db, C> where C: Configuration, { value: &'db Value, key: DatabaseKeyIndex, } impl<'db, C> StructEntry<'db, C> where C: Configuration, { /// Returns the `DatabaseKeyIndex` for this entry. pub fn key(&self) -> DatabaseKeyIndex { self.key } /// Returns the tracked struct. pub fn as_struct(&self) -> C::Struct<'_> { FromId::from_id(self.key.key_index()) } #[cfg(feature = "salsa_unstable")] pub fn value(&self) -> &'db Value { self.value } } impl Ingredient for IngredientImpl where C: Configuration, { fn location(&self) -> &'static crate::ingredient::Location { &C::LOCATION } fn ingredient_index(&self) -> IngredientIndex { self.ingredient_index } unsafe fn maybe_changed_after( &self, _zalsa: &Zalsa, _db: crate::database::RawDatabase<'_>, _input: Id, _revision: Revision, ) -> VerifyResult { // Any change to a tracked struct results in a new ID generation, so there // are no direct dependencies on the struct, only on its tracked fields. panic!("nothing should ever depend on a tracked struct directly") } fn collect_minimum_serialized_edges( &self, _zalsa: &Zalsa, _edge: QueryEdge, _serialized_edges: &mut FxIndexSet, _visited_edges: &mut FxHashSet, ) { // Note that tracked structs are referenced by the identity map, but that // only matters if we are serializing the creating query, in which case // the dependency edge will be serialized directly. // // TODO: We could flatten the identity map here if the tracked struct is being // persisted, in order to more aggressively preserve the tracked struct IDs if // the transitive query is re-executed. panic!("nothing should ever depend on a tracked struct directly") } fn flatten_cycle_head_dependencies( &self, _zalsa: &Zalsa, _id: Id, _flattened_input_outputs: &mut FxIndexSet, _seen: &mut FxHashSet, ) { panic!("nothing should ever depend on a tracked struct directly") } fn remove_stale_output( &self, zalsa: &Zalsa, _executor: DatabaseKeyIndex, stale_output_key: crate::Id, ) { // This method is called when, in prior revisions, // `executor` creates a tracked struct `salsa_output_key`, // but it did not in the current revision. // In that case, we can delete `stale_output_key` and any data associated with it. self.delete_entity(zalsa, stale_output_key) } fn debug_name(&self) -> &'static str { C::DEBUG_NAME } fn jar_kind(&self) -> JarKind { JarKind::Struct } fn memo_table_types(&self) -> &Arc { &self.memo_table_types } fn memo_table_types_mut(&mut self) -> &mut Arc { &mut self.memo_table_types } /// Returns memory usage information about any tracked structs. #[cfg(feature = "salsa_unstable")] fn memory_usage(&self, db: &dyn crate::Database) -> Option> { let memory_usage = self .entries(db.zalsa()) // SAFETY: The memo table belongs to a value that we allocated, so it // has the correct type. .map(|entry| unsafe { entry.value.memory_usage(&self.memo_table_types) }) .collect(); Some(memory_usage) } fn is_persistable(&self) -> bool { C::PERSIST } fn should_serialize(&self, zalsa: &Zalsa) -> bool { C::PERSIST && self.entries(zalsa).next().is_some() } #[cfg(feature = "persistence")] unsafe fn serialize<'db>( &'db self, zalsa: &'db Zalsa, f: &mut dyn FnMut(&dyn erased_serde::Serialize), ) { f(&persistence::SerializeIngredient { zalsa, _ingredient: self, }) } #[cfg(feature = "persistence")] fn deserialize( &mut self, zalsa: &mut Zalsa, deserializer: &mut dyn erased_serde::Deserializer, ) -> Result<(), erased_serde::Error> { let deserialize = persistence::DeserializeIngredient { zalsa, ingredient: self, }; serde::de::DeserializeSeed::deserialize(deserialize, deserializer) } } impl std::fmt::Debug for IngredientImpl where C: Configuration, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct(std::any::type_name::()) .field("ingredient_index", &self.ingredient_index) .finish() } } impl Value where C: Configuration, { /// Fields of this tracked struct. /// /// They can change across revisions, but they do not change within /// a particular revision. #[cfg_attr(not(feature = "salsa_unstable"), doc(hidden))] pub fn fields(&self) -> &C::Fields<'static> { &self.fields } } #[inline] fn acquire_read_lock(updated_at: &OptionalAtomicRevision, current_revision: Revision) { loop { match updated_at.load() { None => panic!( "write lock taken; value leaked across threads or user functions not deterministic" ), // the read lock was taken by someone else, so we also succeed Some(r) if r == current_revision => return, Some(r) => { if updated_at .compare_exchange(Some(r), Some(current_revision)) .is_ok() { break; } } } } } #[cfg(feature = "salsa_unstable")] impl Value where C: Configuration, { /// Returns memory usage information about the tracked struct. /// /// # Safety /// /// The `MemoTable` must belong to a `Value` of the correct type. unsafe fn memory_usage(&self, memo_table_types: &MemoTableTypes) -> crate::database::SlotInfo { let heap_size = C::heap_size(self.fields()); // SAFETY: The caller guarantees this is the correct types table. let memos = unsafe { memo_table_types.attach_memos(&self.memos) }; crate::database::SlotInfo { debug_name: C::DEBUG_NAME, size_of_metadata: mem::size_of::() - mem::size_of::>(), size_of_fields: mem::size_of::>(), heap_size_of_fields: heap_size, memos: memos.memory_usage(), } } } // SAFETY: `Value` is our private type branded over the unique configuration `C`. unsafe impl Slot for Value where C: Configuration, { #[inline(always)] unsafe fn memos( this: *const Self, current_revision: Revision, ) -> *const crate::table::memo::MemoTable { // Acquiring the read lock here with the current revision to ensure that there // is no danger of a race when deleting a tracked struct. // SAFETY: `this` is a valid pointer given the caller obligation unsafe { acquire_read_lock(&(*this).updated_at, current_revision) }; // SAFETY: `this` is a valid pointer given the caller obligation and we have acquired a read // lock, so `values` is not aliased unsafe { &raw const (*this).memos } } #[inline(always)] fn memos_mut(&mut self) -> &mut crate::table::memo::MemoTable { &mut self.memos } } #[cfg(test)] mod tests { use super::*; #[test] fn disambiguate_map_works() { let mut d = DisambiguatorMap::default(); // set up all 4 permutations of differing field values let h1 = IdentityHash { ingredient_index: IngredientIndex::new(0), hash: 0, }; let h2 = IdentityHash { ingredient_index: IngredientIndex::new(1), hash: 0, }; let h3 = IdentityHash { ingredient_index: IngredientIndex::new(0), hash: 1, }; let h4 = IdentityHash { ingredient_index: IngredientIndex::new(1), hash: 1, }; assert_eq!(d.disambiguate(h1), Disambiguator(0)); assert_eq!(d.disambiguate(h1), Disambiguator(1)); assert_eq!(d.disambiguate(h2), Disambiguator(0)); assert_eq!(d.disambiguate(h2), Disambiguator(1)); assert_eq!(d.disambiguate(h3), Disambiguator(0)); assert_eq!(d.disambiguate(h3), Disambiguator(1)); assert_eq!(d.disambiguate(h4), Disambiguator(0)); assert_eq!(d.disambiguate(h4), Disambiguator(1)); } #[test] fn identity_map_works() { let mut d = IdentityMap::default(); // set up all 8 permutations of differing field values let i1 = Identity { ingredient_index: IngredientIndex::new(0), hash: 0, disambiguator: Disambiguator(0), }; let i2 = Identity { ingredient_index: IngredientIndex::new(1), hash: 0, disambiguator: Disambiguator(0), }; let i3 = Identity { ingredient_index: IngredientIndex::new(0), hash: 1, disambiguator: Disambiguator(0), }; let i4 = Identity { ingredient_index: IngredientIndex::new(1), hash: 1, disambiguator: Disambiguator(0), }; let i5 = Identity { ingredient_index: IngredientIndex::new(0), hash: 0, disambiguator: Disambiguator(1), }; let i6 = Identity { ingredient_index: IngredientIndex::new(1), hash: 0, disambiguator: Disambiguator(1), }; let i7 = Identity { ingredient_index: IngredientIndex::new(0), hash: 1, disambiguator: Disambiguator(1), }; let i8 = Identity { ingredient_index: IngredientIndex::new(1), hash: 1, disambiguator: Disambiguator(1), }; // SAFETY: We don't use the IDs within salsa internals so this is fine unsafe { assert_eq!(d.insert(i1, Id::from_index(0)), None); assert_eq!(d.insert(i2, Id::from_index(1)), None); assert_eq!(d.insert(i3, Id::from_index(2)), None); assert_eq!(d.insert(i4, Id::from_index(3)), None); assert_eq!(d.insert(i5, Id::from_index(4)), None); assert_eq!(d.insert(i6, Id::from_index(5)), None); assert_eq!(d.insert(i7, Id::from_index(6)), None); assert_eq!(d.insert(i8, Id::from_index(7)), None); assert_eq!(d.reuse(&i1), Some(Id::from_index(0))); assert_eq!(d.reuse(&i2), Some(Id::from_index(1))); assert_eq!(d.reuse(&i3), Some(Id::from_index(2))); assert_eq!(d.reuse(&i4), Some(Id::from_index(3))); assert_eq!(d.reuse(&i5), Some(Id::from_index(4))); assert_eq!(d.reuse(&i6), Some(Id::from_index(5))); assert_eq!(d.reuse(&i7), Some(Id::from_index(6))); assert_eq!(d.reuse(&i8), Some(Id::from_index(7))); }; } } #[cfg(feature = "persistence")] mod persistence { use std::fmt; use serde::ser::{SerializeMap, SerializeStruct}; use serde::{Deserialize, de}; use super::{Configuration, IngredientImpl, Value}; use crate::plumbing::Ingredient; use crate::revision::OptionalAtomicRevision; use crate::table::memo::MemoTable; use crate::zalsa::Zalsa; use crate::{Durability, Id}; pub struct SerializeIngredient<'db, C> where C: Configuration, { pub zalsa: &'db Zalsa, pub _ingredient: &'db IngredientImpl, } impl serde::Serialize for SerializeIngredient<'_, C> where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let Self { zalsa, .. } = self; let count = zalsa.table().slots_of::>().count(); let mut map = serializer.serialize_map(Some(count))?; for (id, value) in zalsa.table().slots_of::>() { map.serialize_entry(&id.as_bits(), value)?; } map.end() } } impl serde::Serialize for Value where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut value = serializer.serialize_struct("Value", 4)?; let Value { durability, updated_at, revisions, fields, memos: _, } = self; value.serialize_field("durability", &durability)?; value.serialize_field("updated_at", &updated_at)?; value.serialize_field("revisions", &revisions)?; value.serialize_field("fields", &SerializeFields::(fields))?; value.end() } } struct SerializeFields<'db, C: Configuration>(&'db C::Fields<'static>); impl serde::Serialize for SerializeFields<'_, C> where C: Configuration, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { C::serialize(self.0, serializer) } } pub struct DeserializeIngredient<'db, C> where C: Configuration, { pub zalsa: &'db mut Zalsa, pub ingredient: &'db mut IngredientImpl, } impl<'de, C> de::DeserializeSeed<'de> for DeserializeIngredient<'_, C> where C: Configuration, { type Value = (); fn deserialize(self, deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_map(self) } } impl<'de, C> de::Visitor<'de> for DeserializeIngredient<'_, C> where C: Configuration, { type Value = (); fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map") } fn visit_map(self, mut access: M) -> Result where M: de::MapAccess<'de>, { let DeserializeIngredient { zalsa, ingredient } = self; while let Some((id, value)) = access.next_entry::>()? { let id = Id::from_bits(id); let (page_idx, _) = crate::table::split_id(id); let value = Value:: { updated_at: value.updated_at, durability: value.durability, revisions: value.revisions, fields: value.fields.0, // SAFETY: We only ever access the memos of a value that we allocated through // our `MemoTableTypes`. memos: unsafe { MemoTable::new(ingredient.memo_table_types()) }, }; // Force initialize the relevant page. zalsa.table_mut().force_page::>( page_idx, ingredient.ingredient_index(), ingredient.memo_table_types(), ); // Initialize the slot. // // SAFETY: We have a mutable reference to the database. let (allocated_id, _) = unsafe { zalsa .table() .page::>(page_idx) .allocate(page_idx, |_| value) .unwrap_or_else(|_| panic!("serialized an invalid `Id`: {id:?}")) }; assert_eq!( allocated_id, id, "values are serialized in allocation order" ); } Ok(()) } } #[derive(Deserialize)] #[serde(rename = "Value")] pub struct DeserializeValue { durability: Durability, updated_at: OptionalAtomicRevision, revisions: C::Revisions, #[serde(bound = "C: Configuration")] fields: DeserializeFields, } struct DeserializeFields(C::Fields<'static>); impl<'de, C> serde::Deserialize<'de> for DeserializeFields where C: Configuration, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { C::deserialize(deserializer) .map(DeserializeFields) .map_err(de::Error::custom) } } } salsa-0.26.2/src/update.rs000064400000000000000000000412321046102023000134370ustar 00000000000000#![allow(clippy::undocumented_unsafe_blocks)] // TODO(#697) document safety use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::hash::{BuildHasher, Hash}; use std::marker::PhantomData; use std::path::PathBuf; #[cfg(feature = "rayon")] use rayon::iter::Either; use crate::sync::Arc; /// This is used by the macro generated code. /// If possible, uses `Update` trait, but else requires `'static`. /// /// To use: /// /// ```rust,ignore /// use crate::update::helper::Fallback; /// update::helper::Dispatch::<$ty>::maybe_update(pointer, new_value); /// ``` /// /// It is important that you specify the `$ty` explicitly. /// /// This uses the ["method dispatch hack"](https://github.com/nvzqz/impls#how-it-works) /// to use the `Update` trait if it is available and else fallback to `'static`. pub mod helper { use std::marker::PhantomData; use super::{Update, update_fallback}; pub struct Dispatch(PhantomData); #[allow(clippy::new_without_default)] impl Dispatch { pub fn new() -> Self { Dispatch(PhantomData) } } impl Dispatch where D: Update, { /// # Safety /// /// See the `maybe_update` method in the [`Update`][] trait. pub unsafe fn maybe_update(old_pointer: *mut D, new_value: D) -> bool { // SAFETY: Same safety conditions as `Update::maybe_update` unsafe { D::maybe_update(old_pointer, new_value) } } } /// # Safety /// /// Impl will fulfill the postconditions of `maybe_update` pub unsafe trait Fallback { /// # Safety /// /// Same safety conditions as `Update::maybe_update` unsafe fn maybe_update(old_pointer: *mut T, new_value: T) -> bool; } // SAFETY: Same safety conditions as `Update::maybe_update` unsafe impl Fallback for Dispatch { unsafe fn maybe_update(old_pointer: *mut T, new_value: T) -> bool { // SAFETY: Same safety conditions as `Update::maybe_update` unsafe { update_fallback(old_pointer, new_value) } } } } /// "Fallback" for maybe-update that is suitable for fully owned T /// that implement `Eq`. In this version, we update only if the new value /// is not `Eq` to the old one. Note that given `Eq` impls that are not just /// structurally comparing fields, this may cause us not to update even if /// the value has changed (presumably because this change is not semantically /// significant). /// /// # Safety /// /// See `Update::maybe_update` pub unsafe fn update_fallback(old_pointer: *mut T, new_value: T) -> bool where T: 'static + PartialEq, { // SAFETY: Because everything is owned, this ref is simply a valid `&mut` let old_ref: &mut T = unsafe { &mut *old_pointer }; if *old_ref != new_value { *old_ref = new_value; true } else { // Subtle but important: Eq impls can be buggy or define equality // in surprising ways. If it says that the value has not changed, // we do not modify the existing value, and thus do not have to // update the revision, as downstream code will not see the new value. false } } /// Helper for generated code. Updates `*old_pointer` with `new_value`. /// Used for fields tagged with `#[no_eq]` /// /// # Safety /// /// See `Update::maybe_update` pub unsafe fn always_update(old_pointer: *mut T, new_value: T) -> bool { unsafe { *old_pointer = new_value }; true } /// # Safety /// /// Implementing this trait requires the implementor to verify: /// /// * `maybe_update` ensures the properties it is intended to ensure. /// * If the value implements `Eq`, it is safe to compare an instance /// of the value from an older revision with one from the newer /// revision. If the value compares as equal, no update is needed to /// bring it into the newer revision. /// /// NB: The second point implies that `Update` cannot be implemented for any /// `&'db T` -- (i.e., any Rust reference tied to the database). /// Such a value could refer to memory that was freed in some /// earlier revision. Even if the memory is still valid, it could also /// have been part of a tracked struct whose values were mutated, /// thus invalidating the `'db` lifetime (from a stacked borrows perspective). /// Either way, the `Eq` implementation would be invalid. pub unsafe trait Update { /// # Returns /// /// True if the value should be considered to have changed in the new revision. /// /// # Safety /// /// ## Requires /// /// Informally, requires that `old_value` points to a value in the /// database that is potentially from a previous revision and `new_value` /// points to a value produced in this revision. /// /// More formally, requires that /// /// * all parameters meet the [validity and safety invariants][i] for their type /// * `old_value` further points to allocated memory that meets the [validity invariant][i] for `Self` /// * all data *owned* by `old_value` further meets its safety invariant /// * not that borrowed data in `old_value` only meets its validity invariant /// and hence cannot be dereferenced; essentially, a `&T` may point to memory /// in the database which has been modified or even freed in the newer revision. /// /// [i]: https://www.ralfj.de/blog/2018/08/22/two-kinds-of-invariants.html /// /// ## Ensures /// /// That `old_value` is updated with unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool; } unsafe impl Update for std::convert::Infallible { unsafe fn maybe_update(_old_pointer: *mut Self, new_value: Self) -> bool { match new_value {} } } macro_rules! maybe_update_vec { ($old_pointer: expr, $new_vec: expr, $elem_ty: ty) => {{ let old_pointer = $old_pointer; let new_vec = $new_vec; let old_vec: &mut Self = unsafe { &mut *old_pointer }; if old_vec.len() != new_vec.len() { old_vec.clear(); old_vec.extend(new_vec); return true; } let mut changed = false; for (old_element, new_element) in old_vec.iter_mut().zip(new_vec) { changed |= unsafe { <$elem_ty>::maybe_update(old_element, new_element) }; } changed }}; } unsafe impl Update for Vec where T: Update, { unsafe fn maybe_update(old_pointer: *mut Self, new_vec: Self) -> bool { maybe_update_vec!(old_pointer, new_vec, T) } } unsafe impl Update for thin_vec::ThinVec where T: Update, { unsafe fn maybe_update(old_pointer: *mut Self, new_vec: Self) -> bool { maybe_update_vec!(old_pointer, new_vec, T) } } unsafe impl Update for smallvec::SmallVec where A: smallvec::Array, A::Item: Update, { unsafe fn maybe_update(old_pointer: *mut Self, new_vec: Self) -> bool { maybe_update_vec!(old_pointer, new_vec, A::Item) } } macro_rules! maybe_update_set { ($old_pointer: expr, $new_set: expr) => {{ let old_pointer = $old_pointer; let new_set = $new_set; let old_set: &mut Self = unsafe { &mut *old_pointer }; if *old_set == new_set { false } else { old_set.clear(); old_set.extend(new_set); return true; } }}; } unsafe impl Update for HashSet where K: Update + Eq + Hash, S: BuildHasher, { unsafe fn maybe_update(old_pointer: *mut Self, new_set: Self) -> bool { maybe_update_set!(old_pointer, new_set) } } unsafe impl Update for BTreeSet where K: Update + Eq + Ord, { unsafe fn maybe_update(old_pointer: *mut Self, new_set: Self) -> bool { maybe_update_set!(old_pointer, new_set) } } // Duck typing FTW, it was too annoying to make a proper function out of this. macro_rules! maybe_update_map { ($old_pointer: expr, $new_map: expr) => { 'function: { let old_pointer = $old_pointer; let new_map = $new_map; let old_map: &mut Self = unsafe { &mut *old_pointer }; // To be considered "equal", the set of keys // must be the same between the two maps. let same_keys = old_map.len() == new_map.len() && old_map.keys().all(|k| new_map.contains_key(k)); // If the set of keys has changed, then just pull in the new values // from new_map and discard the old ones. if !same_keys { old_map.clear(); old_map.extend(new_map); break 'function true; } // Otherwise, recursively descend to the values. // We do not invoke `K::update` because we assume // that if the values are `Eq` they must not need // updating (see the trait criteria). let mut changed = false; for (key, new_value) in new_map.into_iter() { let old_value = old_map.get_mut(&key).unwrap(); changed |= unsafe { V::maybe_update(old_value, new_value) }; } changed } }; } unsafe impl Update for HashMap where K: Update + Eq + Hash, V: Update, S: BuildHasher, { unsafe fn maybe_update(old_pointer: *mut Self, new_map: Self) -> bool { maybe_update_map!(old_pointer, new_map) } } unsafe impl Update for hashbrown::HashMap where K: Update + Eq + Hash, V: Update, S: BuildHasher, { unsafe fn maybe_update(old_pointer: *mut Self, new_map: Self) -> bool { maybe_update_map!(old_pointer, new_map) } } unsafe impl Update for hashbrown::HashSet where K: Update + Eq + Hash, S: BuildHasher, { unsafe fn maybe_update(old_pointer: *mut Self, new_set: Self) -> bool { maybe_update_set!(old_pointer, new_set) } } unsafe impl Update for indexmap::IndexMap where K: Update + Eq + Hash, V: Update, S: BuildHasher, { unsafe fn maybe_update(old_pointer: *mut Self, new_map: Self) -> bool { maybe_update_map!(old_pointer, new_map) } } unsafe impl Update for indexmap::IndexSet where K: Update + Eq + Hash, S: BuildHasher, { unsafe fn maybe_update(old_pointer: *mut Self, new_set: Self) -> bool { maybe_update_set!(old_pointer, new_set) } } #[cfg(feature = "ordermap")] unsafe impl Update for ordermap::OrderMap where K: Update + Eq + Hash, V: Update, S: BuildHasher, { unsafe fn maybe_update(old_pointer: *mut Self, new_map: Self) -> bool { maybe_update_map!(old_pointer, new_map) } } #[cfg(feature = "ordermap")] unsafe impl Update for ordermap::OrderSet where K: Update + Eq + Hash, S: BuildHasher, { unsafe fn maybe_update(old_pointer: *mut Self, new_set: Self) -> bool { maybe_update_set!(old_pointer, new_set) } } unsafe impl Update for BTreeMap where K: Update + Eq + Ord, V: Update, { unsafe fn maybe_update(old_pointer: *mut Self, new_map: Self) -> bool { maybe_update_map!(old_pointer, new_map) } } unsafe impl Update for Box where T: Update, { unsafe fn maybe_update(old_pointer: *mut Self, new_box: Self) -> bool { let old_box: &mut Box = unsafe { &mut *old_pointer }; unsafe { T::maybe_update(&mut **old_box, *new_box) } } } unsafe impl Update for Box<[T]> where T: Update, { unsafe fn maybe_update(old_pointer: *mut Self, new_box: Self) -> bool { let old_box: &mut Box<[T]> = unsafe { &mut *old_pointer }; if old_box.len() == new_box.len() { let mut changed = false; for (old_element, new_element) in old_box.iter_mut().zip(new_box) { changed |= unsafe { T::maybe_update(old_element, new_element) }; } changed } else { *old_box = new_box; true } } } unsafe impl Update for Arc where T: Update, { unsafe fn maybe_update(old_pointer: *mut Self, new_arc: Self) -> bool { let old_arc: &mut Arc = unsafe { &mut *old_pointer }; if Arc::ptr_eq(old_arc, &new_arc) { return false; } if let Some(inner) = Arc::get_mut(old_arc) { match Arc::try_unwrap(new_arc) { Ok(new_inner) => unsafe { T::maybe_update(inner, new_inner) }, Err(new_arc) => { // We can't unwrap the new arc, so we have to update the old one in place. *old_arc = new_arc; true } } } else { unsafe { *old_pointer = new_arc }; true } } } unsafe impl Update for [T; N] where T: Update, { unsafe fn maybe_update(old_pointer: *mut Self, new_vec: Self) -> bool { let old_pointer: *mut T = unsafe { std::ptr::addr_of_mut!((*old_pointer)[0]) }; let mut changed = false; for (new_element, i) in new_vec.into_iter().zip(0..) { changed |= unsafe { T::maybe_update(old_pointer.add(i), new_element) }; } changed } } unsafe impl Update for Result where T: Update, E: Update, { unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { let old_value = unsafe { &mut *old_pointer }; match (old_value, new_value) { (Ok(old), Ok(new)) => unsafe { T::maybe_update(old, new) }, (Err(old), Err(new)) => unsafe { E::maybe_update(old, new) }, (old_value, new_value) => { *old_value = new_value; true } } } } #[cfg(feature = "rayon")] unsafe impl Update for Either where L: Update, R: Update, { unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { let old_value = unsafe { &mut *old_pointer }; match (old_value, new_value) { (Either::Left(old), Either::Left(new)) => unsafe { L::maybe_update(old, new) }, (Either::Right(old), Either::Right(new)) => unsafe { R::maybe_update(old, new) }, (old_value, new_value) => { *old_value = new_value; true } } } } macro_rules! fallback_impl { ($($t:ty,)*) => { $( unsafe impl Update for $t { unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { unsafe { update_fallback(old_pointer, new_value) } } } )* } } fallback_impl! { String, i64, u64, i32, u32, i16, u16, i8, u8, bool, f32, f64, usize, isize, PathBuf, } #[cfg(feature = "compact_str")] fallback_impl! { compact_str::CompactString, } macro_rules! tuple_impl { ($($t:ident),*; $($u:ident),*) => { unsafe impl<$($t),*> Update for ($($t,)*) where $($t: Update,)* { #[allow(non_snake_case)] unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { let ($($t,)*) = new_value; let ($($u,)*) = unsafe { &mut *old_pointer }; #[allow(unused_mut)] let mut changed = false; $( unsafe { changed |= Update::maybe_update($u, $t); } )* changed } } } } // Create implementations for tuples up to arity 12 tuple_impl!(;); tuple_impl!(A; a); tuple_impl!(A, B; a, b); tuple_impl!(A, B, C; a, b, c); tuple_impl!(A, B, C, D; a, b, c, d); tuple_impl!(A, B, C, D, E; a, b, c, d, e); tuple_impl!(A, B, C, D, E, F; a, b, c, d, e, f); tuple_impl!(A, B, C, D, E, F, G; a, b, c, d, e, f, g); tuple_impl!(A, B, C, D, E, F, G, H; a, b, c, d, e, f, g, h); tuple_impl!(A, B, C, D, E, F, G, H, I; a, b, c, d, e, f, g, h, i); tuple_impl!(A, B, C, D, E, F, G, H, I, J; a, b, c, d, e, f, g, h, i, j); tuple_impl!(A, B, C, D, E, F, G, H, I, J, K; a, b, c, d, e, f, g, h, i, j, k); tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L; a, b, c, d, e, f, g, h, i, j, k, l); unsafe impl Update for Option where T: Update, { unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { let old_value = unsafe { &mut *old_pointer }; match (old_value, new_value) { (Some(old), Some(new)) => unsafe { T::maybe_update(old, new) }, (None, None) => false, (old_value, new_value) => { *old_value = new_value; true } } } } unsafe impl Update for PhantomData { unsafe fn maybe_update(_old_pointer: *mut Self, _new_value: Self) -> bool { false } } salsa-0.26.2/src/views.rs000064400000000000000000000142341046102023000133140ustar 00000000000000use std::{ any::{Any, TypeId}, marker::PhantomData, mem, ptr::NonNull, }; use crate::{Database, database::RawDatabase}; /// A `Views` struct is associated with some specific database type /// (a `DatabaseImpl` for some existential `U`). It contains functions /// to downcast to `dyn DbView` for various traits `DbView` via this specific /// database type. /// None of these types are known at compilation time, they are all checked /// dynamically through `TypeId` magic. pub struct Views { source_type_id: TypeId, view_casters: boxcar::Vec, } #[derive(Copy, Clone)] struct ViewCaster { /// The id of the target type `dyn DbView` that we can cast to. target_type_id: TypeId, /// The name of the target type `dyn DbView` that we can cast to. type_name: &'static str, /// Type-erased function pointer that downcasts to `dyn DbView`. cast: ErasedDatabaseDownCasterSig, } impl ViewCaster { fn new(func: DatabaseDownCasterSig) -> ViewCaster { ViewCaster { target_type_id: TypeId::of::(), type_name: std::any::type_name::(), // SAFETY: We are type erasing for storage, taking care of unerasing before we call // the function pointer. cast: unsafe { mem::transmute::, ErasedDatabaseDownCasterSig>(func) }, } } } type ErasedDatabaseDownCasterSig = unsafe fn(RawDatabase<'_>) -> NonNull<()>; type DatabaseDownCasterSig = unsafe fn(RawDatabase<'_>) -> NonNull; #[repr(transparent)] pub struct DatabaseDownCaster(ViewCaster, PhantomData DbView>); impl Copy for DatabaseDownCaster {} impl Clone for DatabaseDownCaster { fn clone(&self) -> Self { *self } } impl DatabaseDownCaster { /// Downcast `db` to `DbView`. /// /// # Safety /// /// The caller must ensure that `db` is of the correct type. #[inline] pub unsafe fn downcast_unchecked<'db>(&self, db: RawDatabase<'db>) -> &'db DbView { // SAFETY: The caller must ensure that `db` is of the correct type. // The returned pointer is live for `'db` due to construction of the downcaster functions. unsafe { (self.unerased_downcaster())(db).as_ref() } } /// Downcast `db` to `DbView`. /// /// # Safety /// /// The caller must ensure that `db` is of the correct type. #[inline] pub unsafe fn downcast_mut_unchecked<'db>(&self, db: RawDatabase<'db>) -> &'db mut DbView { // SAFETY: The caller must ensure that `db` is of the correct type. // The returned pointer is live for `'db` due to construction of the downcaster functions. unsafe { (self.unerased_downcaster())(db).as_mut() } } #[inline] fn unerased_downcaster(&self) -> DatabaseDownCasterSig { // SAFETY: The type-erased function pointer is guaranteed to be ABI compatible for `DbView` unsafe { mem::transmute::>( self.0.cast, ) } } } impl Views { pub(crate) fn new() -> Self { let source_type_id = TypeId::of::(); let view_casters = boxcar::Vec::new(); view_casters.push(ViewCaster::new::(|db| db.ptr.cast::())); Self { source_type_id, view_casters, } } /// Add a new downcaster to `dyn DbView`. pub fn add( &self, func: fn(NonNull) -> NonNull, ) -> &DatabaseDownCaster { assert_eq!( self.source_type_id, TypeId::of::(), "mismatched source type" ); let target_type_id = TypeId::of::(); if let Some((_, caster)) = self .view_casters .iter() .find(|(_, u)| u.target_type_id == target_type_id) { // SAFETY: The type-erased function pointer is guaranteed to be valid for `DbView` return unsafe { &*(&raw const *caster).cast::>() }; } // SAFETY: We are type erasing the function pointer for storage, and we will unerase it // before we call it. let caster = unsafe { mem::transmute::) -> NonNull, DatabaseDownCasterSig>( func, ) }; let caster = ViewCaster::new::(caster); let idx = self.view_casters.push(caster); // SAFETY: The type-erased function pointer is guaranteed to be valid for `DbView` unsafe { &*(&raw const self.view_casters[idx]).cast::>() } } /// Retrieve an downcaster function to `dyn DbView`. /// /// # Panics /// /// If the underlying type of `db` is not the same as the database type this downcasts was created for. pub fn downcaster_for(&self) -> &DatabaseDownCaster { let view_type_id = TypeId::of::(); for (_, view) in self.view_casters.iter() { if view.target_type_id == view_type_id { // SAFETY: We are unerasing the type erased function pointer having made sure the // TypeId matches. return unsafe { &*((view as *const ViewCaster).cast::>()) }; } } panic!( "No downcaster registered for type `{}` in `Views`", std::any::type_name::(), ); } } impl std::fmt::Debug for Views { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Views") .field("view_casters", &self.view_casters) .finish() } } impl std::fmt::Debug for ViewCaster { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("DynViewCaster") .field(&self.type_name) .finish() } } salsa-0.26.2/src/zalsa.rs000064400000000000000000000447611046102023000133010ustar 00000000000000use std::any::{Any, TypeId}; use std::hash::BuildHasherDefault; use std::panic::RefUnwindSafe; use hashbrown::HashMap; use rustc_hash::FxHashMap; use crate::hash::TypeIdHasher; use crate::ingredient::{Ingredient, Jar}; use crate::plumbing::SalsaStructInDb; use crate::runtime::Runtime; use crate::table::Table; use crate::table::memo::MemoTableWithTypes; use crate::views::Views; use crate::zalsa_local::ZalsaLocal; use crate::{Database, Durability, Id, Revision}; /// Internal plumbing trait. /// /// [`ZalsaDatabase`] is created automatically when [`#[salsa::db]`](`crate::db`) /// is attached to a database struct. it Contains methods that give access /// to the internal data from the `storage` field. /// /// # Safety /// /// The system assumes this is implemented by a salsa procedural macro /// which makes use of private data from the [`Storage`](`crate::storage::Storage`) struct. /// Do not implement this yourself, instead, apply the [`#[salsa::db]`](`crate::db`) macro /// to your database. pub unsafe trait ZalsaDatabase: Any { /// Plumbing method: access both zalsa and zalsa-local at once. /// More efficient if you need both as it does only a single vtable dispatch. #[doc(hidden)] fn zalsas(&self) -> (&Zalsa, &ZalsaLocal) { (self.zalsa(), self.zalsa_local()) } /// Plumbing method: Access the internal salsa methods. #[doc(hidden)] fn zalsa(&self) -> &Zalsa; /// Plumbing method: Access the internal salsa methods for mutating the database. /// /// **WARNING:** Triggers cancellation to other database handles. /// This can lead to deadlock! #[doc(hidden)] fn zalsa_mut(&mut self) -> &mut Zalsa; /// Access the thread-local state associated with this database #[doc(hidden)] fn zalsa_local(&self) -> &ZalsaLocal; } pub fn views(db: &Db) -> &Views { db.zalsa().views() } /// Nonce type representing the underlying database storage. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] #[cfg(not(feature = "inventory"))] pub struct StorageNonce; // Generator for storage nonces. #[cfg(not(feature = "inventory"))] static NONCE: crate::nonce::NonceGenerator = crate::nonce::NonceGenerator::new(); /// An ingredient index identifies a particular [`Ingredient`] in the database. /// /// The database contains a number of jars, and each jar contains a number of ingredients. /// Each ingredient is given a unique index as the database is being created. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "persistence", serde(transparent))] pub struct IngredientIndex(u32); impl IngredientIndex { /// The maximum supported ingredient index. /// /// This reserves one bit for an optional tag. const MAX_INDEX: u32 = 0x7FFF_FFFF; /// Create an ingredient index from a `u32`. pub(crate) fn new(v: u32) -> Self { assert!(v <= Self::MAX_INDEX); Self(v) } /// Create an ingredient index from a `u32`, without performing validating /// that the index is valid. /// /// # Safety /// /// The index must be less than or equal to `IngredientIndex::MAX_INDEX`. pub(crate) unsafe fn new_unchecked(v: u32) -> Self { Self(v) } /// Convert the ingredient index back into a `u32`. pub(crate) fn as_u32(self) -> u32 { self.0 } pub fn successor(self, index: usize) -> Self { IngredientIndex(self.0 + 1 + index as u32) } /// Returns a new `IngredientIndex` with the tag bit set to the provided value. pub(crate) fn with_tag(mut self, tag: bool) -> IngredientIndex { self.0 &= Self::MAX_INDEX; self.0 |= (tag as u32) << 31; self } /// Returns the value of the tag bit. pub(crate) fn tag(self) -> bool { self.0 & !Self::MAX_INDEX != 0 } } /// A special secondary index *just* for ingredients that attach /// "memos" to salsa structs (currently: just tracked functions). #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct MemoIngredientIndex(u32); impl MemoIngredientIndex { pub(crate) fn from_usize(u: usize) -> Self { assert!(u <= u32::MAX as usize); MemoIngredientIndex(u as u32) } #[inline] pub(crate) fn as_usize(self) -> usize { self.0 as usize } } /// The "plumbing interface" to the Salsa database. Stores all the ingredients and other data. /// /// **NOT SEMVER STABLE.** pub struct Zalsa { views_of: Views, #[cfg(not(feature = "inventory"))] nonce: crate::nonce::Nonce, /// Map from the [`IngredientIndex::as_usize`][] of a salsa struct to a list of /// [ingredient-indices](`IngredientIndex`) for tracked functions that have this salsa struct /// as input. memo_ingredient_indices: Vec>, /// Map from the type-id of an `impl Jar` to the index of its first ingredient. jar_map: HashMap>, /// A map from the `IngredientIndex` to the `TypeId` of its ID struct. /// /// Notably this is not the reverse mapping of `jar_map`. ingredient_to_id_struct_type_id_map: FxHashMap, /// Vector of ingredients. ingredients_vec: Vec>, /// Indices of ingredients that require reset when a new revision starts. ingredients_requiring_reset: Vec, /// The runtime for this particular salsa database handle. /// Each handle gets its own runtime, but the runtimes have shared state between them. runtime: Runtime, event_callback: Option>, } /// All fields on Zalsa are locked behind [`Mutex`]es and [`RwLock`]s and cannot enter /// inconsistent states. The contents of said fields are largely ID mappings, with the exception /// of [`Runtime::dependency_graph`]. However, [`Runtime::dependency_graph`] does not /// invoke any queries and as such there will be no panic from code downstream of Salsa. It can only /// panic if an assertion inside of Salsa fails. impl RefUnwindSafe for Zalsa {} impl Zalsa { pub(crate) fn new( event_callback: Option>, jars: Vec, ) -> Self { let mut zalsa = Self { views_of: Views::new::(), jar_map: HashMap::default(), ingredient_to_id_struct_type_id_map: Default::default(), ingredients_vec: Vec::new(), ingredients_requiring_reset: Vec::new(), runtime: Runtime::default(), memo_ingredient_indices: Default::default(), event_callback, #[cfg(not(feature = "inventory"))] nonce: NONCE.nonce(), }; // Collect and initialize all registered ingredients. #[cfg(feature = "inventory")] let mut jars = inventory::iter::() .copied() .chain(jars) .collect::>(); #[cfg(not(feature = "inventory"))] let mut jars = jars; // Ensure structs are initialized before tracked functions. // // We also further sort by debug name, to maintain a consistent ordering across // builds. jars.sort_by(|a, b| a.kind.cmp(&b.kind).then(a.type_name().cmp(b.type_name()))); for jar in jars { zalsa.insert_jar(jar); } zalsa } #[cfg(not(feature = "inventory"))] pub(crate) fn nonce(&self) -> crate::nonce::Nonce { self.nonce } pub(crate) fn runtime(&self) -> &Runtime { &self.runtime } pub(crate) fn runtime_mut(&mut self) -> &mut Runtime { &mut self.runtime } /// Returns the [`Table`] used to store the value of salsa structs #[inline] pub fn table(&self) -> &Table { self.runtime.table() } /// Returns a mutable reference to the [`Table`] used to store the value of salsa structs #[inline] #[allow(dead_code)] pub(crate) fn table_mut(&mut self) -> &mut Table { self.runtime.table_mut() } /// Returns the [`MemoTable`][] for the salsa struct with the given id pub(crate) fn memo_table_for(&self, id: Id) -> MemoTableWithTypes<'_> { // SAFETY: We are supplying the correct current revision. unsafe { T::memo_table(self, id, self.current_revision()) } } /// Returns the ingredient at the given index, or panics if it is out-of-bounds. #[inline] pub fn lookup_ingredient(&self, index: IngredientIndex) -> &dyn Ingredient { self.ingredients_vec[index.as_u32() as usize].as_ref() } /// Returns the ingredient at the given index. /// /// # Safety /// /// The index must be in-bounds. #[inline] pub unsafe fn lookup_ingredient_unchecked(&self, index: IngredientIndex) -> &dyn Ingredient { // SAFETY: Guaranteed by caller. unsafe { self.ingredients_vec .get_unchecked(index.as_u32() as usize) .as_ref() } } pub(crate) fn ingredient_index_for_memo( &self, struct_ingredient_index: IngredientIndex, memo_ingredient_index: MemoIngredientIndex, ) -> IngredientIndex { self.memo_ingredient_indices[struct_ingredient_index.as_u32() as usize] [memo_ingredient_index.as_usize()] } #[allow(unused)] pub(crate) fn ingredients(&self) -> impl Iterator { self.ingredients_vec .iter() .map(|ingredient| ingredient.as_ref()) } /// Starts unwinding the stack if the current revision is cancelled. /// /// This method can be called by query implementations that perform /// potentially expensive computations, in order to speed up propagation of /// cancellation. /// /// Cancellation will automatically be triggered by salsa on any query /// invocation. #[inline] pub(crate) fn unwind_if_revision_cancelled(&self, zalsa_local: &ZalsaLocal) { self.event(&|| crate::Event::new(crate::EventKind::WillCheckCancellation)); if zalsa_local.should_trigger_local_cancellation() { zalsa_local.unwind_cancelled(); } if self.runtime().load_cancellation_flag() { zalsa_local.unwind_pending_write(); } } pub(crate) fn next_memo_ingredient_index( &mut self, struct_ingredient_index: IngredientIndex, ingredient_index: IngredientIndex, ) -> MemoIngredientIndex { let memo_ingredients = &mut self.memo_ingredient_indices; let idx = struct_ingredient_index.as_u32() as usize; let memo_ingredients = if let Some(memo_ingredients) = memo_ingredients.get_mut(idx) { memo_ingredients } else { memo_ingredients.resize_with(idx + 1, Vec::new); memo_ingredients.get_mut(idx).unwrap() }; let mi = MemoIngredientIndex::from_usize(memo_ingredients.len()); memo_ingredients.push(ingredient_index); mi } } /// Semver unstable APIs used by the macro expansions impl Zalsa { /// **NOT SEMVER STABLE** pub fn views(&self) -> &Views { &self.views_of } /// **NOT SEMVER STABLE** #[inline] pub fn lookup_page_type_id(&self, id: Id) -> TypeId { let ingredient_index = self.ingredient_index(id); *self .ingredient_to_id_struct_type_id_map .get(&ingredient_index) .expect("should have the ingredient index available") } /// **NOT SEMVER STABLE** #[doc(hidden)] #[inline] pub fn lookup_jar_by_type(&self) -> IngredientIndex { let jar_type_id = TypeId::of::(); *self.jar_map.get(&jar_type_id).unwrap_or_else(|| { panic!( "ingredient `{}` was not registered", std::any::type_name::() ) }) } fn insert_jar(&mut self, jar: ErasedJar) { let jar_type_id = (jar.type_id)(); let index = IngredientIndex::new(self.ingredients_vec.len() as u32); if self.jar_map.contains_key(&jar_type_id) { return; } let ingredients = (jar.create_ingredients)(self, index); for ingredient in ingredients { let expected_index = ingredient.ingredient_index(); if ingredient.requires_reset_for_new_revision() { self.ingredients_requiring_reset.push(expected_index); } self.ingredients_vec.push(ingredient); let actual_index = self.ingredients_vec.len() - 1; assert_eq!( expected_index.as_u32() as usize, actual_index, "ingredient `{:?}` was predicted to have index `{:?}` but actually has index `{:?}`", self.ingredients_vec[actual_index], expected_index.as_u32(), actual_index, ); } self.jar_map.insert(jar_type_id, index); self.ingredient_to_id_struct_type_id_map .insert(index, (jar.id_struct_type_id)()); } /// **NOT SEMVER STABLE** #[doc(hidden)] pub fn lookup_ingredient_mut( &mut self, index: IngredientIndex, ) -> (&mut dyn Ingredient, &mut Runtime) { let index = index.as_u32() as usize; let ingredient = self .ingredients_vec .get_mut(index) .unwrap_or_else(|| panic!("index `{index}` is uninitialized")); (ingredient.as_mut(), &mut self.runtime) } /// **NOT SEMVER STABLE** #[doc(hidden)] pub fn take_ingredient(&mut self, index: IngredientIndex) -> Box { self.ingredients_vec.remove(index.as_u32() as usize) } /// **NOT SEMVER STABLE** #[doc(hidden)] pub fn replace_ingredient(&mut self, index: IngredientIndex, ingredient: Box) { self.ingredients_vec .insert(index.as_u32() as usize, ingredient); } /// **NOT SEMVER STABLE** #[doc(hidden)] #[inline] pub fn current_revision(&self) -> Revision { self.runtime.current_revision() } /// **NOT SEMVER STABLE** #[doc(hidden)] #[inline] pub fn last_changed_revision(&self, durability: Durability) -> Revision { self.runtime.last_changed_revision(durability) } /// **NOT SEMVER STABLE** /// Triggers a new revision. #[doc(hidden)] pub fn new_revision(&mut self) -> Revision { let new_revision = self.runtime.new_revision(); let _span = crate::tracing::debug_span!("new_revision", ?new_revision).entered(); for ingredient in &self.ingredients_requiring_reset { self.ingredients_vec[ingredient.as_u32() as usize] .reset_for_new_revision(self.runtime.table_mut()); } new_revision } /// **NOT SEMVER STABLE** #[doc(hidden)] pub fn evict_lru(&mut self) { let _span = crate::tracing::debug_span!("evict_lru").entered(); for ingredient in &self.ingredients_requiring_reset { self.ingredients_vec[ingredient.as_u32() as usize] .reset_for_new_revision(self.runtime.table_mut()); } } #[inline] pub fn ingredient_index(&self, id: Id) -> IngredientIndex { self.table().ingredient_index(id) } #[inline(always)] pub fn event(&self, event: &dyn Fn() -> crate::Event) { if self.event_callback.is_some() { self.event_cold(event); } } // Avoid inlining, as events are typically only enabled for debugging purposes. #[cold] #[inline(never)] pub fn event_cold(&self, event: &dyn Fn() -> crate::Event) { let event_callback = self.event_callback.as_ref().unwrap(); event_callback(event()); } } /// A type-erased `Jar`, used for ingredient registration. #[derive(Clone, Copy)] pub struct ErasedJar { kind: JarKind, type_id: fn() -> TypeId, type_name: fn() -> &'static str, id_struct_type_id: fn() -> TypeId, create_ingredients: fn(&mut Zalsa, IngredientIndex) -> Vec>, } /// The kind of an `Jar`. /// /// Note that the ordering of the variants is important. Struct ingredients must be /// initialized before tracked functions, as tracked function ingredients depend on /// their input struct. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)] pub enum JarKind { /// An input/tracked/interned struct. Struct, /// A tracked function. TrackedFn, } impl ErasedJar { /// Performs type-erasure of a given ingredient. pub const fn erase() -> Self { Self { kind: I::KIND, // This is a false positive of the lint on beta, fixed on nightly. // FIXME: Remove this when nightly stabilizes. #[allow(clippy::incompatible_msrv)] type_id: TypeId::of::, type_name: std::any::type_name::, create_ingredients: ::create_ingredients, id_struct_type_id: ::id_struct_type_id, } } pub fn type_name(&self) -> &'static str { (self.type_name)() } } /// A salsa ingredient that can be registered in the database. /// /// This trait is implemented for tracked functions and salsa structs. pub trait HasJar { /// The [`Jar`] associated with this ingredient. type Jar: Jar; /// The [`JarKind`] for `Self::Jar`. const KIND: JarKind; } // Collect jars statically at compile-time if supported. #[cfg(feature = "inventory")] inventory::collect!(ErasedJar); #[cfg(feature = "inventory")] pub use inventory::submit as register_jar; #[cfg(not(feature = "inventory"))] #[macro_export] #[doc(hidden)] macro_rules! register_jar { ($($_:tt)*) => {}; } #[cfg(not(feature = "inventory"))] pub use crate::register_jar; /// Given a wide pointer `T`, extracts the data pointer (typed as `U`). /// /// # Safety /// /// `U` must be correct type for the data pointer. pub unsafe fn transmute_data_ptr(t: &T) -> &U { let t: *const T = t; let u: *const U = t as *const U; // SAFETY: the caller must guarantee that `T` is a wide pointer for `U` unsafe { &*u } } /// Given a wide pointer `T`, extracts the data pointer (typed as `U`). /// /// # Safety /// /// `U` must be correct type for the data pointer. pub(crate) unsafe fn transmute_data_mut_ptr(t: &mut T) -> &mut U { let t: *mut T = t; let u: *mut U = t as *mut U; // SAFETY: the caller must guarantee that `T` is a wide pointer for `U` unsafe { &mut *u } } salsa-0.26.2/src/zalsa_local.rs000064400000000000000000001445241046102023000144510ustar 00000000000000use std::cell::{RefCell, UnsafeCell}; use std::fmt; use std::fmt::Formatter; use std::panic::UnwindSafe; use std::ptr::{self, NonNull}; use std::sync::Arc; use std::sync::atomic::{AtomicU8, Ordering}; use rustc_hash::FxHashMap; use thin_vec::ThinVec; #[cfg(feature = "accumulator")] use crate::accumulator::{ Accumulator, accumulated_map::{AccumulatedMap, AtomicInputAccumulatedValues}, }; use crate::active_query::{CompletedQuery, QueryStack}; use crate::cycle::{AtomicIterationCount, CycleHeads, IterationCount, empty_cycle_heads}; use crate::durability::Durability; use crate::key::DatabaseKeyIndex; use crate::runtime::Stamp; use crate::sync::atomic::AtomicBool; use crate::table::{PageIndex, Slot, Table}; use crate::tracked_struct::{Disambiguator, Identity, IdentityHash}; use crate::zalsa::{IngredientIndex, Zalsa}; use crate::{Cancelled, Id, Revision}; /// State that is specific to a single execution thread. /// /// Internally, this type uses ref-cells. /// /// **Note also that all mutations to the database handle (and hence /// to the local-state) must be undone during unwinding.** pub struct ZalsaLocal { /// Vector of active queries. /// /// Unwinding note: pushes onto this vector must be popped -- even /// during unwinding. query_stack: RefCell, /// Stores the most recent page for a given ingredient. /// This is thread-local to avoid contention. most_recent_pages: UnsafeCell>, cancelled: CancellationToken, } /// A cancellation token that can be used to cancel a query computation for a specific local `Database`. #[derive(Default, Clone, Debug)] pub struct CancellationToken(Arc); impl CancellationToken { const CANCELLED_MASK: u8 = 0b01; const DISABLED_MASK: u8 = 0b10; /// Inform the database to cancel the current query computation. pub fn cancel(&self) { self.0.fetch_or(Self::CANCELLED_MASK, Ordering::Relaxed); } /// Check if the query computation has been requested to be cancelled. pub fn is_cancelled(&self) -> bool { self.0.load(Ordering::Relaxed) & Self::CANCELLED_MASK != 0 } #[inline] fn set_cancellation_disabled(&self, disabled: bool) -> bool { let previous_disabled_bit = if disabled { self.0.fetch_or(Self::DISABLED_MASK, Ordering::Relaxed) } else { self.0.fetch_and(!Self::DISABLED_MASK, Ordering::Relaxed) }; previous_disabled_bit & Self::DISABLED_MASK != 0 } fn should_trigger_local_cancellation(&self) -> bool { self.0.load(Ordering::Relaxed) == Self::CANCELLED_MASK } fn reset(&self) { self.0.store(0, Ordering::Relaxed); } } impl ZalsaLocal { pub(crate) fn new() -> Self { ZalsaLocal { query_stack: RefCell::new(QueryStack::default()), most_recent_pages: UnsafeCell::new(FxHashMap::default()), cancelled: CancellationToken::default(), } } pub(crate) fn record_unfilled_pages(&mut self, table: &Table) { let most_recent_pages = self.most_recent_pages.get_mut(); most_recent_pages .drain() .for_each(|(ingredient, page)| table.record_unfilled_page(ingredient, page)); } /// Allocate a new id in `table` for the given ingredient /// storing `value`. Remembers the most recent page from this /// thread and attempts to reuse it. pub(crate) fn allocate<'db, T: Slot>( &self, zalsa: &'db Zalsa, ingredient: IngredientIndex, mut value: impl FnOnce(Id) -> T, ) -> (Id, &'db T) { // SAFETY: `ZalsaLocal` is `!Sync`, and we never expose a reference to this field, // so we have exclusive access. let most_recent_pages = unsafe { &mut *self.most_recent_pages.get() }; // Fast-path, we already have an unfilled page available. if let Some(&page) = most_recent_pages.get(&ingredient) { let page_ref = zalsa.table().page::(page); // SAFETY: `ZalsaLocal` is `!Sync`, and we only insert a page into `most_recent_pages` // if it was allocated by our thread, so we are the unique writer. match unsafe { page_ref.allocate(page, value) } { Ok((id, value)) => return (id, value), Err(v) => value = v, } } self.allocate_cold(zalsa, ingredient, value) } #[cold] #[inline(never)] pub(crate) fn allocate_cold<'db, T: Slot>( &self, zalsa: &'db Zalsa, ingredient: IngredientIndex, mut value: impl FnOnce(Id) -> T, ) -> (Id, &'db T) { let memo_types = || { zalsa .lookup_ingredient(ingredient) .memo_table_types() .clone() }; // SAFETY: `ZalsaLocal` is `!Sync`, and we never expose a reference to this field, // so we have exclusive access. let most_recent_pages = unsafe { &mut *self.most_recent_pages.get() }; // Find the most recent page, pushing a page if needed let mut page = *most_recent_pages.entry(ingredient).or_insert_with(|| { zalsa .table() .fetch_or_push_page::(ingredient, memo_types) }); loop { // Try to allocate an entry on that page let page_ref = zalsa.table().page::(page); // SAFETY: `ZalsaLocal` is `!Sync`, and we only insert a page into `most_recent_pages` // if it was allocated by our thread, so we are the unique writer. match unsafe { page_ref.allocate(page, value) } { // If successful, return Ok((id, value)) => return (id, value), // Otherwise, create a new page and try again. // // Note that we could try fetching a page again, but as we just filled one up // it is unlikely that there is a non-full one available. Err(v) => { value = v; page = zalsa.table().push_page::(ingredient, memo_types()); most_recent_pages.insert(ingredient, page); } } } } #[inline] pub(crate) fn push_query( &self, database_key_index: DatabaseKeyIndex, iteration_count: IterationCount, ) -> ActiveQueryGuard<'_> { // SAFETY: We do not access the query stack reentrantly. unsafe { self.with_query_stack_unchecked_mut(|stack| { stack.push_new_query(database_key_index, iteration_count); ActiveQueryGuard { local_state: self, database_key_index, #[cfg(debug_assertions)] push_len: stack.len(), } }) } } /// Executes a closure within the context of the current active query stacks (mutable). /// /// # Safety /// /// The closure cannot access the query stack reentrantly, whether mutable or immutable. #[inline(always)] pub(crate) unsafe fn with_query_stack_unchecked_mut( &self, f: impl UnwindSafe + FnOnce(&mut QueryStack) -> R, ) -> R { // SAFETY: The caller guarantees that the query stack will not be accessed reentrantly. // Additionally, `ZalsaLocal` is `!Sync`, and we never expose a reference to the query // stack except through this method, so we have exclusive access. unsafe { f(&mut self.query_stack.try_borrow_mut().unwrap_unchecked()) } } /// Executes a closure within the context of the current active query stacks. /// /// # Safety /// /// No mutable references to the query stack can exist while the closure is executed. #[inline(always)] pub(crate) unsafe fn with_query_stack_unchecked( &self, f: impl UnwindSafe + FnOnce(&QueryStack) -> R, ) -> R { // SAFETY: The caller guarantees that the query stack will not being accessed mutably. // Additionally, `ZalsaLocal` is `!Sync`, and we never expose a reference to the query // stack except through this method, so we have exclusive access. unsafe { f(&self.query_stack.try_borrow().unwrap_unchecked()) } } #[inline(always)] pub(crate) fn try_with_query_stack( &self, f: impl UnwindSafe + FnOnce(&QueryStack) -> R, ) -> Option { self.query_stack .try_borrow() .ok() .as_ref() .map(|stack| f(stack)) } /// Returns the index of the active query along with its *current* durability/changed-at /// information. As the query continues to execute, naturally, that information may change. pub(crate) fn active_query(&self) -> Option<(DatabaseKeyIndex, Stamp)> { // SAFETY: We do not access the query stack reentrantly. unsafe { self.with_query_stack_unchecked(|stack| { stack .last() .map(|active_query| (active_query.database_key_index, active_query.stamp())) }) } } /// Add an output to the current query's list of dependencies /// /// Returns `Err` if not in a query. #[cfg(feature = "accumulator")] pub(crate) fn accumulate( &self, index: IngredientIndex, value: A, ) -> Result<(), ()> { // SAFETY: We do not access the query stack reentrantly. unsafe { self.with_query_stack_unchecked_mut(|stack| { if let Some(top_query) = stack.last_mut() { top_query.accumulate(index, value); Ok(()) } else { Err(()) } }) } } /// Add an output to the current query's list of dependencies pub(crate) fn add_output(&self, entity: DatabaseKeyIndex) { // SAFETY: We do not access the query stack reentrantly. unsafe { self.with_query_stack_unchecked_mut(|stack| { if let Some(top_query) = stack.last_mut() { top_query.add_output(entity) } }) } } /// Check whether `entity` is a tracked struct that was created by the currently active query (if any) pub(crate) fn is_tracked_struct_of_active_query(&self, entity: DatabaseKeyIndex) -> bool { // SAFETY: We do not access the query stack reentrantly. unsafe { self.with_query_stack_unchecked_mut(|stack| { stack .last_mut() .is_some_and(|top_query| top_query.tracked_struct_ids().is_active(entity)) }) } } /// Register that currently active query reads the given input #[inline(always)] pub(crate) fn report_tracked_read( &self, input: DatabaseKeyIndex, durability: Durability, changed_at: Revision, cycle_heads: &CycleHeads, #[cfg(feature = "accumulator")] has_accumulated: bool, #[cfg(feature = "accumulator")] accumulated_inputs: &AtomicInputAccumulatedValues, ) { crate::tracing::debug!( "report_tracked_read(input={:?}, durability={:?}, changed_at={:?})", input, durability, changed_at ); // SAFETY: We do not access the query stack reentrantly. unsafe { self.with_query_stack_unchecked_mut(|stack| { if let Some(top_query) = stack.last_mut() { top_query.add_read( input, durability, changed_at, cycle_heads, #[cfg(feature = "accumulator")] has_accumulated, #[cfg(feature = "accumulator")] accumulated_inputs, ); } }) } } /// Register that currently active query reads the given input #[inline(always)] pub(crate) fn report_tracked_read_simple( &self, input: DatabaseKeyIndex, durability: Durability, changed_at: Revision, ) { crate::tracing::debug!( "report_tracked_read(input={:?}, durability={:?}, changed_at={:?})", input, durability, changed_at ); // SAFETY: We do not access the query stack reentrantly. unsafe { self.with_query_stack_unchecked_mut(|stack| { if let Some(top_query) = stack.last_mut() { top_query.add_read_simple(input, durability, changed_at); } }) } } /// Register that the current query read an untracked value /// /// # Parameters /// /// * `current_revision`, the current revision #[inline(always)] pub(crate) fn report_untracked_read(&self, current_revision: Revision) { // SAFETY: We do not access the query stack reentrantly. unsafe { self.with_query_stack_unchecked_mut(|stack| { if let Some(top_query) = stack.last_mut() { top_query.add_untracked_read(current_revision); } }) } } /// Called when the active queries creates an index from the /// entity table with the index `entity_index`. Has the following effects: /// /// * Add a query read on `DatabaseKeyIndex::for_table(entity_index)` /// * Identify a unique disambiguator for the hash within the current query, /// adding the hash to the current query's disambiguator table. /// * Returns a tuple of: /// * the id of the current query /// * the current dependencies (durability, changed_at) of current query /// * the disambiguator index #[track_caller] pub(crate) fn disambiguate(&self, key: IdentityHash) -> (Stamp, Disambiguator) { // SAFETY: We do not access the query stack reentrantly. unsafe { self.with_query_stack_unchecked_mut(|stack| { let top_query = stack.last_mut().expect( "cannot create a tracked struct disambiguator outside of a tracked function", ); let disambiguator = top_query.disambiguate(key); (top_query.stamp(), disambiguator) }) } } #[track_caller] pub(crate) fn tracked_struct_id(&self, identity: &Identity) -> Option { // SAFETY: We do not access the query stack reentrantly. unsafe { self.with_query_stack_unchecked_mut(|stack| { let top_query = stack .last_mut() .expect("cannot create a tracked struct ID outside of a tracked function"); top_query.tracked_struct_ids_mut().reuse(identity) }) } } #[track_caller] pub(crate) fn store_tracked_struct_id(&self, identity: Identity, id: Id) { // SAFETY: We do not access the query stack reentrantly. unsafe { self.with_query_stack_unchecked_mut(|stack| { let top_query = stack .last_mut() .expect("cannot store a tracked struct ID outside of a tracked function"); top_query.tracked_struct_ids_mut().insert(identity, id); }) } } #[inline] pub(crate) fn cancellation_token(&self) -> CancellationToken { self.cancelled.clone() } #[inline] pub(crate) fn uncancel(&self) { self.cancelled.reset(); } #[inline] pub fn should_trigger_local_cancellation(&self) -> bool { self.cancelled.should_trigger_local_cancellation() } #[cold] pub(crate) fn unwind_pending_write(&self) { Cancelled::PendingWrite.throw(); } #[cold] pub(crate) fn unwind_cancelled(&self) { Cancelled::Local.throw(); } #[inline] pub(crate) fn set_cancellation_disabled(&self, was_disabled: bool) -> bool { self.cancelled.set_cancellation_disabled(was_disabled) } } // Okay to implement as `ZalsaLocal`` is !Sync // - `most_recent_pages` can't observe broken states as we cannot panic such that we enter an // inconsistent state // - neither can `query_stack` as we require the closures accessing it to be `UnwindSafe` impl std::panic::RefUnwindSafe for ZalsaLocal {} /// Summarizes "all the inputs that a query used" and "all the outputs it has written to". #[derive(Debug)] #[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] // #[derive(Clone)] cloning this is expensive, so we don't derive pub(crate) struct QueryRevisions { /// The most revision in which some input changed. pub(crate) changed_at: Revision, /// Minimum durability of the inputs to this query. pub(crate) durability: Durability, /// How was this query computed? pub(crate) origin: QueryOrigin, /// [`InputAccumulatedValues::Empty`] if any input read during the query's execution /// has any direct or indirect accumulated values. /// /// Note that this field could be in `QueryRevisionsExtra` as it is only relevant /// for accumulators, but we get it for free anyways due to padding. #[cfg(feature = "accumulator")] #[cfg_attr(feature = "persistence", serde(skip))] // TODO: Support serializing accumulators pub(super) accumulated_inputs: AtomicInputAccumulatedValues, /// Are the `cycle_heads` verified to not be provisional anymore? /// /// Note that this field could be in `QueryRevisionsExtra` as it is only /// relevant for queries that participate in a cycle, but we get it for /// free anyways due to padding. #[cfg_attr(feature = "persistence", serde(with = "persistence::verified_final"))] pub(super) verified_final: AtomicBool, /// Lazily allocated state. pub(super) extra: QueryRevisionsExtra, } impl QueryRevisions { #[cfg(feature = "salsa_unstable")] pub(crate) fn allocation_size(&self) -> usize { let QueryRevisions { changed_at: _, durability: _, verified_final: _, origin, extra, #[cfg(feature = "accumulator")] accumulated_inputs: _, } = self; let mut memory = 0; if let QueryOriginRef::Derived(query_edges) | QueryOriginRef::DerivedUntracked(query_edges) = origin.as_ref() { memory += std::mem::size_of_val(query_edges); } if let Some(extra) = extra.0.as_ref() { memory += std::mem::size_of::(); memory += extra.allocation_size(); } memory } } /// Data on `QueryRevisions` that is lazily allocated to save memory /// in the common case. /// /// In particular, not all queries create tracked structs, participate /// in cycles, or create accumulators. #[derive(Debug, Default)] #[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "persistence", serde(transparent))] pub(crate) struct QueryRevisionsExtra(Option>); impl QueryRevisionsExtra { pub fn new( #[cfg(feature = "accumulator")] accumulated: AccumulatedMap, mut tracked_struct_ids: ThinVec<(Identity, Id)>, cycle_heads: CycleHeads, iteration: IterationCount, ) -> Self { #[cfg(feature = "accumulator")] let acc = accumulated.is_empty(); #[cfg(not(feature = "accumulator"))] let acc = true; let inner = if acc && tracked_struct_ids.is_empty() && cycle_heads.is_empty() && iteration.is_initial() { None } else { tracked_struct_ids.shrink_to_fit(); Some(Box::new(QueryRevisionsExtraInner { #[cfg(feature = "accumulator")] accumulated, cycle_heads, tracked_struct_ids, iteration: iteration.into(), cycle_converged: false, })) }; Self(inner) } } #[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] struct QueryRevisionsExtraInner { #[cfg(feature = "accumulator")] #[cfg_attr(feature = "persistence", serde(skip))] // TODO: Support serializing accumulators accumulated: AccumulatedMap, /// The ids of tracked structs created by this query. /// /// This table plays an important role when queries are /// re-executed: /// * A clone of this field is used as the initial set of /// `TrackedStructId`s for the query on the next execution. /// * The query will thus re-use the same ids if it creates /// tracked structs with the same `KeyStruct` as before. /// It may also create new tracked structs. /// * One tricky case involves deleted structs. If /// the old revision created a struct S but the new /// revision did not, there will still be a map entry /// for S. This is because queries only ever grow the map /// and they start with the same entries as from the /// previous revision. To handle this, `diff_outputs` compares /// the structs from the old/new revision and retains /// only entries that appeared in the new revision. // // TODO: We only need to serialize the IDs of tracked structs that // are actually going to be serialized. Those that are not will // be created with new IDs anyways. tracked_struct_ids: ThinVec<(Identity, Id)>, /// This result was computed based on provisional values from /// these cycle heads. The "cycle head" is the query responsible /// for managing a fixpoint iteration. In a cycle like /// `--> A --> B --> C --> A`, the cycle head is query `A`: it is /// the query whose value is requested while it is executing, /// which must provide the initial provisional value and decide, /// after each iteration, whether the cycle has converged or must /// iterate again. cycle_heads: CycleHeads, iteration: AtomicIterationCount, /// Stores for nested cycle heads whether they've converged in the last iteration. /// This value is always `false` for other queries. #[cfg_attr(feature = "persistence", serde(skip))] cycle_converged: bool, } impl QueryRevisionsExtraInner { #[cfg(feature = "salsa_unstable")] fn allocation_size(&self) -> usize { let QueryRevisionsExtraInner { #[cfg(feature = "accumulator")] accumulated, tracked_struct_ids, cycle_heads, iteration: _, cycle_converged: _, } = self; #[cfg(feature = "accumulator")] let b = accumulated.allocation_size(); #[cfg(not(feature = "accumulator"))] let b = 0; b + cycle_heads.allocation_size() + std::mem::size_of_val(tracked_struct_ids.as_slice()) } } impl fmt::Debug for QueryRevisionsExtraInner { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { struct FmtTrackedStructIds<'a>(&'a ThinVec<(Identity, Id)>); impl fmt::Debug for FmtTrackedStructIds<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut f = f.debug_list(); if self.0.len() > 5 { f.entries(&self.0[..5]); f.finish_non_exhaustive() } else { f.entries(self.0); f.finish() } } } let mut f = f.debug_struct("QueryRevisionsExtraInner"); f.field("cycle_heads", &self.cycle_heads) .field("iteration", &self.iteration) .field("cycle_converged", &self.cycle_converged); #[cfg(feature = "accumulator")] { f.field("accumulated", &self.accumulated); } f.field( "tracked_struct_ids", &FmtTrackedStructIds(&self.tracked_struct_ids), ); f.finish() } } #[cfg(not(feature = "shuttle"))] #[cfg(target_pointer_width = "64")] const _: [(); std::mem::size_of::()] = [(); std::mem::size_of::<[usize; 4]>()]; #[cfg(not(feature = "shuttle"))] #[cfg(target_pointer_width = "64")] const _: [(); std::mem::size_of::()] = [(); std::mem::size_of::<[usize; if cfg!(feature = "accumulator") { 7 } else { 3 }]>()]; impl QueryRevisions { pub(crate) fn fixpoint_initial(query: DatabaseKeyIndex, iteration: IterationCount) -> Self { Self { changed_at: Revision::start(), durability: Durability::MAX, origin: QueryOrigin::derived(Box::default()), #[cfg(feature = "accumulator")] accumulated_inputs: Default::default(), verified_final: AtomicBool::new(false), extra: QueryRevisionsExtra::new( #[cfg(feature = "accumulator")] AccumulatedMap::default(), ThinVec::default(), CycleHeads::initial(query, iteration), iteration, ), } } /// Returns a reference to the `AccumulatedMap` for this query, or `None` if the map is empty. #[cfg(feature = "accumulator")] pub(crate) fn accumulated(&self) -> Option<&AccumulatedMap> { self.extra .0 .as_deref() .map(|extra| &extra.accumulated) .filter(|map| !map.is_empty()) } /// Returns a reference to the `CycleHeads` for this query. pub(crate) fn cycle_heads(&self) -> &CycleHeads { match &self.extra.0 { Some(extra) => &extra.cycle_heads, None => empty_cycle_heads(), } } /// Sets the `CycleHeads` for this query. pub(crate) fn set_cycle_heads(&mut self, cycle_heads: CycleHeads) { match &mut self.extra.0 { Some(extra) => extra.cycle_heads = cycle_heads, None => { self.extra = QueryRevisionsExtra::new( #[cfg(feature = "accumulator")] AccumulatedMap::default(), ThinVec::default(), cycle_heads, IterationCount::default(), ); } }; } pub(crate) fn cycle_converged(&self) -> bool { match &self.extra.0 { Some(extra) => extra.cycle_converged, None => false, } } pub(crate) fn set_cycle_converged(&mut self, cycle_converged: bool) { if let Some(extra) = &mut self.extra.0 { extra.cycle_converged = cycle_converged } } pub(crate) fn iteration(&self) -> IterationCount { match &self.extra.0 { Some(extra) => extra.iteration.load(), None => IterationCount::initial(), } } pub(crate) fn set_iteration_count( &self, database_key_index: DatabaseKeyIndex, iteration_count: IterationCount, ) { let Some(extra) = &self.extra.0 else { return; }; debug_assert!(extra.iteration.load() <= iteration_count); extra.iteration.store(iteration_count); extra .cycle_heads .update_iteration_count(database_key_index, iteration_count); } fn get_or_insert_extra(&mut self) -> &mut QueryRevisionsExtraInner { self.extra.0.get_or_insert_with(|| { Box::new(QueryRevisionsExtraInner { #[cfg(feature = "accumulator")] accumulated: AccumulatedMap::default(), tracked_struct_ids: ThinVec::default(), cycle_heads: empty_cycle_heads().clone(), iteration: IterationCount::default().into(), cycle_converged: false, }) }) } fn extra(&self) -> Option<&QueryRevisionsExtraInner> { self.extra.0.as_deref() } /// Updates the iteration count of the memo without updating the iteration in `cycle_heads`. /// /// Don't call this method on a cycle head, as it results in diverging iteration counts /// between what's in cycle heads and stored on the memo. pub(crate) fn update_cycle_participant_iteration_count( &mut self, iteration_count: IterationCount, ) { let extra = self.get_or_insert_extra(); extra.iteration.set(iteration_count); } /// Updates the iteration count if this query has any cycle heads. Otherwise it's a no-op. pub(crate) fn update_iteration_count_mut( &mut self, cycle_head_index: DatabaseKeyIndex, iteration_count: IterationCount, ) { let extra = self.get_or_insert_extra(); extra.iteration.set(iteration_count); extra .cycle_heads .update_iteration_count_mut(cycle_head_index, iteration_count); } /// Returns the ids of the tracked structs created when running this query. pub fn tracked_struct_ids(&self) -> &[(Identity, Id)] { self.extra() .map(|extra| &*extra.tracked_struct_ids) .unwrap_or_default() } /// Returns a mutable reference to the `IdentityMap` for this query, or `None` if the map is empty. pub fn tracked_struct_ids_mut(&mut self) -> Option<&mut ThinVec<(Identity, Id)>> { self.extra .0 .as_mut() .map(|extra| &mut extra.tracked_struct_ids) .filter(|tracked_struct_ids| !tracked_struct_ids.is_empty()) } } /// Tracks the way that a memoized value for a query was created. /// /// This is a read-only reference to a `PackedQueryOrigin`. #[repr(u8)] #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "persistence", derive(serde::Serialize))] #[cfg_attr(feature = "persistence", serde(rename = "QueryOrigin"))] pub enum QueryOriginRef<'a> { /// The value was assigned as the output of another query (e.g., using `specify`). /// The `DatabaseKeyIndex` is the identity of the assigning query. Assigned(DatabaseKeyIndex) = QueryOriginKind::Assigned as u8, /// The value was derived by executing a function /// and we were able to track ALL of that function's inputs. /// Those inputs are described in [`QueryEdges`]. Derived(&'a [QueryEdge]) = QueryOriginKind::Derived as u8, /// The value was derived by executing a function /// but that function also reported that it read untracked inputs. /// The [`QueryEdges`] argument contains a listing of all the inputs we saw /// (but we know there were more). DerivedUntracked(&'a [QueryEdge]) = QueryOriginKind::DerivedUntracked as u8, } impl<'a> QueryOriginRef<'a> { /// Indices for queries *read* by this query #[inline] pub(crate) fn inputs(self) -> impl DoubleEndedIterator + use<'a> { let opt_edges = match self { QueryOriginRef::Derived(edges) | QueryOriginRef::DerivedUntracked(edges) => Some(edges), QueryOriginRef::Assigned(_) => None, }; opt_edges.into_iter().flat_map(input_edges) } /// Indices for queries *written* by this query (if any) pub(crate) fn outputs(self) -> impl DoubleEndedIterator + use<'a> { let opt_edges = match self { QueryOriginRef::Derived(edges) | QueryOriginRef::DerivedUntracked(edges) => Some(edges), QueryOriginRef::Assigned(_) => None, }; opt_edges.into_iter().flat_map(output_edges) } #[inline] pub(crate) fn edges(self) -> &'a [QueryEdge] { let opt_edges = match self { QueryOriginRef::Derived(edges) | QueryOriginRef::DerivedUntracked(edges) => Some(edges), QueryOriginRef::Assigned(_) => None, }; opt_edges.unwrap_or_default() } } // Note: The discriminant assignment is intentional, // we want to group `Derived` and `DerivedUntracked` together on a same bit (the second LSB) // as we tend to match against both of them in the same branch. #[derive(Clone, Copy)] #[repr(u8)] enum QueryOriginKind { /// The value was assigned as the output of another query. /// /// This can, for example, can occur when `specify` is used. Assigned = 0b01, /// The value was derived by executing a function /// _and_ Salsa was able to track all of said function's inputs. Derived = 0b11, /// The value was derived by executing a function /// but that function also reported that it read untracked inputs. DerivedUntracked = 0b10, } /// Tracks how a memoized value for a given query was created. /// /// This type is a manual enum packed to 13 bytes to reduce the size of `QueryRevisions`. #[repr(Rust, packed)] pub struct QueryOrigin { /// The tag of this enum. /// /// Note that this tag only requires two bits and could likely be packed into /// some other field. However, we get this byte for free thanks to alignment. kind: QueryOriginKind, /// The data portion of this enum. data: QueryOriginData, /// The metadata of this enum. /// /// For `QueryOriginKind::Derived` and `QueryOriginKind::DerivedUntracked`, this /// is the length of the `input_outputs` allocation. /// /// For `QueryOriginKind::Assigned`, this is the `IngredientIndex` of assigning query. /// Combined with the `Id` data, this forms a complete `DatabaseKeyIndex`. metadata: u32, } /// The data portion of `PackedQueryOrigin`. union QueryOriginData { /// Query edges for `QueryOriginKind::Derived` or `QueryOriginKind::DerivedUntracked`. /// /// The query edges are between a memoized value and other queries in the dependency graph, /// including both dependency edges (e.g., when creating the memoized value for Q0 /// executed another function Q1) and output edges (e.g., when Q0 specified the value /// for another query Q2). /// /// Note that we always track input dependencies even when there are untracked reads. /// Untracked reads mean that Salsa can't verify values, so the list of inputs is unused. /// However, Salsa still uses these edges to find the transitive inputs to an accumulator. /// /// You can access the input/output list via the methods [`inputs`] and [`outputs`] respectively. /// /// Important: /// /// * The inputs must be in **execution order** for the red-green algorithm to work. input_outputs: NonNull, /// The identity of the assigning query for `QueryOriginKind::Assigned`. index: Id, } /// SAFETY: The `input_outputs` pointer is owned and not accessed or shared concurrently. unsafe impl Send for QueryOriginData {} /// SAFETY: Same as above. unsafe impl Sync for QueryOriginData {} impl QueryOrigin { pub fn is_derived_untracked(&self) -> bool { matches!(self.kind, QueryOriginKind::DerivedUntracked) } /// Create a query origin of type `QueryOriginKind::Derived`, with the given edges. pub fn derived(input_outputs: Box<[QueryEdge]>) -> QueryOrigin { // Exceeding `u32::MAX` query edges should never happen in real-world usage. let length = u32::try_from(input_outputs.len()) .expect("exceeded more than `u32::MAX` query edges; this should never happen."); // SAFETY: `Box::into_raw` returns a non-null pointer. let input_outputs = unsafe { NonNull::new_unchecked(Box::into_raw(input_outputs).cast::()) }; QueryOrigin { kind: QueryOriginKind::Derived, metadata: length, data: QueryOriginData { input_outputs }, } } /// Create a query origin of type `QueryOriginKind::DerivedUntracked`, with the given edges. pub fn derived_untracked(input_outputs: Box<[QueryEdge]>) -> QueryOrigin { let mut origin = QueryOrigin::derived(input_outputs); origin.kind = QueryOriginKind::DerivedUntracked; origin } /// Sets the `input_outputs` of this query's origin if it's derived or derived untracked. /// Returns `Err` if the query origin isn't derived. pub fn set_edges( &mut self, input_outputs: Box<[QueryEdge]>, ) -> Result, Box<[QueryEdge]>> { match self.kind { QueryOriginKind::Assigned => Err(input_outputs), QueryOriginKind::Derived | QueryOriginKind::DerivedUntracked => { // Exceeding `u32::MAX` query edges should never happen in real-world usage. let length = u32::try_from(input_outputs.len()) .expect("exceeded more than `u32::MAX` query edges; this should never happen."); // SAFETY: `data.input_outputs` is initialized when the tag is `QueryOriginKind::Derived` // or `QueryOriginKind::DerivedUntracked`. let prev_input_outputs = unsafe { self.data.input_outputs }; let prev_length = self.metadata as usize; // SAFETY: `input_outputs` and `self.metadata` form a valid slice when the tag is // `QueryOriginKind::DerivedUntracked` or `QueryOriginKind::DerivedUntracked`, and // we have `&mut self`. let prev_input_outputs: Box<[QueryEdge]> = unsafe { Box::from_raw(ptr::slice_from_raw_parts_mut( prev_input_outputs.as_ptr(), prev_length, )) }; // SAFETY: `Box::into_raw` returns a non-null pointer. let input_outputs = unsafe { NonNull::new_unchecked(Box::into_raw(input_outputs).cast::()) }; self.data = QueryOriginData { input_outputs }; self.metadata = length; Ok(prev_input_outputs) } } } /// Create a query origin of type `QueryOriginKind::Assigned`, with the given key. pub fn assigned(key: DatabaseKeyIndex) -> QueryOrigin { QueryOrigin { kind: QueryOriginKind::Assigned, metadata: key.ingredient_index().as_u32(), data: QueryOriginData { index: key.key_index(), }, } } /// Return a read-only reference to this query origin. pub fn as_ref(&self) -> QueryOriginRef<'_> { match self.kind { QueryOriginKind::Assigned => { // SAFETY: `data.index` is initialized when the tag is `QueryOriginKind::Assigned`. let index = unsafe { self.data.index }; // SAFETY: `metadata` is initialized from a valid `IngredientIndex` when the tag // is `QueryOriginKind::Assigned`. let ingredient_index = unsafe { IngredientIndex::new_unchecked(self.metadata) }; QueryOriginRef::Assigned(DatabaseKeyIndex::new(ingredient_index, index)) } QueryOriginKind::Derived => { // SAFETY: `data.input_outputs` is initialized when the tag is `QueryOriginKind::Derived`. let input_outputs = unsafe { self.data.input_outputs }; let length = self.metadata as usize; // SAFETY: `input_outputs` and `self.metadata` form a valid slice when the // tag is `QueryOriginKind::Derived`. let input_outputs = unsafe { std::slice::from_raw_parts(input_outputs.as_ptr(), length) }; QueryOriginRef::Derived(input_outputs) } QueryOriginKind::DerivedUntracked => { // SAFETY: `data.input_outputs` is initialized when the tag is `QueryOriginKind::DerivedUntracked`. let input_outputs = unsafe { self.data.input_outputs }; let length = self.metadata as usize; // SAFETY: `input_outputs` and `self.metadata` form a valid slice when the // tag is `QueryOriginKind::DerivedUntracked`. let input_outputs = unsafe { std::slice::from_raw_parts(input_outputs.as_ptr(), length) }; QueryOriginRef::DerivedUntracked(input_outputs) } } } } #[cfg(feature = "persistence")] impl serde::Serialize for QueryOrigin { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.as_ref().serialize(serializer) } } #[cfg(feature = "persistence")] impl<'de> serde::Deserialize<'de> for QueryOrigin { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { // Matches the signature of `QueryOriginRef`. #[repr(u8)] #[derive(serde::Deserialize)] #[serde(rename = "QueryOrigin")] pub enum QueryOriginOwned { Assigned(DatabaseKeyIndex) = QueryOriginKind::Assigned as u8, Derived(Box<[QueryEdge]>) = QueryOriginKind::Derived as u8, DerivedUntracked(Box<[QueryEdge]>) = QueryOriginKind::DerivedUntracked as u8, } Ok(match QueryOriginOwned::deserialize(deserializer)? { QueryOriginOwned::Assigned(key) => QueryOrigin::assigned(key), QueryOriginOwned::Derived(edges) => QueryOrigin::derived(edges), QueryOriginOwned::DerivedUntracked(edges) => QueryOrigin::derived_untracked(edges), }) } } impl Drop for QueryOrigin { fn drop(&mut self) { match self.kind { QueryOriginKind::Derived | QueryOriginKind::DerivedUntracked => { // SAFETY: `data.input_outputs` is initialized when the tag is `QueryOriginKind::Derived` // or `QueryOriginKind::DerivedUntracked`. let input_outputs = unsafe { self.data.input_outputs }; let length = self.metadata as usize; // SAFETY: `input_outputs` and `self.metadata` form a valid slice when the tag is // `QueryOriginKind::DerivedUntracked` or `QueryOriginKind::DerivedUntracked`, and // we have `&mut self`. let _input_outputs: Box<[QueryEdge]> = unsafe { Box::from_raw(ptr::slice_from_raw_parts_mut( input_outputs.as_ptr(), length, )) }; } // The data stored for this variant is `Copy`. QueryOriginKind::Assigned => {} } } } impl std::fmt::Debug for QueryOrigin { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.as_ref().fmt(f) } } /// An input or output query edge. /// /// This type is a packed version of `QueryEdgeKind`, tagging the `IngredientIndex` /// in `key` with a discriminator for the input and output variants without increasing /// the size of the type. Notably, this type is 12 bytes as opposed to the 16 byte /// `QueryEdgeKind`, which is meaningful as inputs and outputs are stored contiguously. #[derive(Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "persistence", serde(transparent))] pub struct QueryEdge { key: DatabaseKeyIndex, } impl QueryEdge { /// Create an input query edge with the given index. pub fn input(key: DatabaseKeyIndex) -> QueryEdge { Self { key } } /// Create an output query edge with the given index. pub fn output(key: DatabaseKeyIndex) -> QueryEdge { let ingredient_index = key.ingredient_index().with_tag(true); Self { key: DatabaseKeyIndex::new(ingredient_index, key.key_index()), } } /// Return the key of this query edge. pub fn key(self) -> DatabaseKeyIndex { // Clear the tag to restore the original index. DatabaseKeyIndex::new( self.key.ingredient_index().with_tag(false), self.key.key_index(), ) } /// Returns the kind of this query edge. pub fn kind(self) -> QueryEdgeKind { if self.key.ingredient_index().tag() { QueryEdgeKind::Output(self.key()) } else { QueryEdgeKind::Input(self.key()) } } } impl std::fmt::Debug for QueryEdge { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.kind().fmt(f) } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum QueryEdgeKind { Input(DatabaseKeyIndex), Output(DatabaseKeyIndex), } /// Returns the (tracked) inputs that were executed in computing this memoized value. /// /// These will always be in execution order. pub(crate) fn input_edges( input_outputs: &[QueryEdge], ) -> impl DoubleEndedIterator + use<'_> { input_outputs.iter().filter_map(|&edge| match edge.kind() { QueryEdgeKind::Input(dependency_index) => Some(dependency_index), QueryEdgeKind::Output(_) => None, }) } /// Returns the (tracked) outputs that were executed in computing this memoized value. /// /// These will always be in execution order. pub(crate) fn output_edges( input_outputs: &[QueryEdge], ) -> impl DoubleEndedIterator + use<'_> { input_outputs.iter().filter_map(|&edge| match edge.kind() { QueryEdgeKind::Output(dependency_index) => Some(dependency_index), QueryEdgeKind::Input(_) => None, }) } /// When a query is pushed onto the `active_query` stack, this guard /// is returned to represent its slot. The guard can be used to pop /// the query from the stack -- in the case of unwinding, the guard's /// destructor will also remove the query. pub(crate) struct ActiveQueryGuard<'me> { local_state: &'me ZalsaLocal, #[cfg(debug_assertions)] push_len: usize, pub(crate) database_key_index: DatabaseKeyIndex, } impl ActiveQueryGuard<'_> { /// Initialize the tracked struct ids with the values from the prior execution. pub(crate) fn seed_tracked_struct_ids(&self, tracked_struct_ids: &[(Identity, Id)]) { // SAFETY: We do not access the query stack reentrantly. unsafe { self.local_state.with_query_stack_unchecked_mut(|stack| { #[cfg(debug_assertions)] assert_eq!(stack.len(), self.push_len, "mismatched push and pop"); let frame = stack.last_mut().unwrap(); frame.tracked_struct_ids_mut().seed(tracked_struct_ids); }) } } /// Append the given `outputs` to the query's output list. pub(crate) fn seed_iteration(&self, previous: &QueryRevisions) { let durability = previous.durability; let changed_at = previous.changed_at; let edges = previous.origin.as_ref().edges(); let untracked_read = matches!( previous.origin.as_ref(), QueryOriginRef::DerivedUntracked(_) ); let tracked_ids = previous.tracked_struct_ids(); // SAFETY: We do not access the query stack reentrantly. unsafe { self.local_state.with_query_stack_unchecked_mut(|stack| { #[cfg(debug_assertions)] assert_eq!(stack.len(), self.push_len, "mismatched push and pop"); let frame = stack.last_mut().unwrap(); frame.seed_iteration(durability, changed_at, edges, untracked_read, tracked_ids); }) } } pub(crate) fn take_cycle_heads(&mut self) -> CycleHeads { // SAFETY: We do not access the query stack reentrantly. unsafe { self.local_state.with_query_stack_unchecked_mut(|stack| { #[cfg(debug_assertions)] assert_eq!(stack.len(), self.push_len); let frame = stack.last_mut().unwrap(); frame.take_cycle_heads() }) } } /// Invoked when the query has successfully completed execution. fn complete(self) -> CompletedQuery { // SAFETY: We do not access the query stack reentrantly. let query = unsafe { self.local_state.with_query_stack_unchecked_mut(|stack| { stack.pop_into_revisions( self.database_key_index, #[cfg(debug_assertions)] self.push_len, ) }) }; std::mem::forget(self); query } /// Pops an active query from the stack. Returns the [`CompletedQuery`] /// which summarizes the other queries that were accessed during this /// query's execution. #[inline] pub(crate) fn pop(self) -> CompletedQuery { self.complete() } } impl Drop for ActiveQueryGuard<'_> { fn drop(&mut self) { // SAFETY: We do not access the query stack reentrantly. unsafe { self.local_state.with_query_stack_unchecked_mut(|stack| { stack.pop( self.database_key_index, #[cfg(debug_assertions)] self.push_len, ); }) }; } } #[cfg(feature = "persistence")] pub(crate) mod persistence { use super::{QueryOrigin, QueryRevisions, QueryRevisionsExtra}; use crate::sync::atomic::{AtomicBool, Ordering}; use crate::{Durability, Revision}; /// A reference to the fields of [`QueryRevisions`], with its [`QueryOrigin`] transformed. #[derive(serde::Serialize)] pub(crate) struct MappedQueryRevisions<'a> { changed_at: Revision, durability: Durability, origin: QueryOrigin, #[serde(with = "verified_final")] verified_final: AtomicBool, extra: &'a QueryRevisionsExtra, } impl QueryRevisions { pub(crate) fn with_origin(&self, origin: QueryOrigin) -> MappedQueryRevisions<'_> { let QueryRevisions { changed_at, durability, ref verified_final, ref extra, #[cfg(feature = "accumulator")] accumulated_inputs: _, // TODO: Support serializing accumulators origin: _, } = *self; MappedQueryRevisions { changed_at, durability, extra, origin, verified_final: AtomicBool::new(verified_final.load(Ordering::Relaxed)), } } } // A workaround the fact that `shuttle` atomic types do not implement `serde::{Serialize, Deserialize}`. pub(super) mod verified_final { use crate::sync::atomic::{AtomicBool, Ordering}; pub fn serialize(value: &AtomicBool, serializer: S) -> Result where S: serde::Serializer, { serde::Serialize::serialize(&value.load(Ordering::Relaxed), serializer) } pub fn deserialize<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, { serde::Deserialize::deserialize(deserializer).map(AtomicBool::new) } } } salsa-0.26.2/tests/accumulate-chain.rs000064400000000000000000000023401046102023000157300ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] //! Test that when having nested tracked functions //! we don't drop any values when accumulating. mod common; use expect_test::expect; use salsa::{Accumulator, Database, DatabaseImpl}; use test_log::test; #[salsa::accumulator] #[derive(Debug)] struct Log(#[allow(dead_code)] String); #[salsa::tracked] fn push_logs(db: &dyn Database) { push_a_logs(db); } #[salsa::tracked] fn push_a_logs(db: &dyn Database) { Log("log a".to_string()).accumulate(db); push_b_logs(db); } #[salsa::tracked] fn push_b_logs(db: &dyn Database) { // No logs push_c_logs(db); } #[salsa::tracked] fn push_c_logs(db: &dyn Database) { // No logs push_d_logs(db); } #[salsa::tracked] fn push_d_logs(db: &dyn Database) { Log("log d".to_string()).accumulate(db); } #[test] fn accumulate_chain() { DatabaseImpl::new().attach(|db| { let logs = push_logs::accumulated::(db); // Check that we get all the logs. expect![[r#" [ Log( "log a", ), Log( "log d", ), ]"#]] .assert_eq(&format!("{logs:#?}")); }) } salsa-0.26.2/tests/accumulate-custom-debug.rs000064400000000000000000000020061046102023000172430ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] mod common; use expect_test::expect; use salsa::{Accumulator, Database}; use test_log::test; #[salsa::input(debug)] struct MyInput { count: u32, } #[salsa::accumulator] struct Log(String); impl std::fmt::Debug for Log { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("CustomLog").field(&self.0).finish() } } #[salsa::tracked] fn push_logs(db: &dyn salsa::Database, input: MyInput) { for i in 0..input.count(db) { Log(format!("#{i}")).accumulate(db); } } #[test] fn accumulate_custom_debug() { salsa::DatabaseImpl::new().attach(|db| { let input = MyInput::new(db, 2); let logs = push_logs::accumulated::(db, input); expect![[r##" [ CustomLog( "#0", ), CustomLog( "#1", ), ] "##]] .assert_debug_eq(&logs); }) } salsa-0.26.2/tests/accumulate-dag.rs000064400000000000000000000031721046102023000154050ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] mod common; use expect_test::expect; use salsa::{Accumulator, Database}; use test_log::test; #[salsa::input(debug)] struct MyInput { field_a: u32, field_b: u32, } #[salsa::accumulator] #[derive(Debug)] struct Log(#[allow(dead_code)] String); #[salsa::tracked] fn push_logs(db: &dyn Database, input: MyInput) { push_a_logs(db, input); push_b_logs(db, input); } #[salsa::tracked] fn push_a_logs(db: &dyn Database, input: MyInput) { let count = input.field_a(db); for i in 0..count { Log(format!("log_a({i} of {count})")).accumulate(db); } } #[salsa::tracked] fn push_b_logs(db: &dyn Database, input: MyInput) { // Note that b calls a push_a_logs(db, input); let count = input.field_b(db); for i in 0..count { Log(format!("log_b({i} of {count})")).accumulate(db); } } #[test] fn accumulate_a_called_twice() { salsa::DatabaseImpl::new().attach(|db| { let input = MyInput::new(db, 2, 3); let logs = push_logs::accumulated::(db, input); // Check that we don't see logs from `a` appearing twice in the input. expect![[r#" [ Log( "log_a(0 of 2)", ), Log( "log_a(1 of 2)", ), Log( "log_b(0 of 3)", ), Log( "log_b(1 of 3)", ), Log( "log_b(2 of 3)", ), ]"#]] .assert_eq(&format!("{logs:#?}")); }) } salsa-0.26.2/tests/accumulate-execution-order.rs000064400000000000000000000027131046102023000177660ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] //! Demonstrates that accumulation is done in the order //! in which things were originally executed. mod common; use expect_test::expect; use salsa::{Accumulator, Database}; use test_log::test; #[salsa::accumulator] #[derive(Debug)] struct Log(#[allow(dead_code)] String); #[salsa::tracked] fn push_logs(db: &dyn Database) { push_a_logs(db); } #[salsa::tracked] fn push_a_logs(db: &dyn Database) { Log("log a".to_string()).accumulate(db); push_b_logs(db); push_c_logs(db); push_d_logs(db); } #[salsa::tracked] fn push_b_logs(db: &dyn Database) { Log("log b".to_string()).accumulate(db); push_d_logs(db); } #[salsa::tracked] fn push_c_logs(db: &dyn Database) { Log("log c".to_string()).accumulate(db); } #[salsa::tracked] fn push_d_logs(db: &dyn Database) { Log("log d".to_string()).accumulate(db); } #[test] fn accumulate_execution_order() { salsa::DatabaseImpl::new().attach(|db| { let logs = push_logs::accumulated::(db); // Check that we get logs in execution order expect![[r#" [ Log( "log a", ), Log( "log b", ), Log( "log d", ), Log( "log c", ), ]"#]] .assert_eq(&format!("{logs:#?}")); }) } salsa-0.26.2/tests/accumulate-from-tracked-fn.rs000064400000000000000000000034051046102023000176300ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] //! Accumulate values from within a tracked function. //! Then mutate the values so that the tracked function re-executes. //! Check that we accumulate the appropriate, new values. use expect_test::expect; use salsa::{Accumulator, Setter}; use test_log::test; #[salsa::input(debug)] struct List { value: u32, next: Option, } #[salsa::accumulator] #[derive(Copy, Clone, Debug)] struct Integers(u32); #[salsa::tracked] fn compute(db: &dyn salsa::Database, input: List) { eprintln!( "{:?}(value={:?}, next={:?})", input, input.value(db), input.next(db) ); let result = if let Some(next) = input.next(db) { let next_integers = compute::accumulated::(db, next); eprintln!("{next_integers:?}"); let v = input.value(db) + next_integers.iter().map(|a| a.0).sum::(); eprintln!("input={:?} v={:?}", input.value(db), v); v } else { input.value(db) }; Integers(result).accumulate(db); eprintln!("pushed result {result:?}"); } #[test] fn test1() { let mut db = salsa::DatabaseImpl::new(); let l0 = List::new(&db, 1, None); let l1 = List::new(&db, 10, Some(l0)); compute(&db, l1); expect![[r#" [ Integers( 11, ), Integers( 1, ), ] "#]] .assert_debug_eq(&compute::accumulated::(&db, l1)); l0.set_value(&mut db).to(2); compute(&db, l1); expect![[r#" [ Integers( 12, ), Integers( 2, ), ] "#]] .assert_debug_eq(&compute::accumulated::(&db, l1)); } salsa-0.26.2/tests/accumulate-no-duplicates.rs000064400000000000000000000042261046102023000174220ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] //! Test that we don't get duplicate accumulated values mod common; use expect_test::expect; use salsa::{Accumulator, Database}; use test_log::test; // A(1) { // B // B // C { // D { // A(2) { // B // } // B // } // E // } // B // } #[salsa::accumulator] #[derive(Debug)] struct Log(#[allow(dead_code)] String); #[salsa::input(debug)] struct MyInput { n: u32, } #[salsa::tracked] fn push_logs(db: &dyn Database) { push_a_logs(db, MyInput::new(db, 1)); } #[salsa::tracked] fn push_a_logs(db: &dyn Database, input: MyInput) { Log("log a".to_string()).accumulate(db); if input.n(db) == 1 { push_b_logs(db); push_b_logs(db); push_c_logs(db); push_b_logs(db); } else { push_b_logs(db); } } #[salsa::tracked] fn push_b_logs(db: &dyn Database) { Log("log b".to_string()).accumulate(db); } #[salsa::tracked] fn push_c_logs(db: &dyn Database) { Log("log c".to_string()).accumulate(db); push_d_logs(db); push_e_logs(db); } // Note this isn't tracked fn push_d_logs(db: &dyn Database) { Log("log d".to_string()).accumulate(db); push_a_logs(db, MyInput::new(db, 2)); push_b_logs(db); } #[salsa::tracked] fn push_e_logs(db: &dyn Database) { Log("log e".to_string()).accumulate(db); } #[test] fn accumulate_no_duplicates() { salsa::DatabaseImpl::new().attach(|db| { let logs = push_logs::accumulated::(db); // Test that there aren't duplicate B logs. // Note that log A appears twice, because they both come // from different inputs. expect![[r#" [ Log( "log a", ), Log( "log b", ), Log( "log c", ), Log( "log d", ), Log( "log a", ), Log( "log e", ), ]"#]] .assert_eq(&format!("{logs:#?}")); }) } salsa-0.26.2/tests/accumulate-reuse-workaround.rs000064400000000000000000000040651046102023000201700ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] //! Demonstrates the workaround of wrapping calls to //! `accumulated` in a tracked function to get better //! reuse. mod common; use common::{LogDatabase, LoggerDatabase}; use expect_test::expect; use salsa::{Accumulator, Setter}; use test_log::test; #[salsa::input(debug)] struct List { value: u32, next: Option, } #[salsa::accumulator] #[derive(Copy, Clone, Debug)] struct Integers(u32); #[salsa::tracked] fn compute(db: &dyn LogDatabase, input: List) -> u32 { db.push_log(format!("compute({input:?})",)); // always pushes 0 Integers(0).accumulate(db); // return value changes if let Some(next) = input.next(db) { let next_integers = accumulated(db, next); input.value(db) + next_integers.iter().sum::() } else { input.value(db) } } #[salsa::tracked(returns(ref))] fn accumulated(db: &dyn LogDatabase, input: List) -> Vec { db.push_log(format!("accumulated({input:?})")); compute::accumulated::(db, input) .into_iter() .map(|a| a.0) .collect() } #[test] fn test1() { let mut db = LoggerDatabase::default(); let l1 = List::new(&db, 1, None); let l2 = List::new(&db, 2, Some(l1)); assert_eq!(compute(&db, l2), 2); db.assert_logs(expect![[r#" [ "compute(List { [salsa id]: Id(1), value: 2, next: Some(List { [salsa id]: Id(0), value: 1, next: None }) })", "accumulated(List { [salsa id]: Id(0), value: 1, next: None })", "compute(List { [salsa id]: Id(0), value: 1, next: None })", ]"#]]); // When we mutate `l1`, we should re-execute `compute` for `l1`, // and we re-execute accumulated for `l1`, but we do NOT re-execute // `compute` for `l2`. l1.set_value(&mut db).to(2); assert_eq!(compute(&db, l2), 2); db.assert_logs(expect![[r#" [ "accumulated(List { [salsa id]: Id(0), value: 2, next: None })", "compute(List { [salsa id]: Id(0), value: 2, next: None })", ]"#]]); } salsa-0.26.2/tests/accumulate-reuse.rs000064400000000000000000000035521046102023000157770ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] //! Accumulator re-use test. //! //! Tests behavior when a query's only inputs //! are the accumulated values from another query. mod common; use common::{LogDatabase, LoggerDatabase}; use expect_test::expect; use salsa::{Accumulator, Setter}; use test_log::test; #[salsa::input(debug)] struct List { value: u32, next: Option, } #[salsa::accumulator] struct Integers(u32); #[salsa::tracked] fn compute(db: &dyn LogDatabase, input: List) -> u32 { db.push_log(format!("compute({input:?})",)); // always pushes 0 Integers(0).accumulate(db); // return value changes if let Some(next) = input.next(db) { let next_integers = compute::accumulated::(db, next); input.value(db) + next_integers.iter().map(|i| i.0).sum::() } else { input.value(db) } } #[test] fn test1() { let mut db = LoggerDatabase::default(); let l1 = List::new(&db, 1, None); let l2 = List::new(&db, 2, Some(l1)); assert_eq!(compute(&db, l2), 2); db.assert_logs(expect![[r#" [ "compute(List { [salsa id]: Id(1), value: 2, next: Some(List { [salsa id]: Id(0), value: 1, next: None }) })", "compute(List { [salsa id]: Id(0), value: 1, next: None })", ]"#]]); // When we mutate `l1`, we should re-execute `compute` for `l1`, // but we should not have to re-execute `compute` for `l2`. // The only input for `compute(l1)` is the accumulated values from `l1`, // which have not changed. l1.set_value(&mut db).to(2); assert_eq!(compute(&db, l2), 2); db.assert_logs(expect![[r#" [ "compute(List { [salsa id]: Id(1), value: 2, next: Some(List { [salsa id]: Id(0), value: 2, next: None }) })", "compute(List { [salsa id]: Id(0), value: 2, next: None })", ]"#]]); } salsa-0.26.2/tests/accumulate.rs000064400000000000000000000137401046102023000146560ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] mod common; use common::{LogDatabase, LoggerDatabase}; use expect_test::expect; use salsa::{Accumulator, Setter}; use test_log::test; #[salsa::input(debug)] struct MyInput { field_a: u32, field_b: u32, } #[salsa::accumulator] #[derive(Debug)] struct Log(#[allow(dead_code)] String); #[salsa::tracked] fn push_logs(db: &dyn LogDatabase, input: MyInput) { db.push_log(format!( "push_logs(a = {}, b = {})", input.field_a(db), input.field_b(db) )); // We don't invoke `push_a_logs` (or `push_b_logs`) with a value of 0. // This allows us to test what happens a change in inputs causes a function not to be called at all. if input.field_a(db) > 0 { push_a_logs(db, input); } if input.field_b(db) > 0 { push_b_logs(db, input); } } #[salsa::tracked] fn push_a_logs(db: &dyn LogDatabase, input: MyInput) { let field_a = input.field_a(db); db.push_log(format!("push_a_logs({field_a})")); for i in 0..field_a { Log(format!("log_a({i} of {field_a})")).accumulate(db); } } #[salsa::tracked] fn push_b_logs(db: &dyn LogDatabase, input: MyInput) { let field_a = input.field_b(db); db.push_log(format!("push_b_logs({field_a})")); for i in 0..field_a { Log(format!("log_b({i} of {field_a})")).accumulate(db); } } #[test] fn accumulate_once() { let db = common::LoggerDatabase::default(); // Just call accumulate on a base input to see what happens. let input = MyInput::new(&db, 2, 3); let logs = push_logs::accumulated::(&db, input); db.assert_logs(expect![[r#" [ "push_logs(a = 2, b = 3)", "push_a_logs(2)", "push_b_logs(3)", ]"#]]); // Check that we see logs from `a` first and then logs from `b` // (execution order). expect![[r#" [ Log( "log_a(0 of 2)", ), Log( "log_a(1 of 2)", ), Log( "log_b(0 of 3)", ), Log( "log_b(1 of 3)", ), Log( "log_b(2 of 3)", ), ]"#]] .assert_eq(&format!("{logs:#?}")); } #[test] fn change_a_from_2_to_0() { let mut db = common::LoggerDatabase::default(); // Accumulate logs for `a = 2` and `b = 3` let input = MyInput::new(&db, 2, 3); let logs = push_logs::accumulated::(&db, input); expect![[r#" [ Log( "log_a(0 of 2)", ), Log( "log_a(1 of 2)", ), Log( "log_b(0 of 3)", ), Log( "log_b(1 of 3)", ), Log( "log_b(2 of 3)", ), ]"#]] .assert_eq(&format!("{logs:#?}")); db.assert_logs(expect![[r#" [ "push_logs(a = 2, b = 3)", "push_a_logs(2)", "push_b_logs(3)", ]"#]]); // Change to `a = 0`, which means `push_logs` does not call `push_a_logs` at all input.set_field_a(&mut db).to(0); let logs = push_logs::accumulated::(&db, input); expect![[r#" [ Log( "log_b(0 of 3)", ), Log( "log_b(1 of 3)", ), Log( "log_b(2 of 3)", ), ]"#]] .assert_eq(&format!("{logs:#?}")); db.assert_logs(expect![[r#" [ "push_logs(a = 0, b = 3)", ]"#]]); } #[test] fn change_a_from_2_to_1() { let mut db = LoggerDatabase::default(); // Accumulate logs for `a = 2` and `b = 3` let input = MyInput::new(&db, 2, 3); let logs = push_logs::accumulated::(&db, input); expect![[r#" [ Log( "log_a(0 of 2)", ), Log( "log_a(1 of 2)", ), Log( "log_b(0 of 3)", ), Log( "log_b(1 of 3)", ), Log( "log_b(2 of 3)", ), ]"#]] .assert_eq(&format!("{logs:#?}")); db.assert_logs(expect![[r#" [ "push_logs(a = 2, b = 3)", "push_a_logs(2)", "push_b_logs(3)", ]"#]]); // Change to `a = 1`, which means `push_logs` does not call `push_a_logs` at all input.set_field_a(&mut db).to(1); let logs = push_logs::accumulated::(&db, input); expect![[r#" [ Log( "log_a(0 of 1)", ), Log( "log_b(0 of 3)", ), Log( "log_b(1 of 3)", ), Log( "log_b(2 of 3)", ), ]"#]] .assert_eq(&format!("{logs:#?}")); db.assert_logs(expect![[r#" [ "push_logs(a = 1, b = 3)", "push_a_logs(1)", ]"#]]); } #[test] fn get_a_logs_after_changing_b() { let mut db = common::LoggerDatabase::default(); // Invoke `push_a_logs` with `a = 2` and `b = 3` (but `b` doesn't matter) let input = MyInput::new(&db, 2, 3); let logs = push_a_logs::accumulated::(&db, input); expect![[r#" [ Log( "log_a(0 of 2)", ), Log( "log_a(1 of 2)", ), ]"#]] .assert_eq(&format!("{logs:#?}")); db.assert_logs(expect![[r#" [ "push_a_logs(2)", ]"#]]); // Changing `b` does not cause `push_a_logs` to re-execute // and we still get the same result input.set_field_b(&mut db).to(5); let logs = push_a_logs::accumulated::(&db, input); expect![[r#" [ Log( "log_a(0 of 2)", ), Log( "log_a(1 of 2)", ), ] "#]] .assert_debug_eq(&logs); db.assert_logs(expect!["[]"]); } salsa-0.26.2/tests/accumulated_backdate.rs000064400000000000000000000034541046102023000166410ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] //! Tests that accumulated values are correctly accounted for //! when backdating a value. mod common; use common::LogDatabase; use expect_test::expect; use salsa::{Accumulator, Setter}; use test_log::test; #[salsa::input(debug)] struct File { content: String, } #[salsa::accumulator] #[derive(Debug)] struct Log(#[allow(dead_code)] String); #[salsa::tracked] fn compile(db: &dyn LogDatabase, input: File) -> u32 { parse(db, input) } #[salsa::tracked] fn parse(db: &dyn LogDatabase, input: File) -> u32 { let value: Result = input.content(db).parse(); match value { Ok(value) => value, Err(error) => { Log(error.to_string()).accumulate(db); 0 } } } #[test] fn backdate() { let mut db = common::LoggerDatabase::default(); let input = File::new(&db, "0".to_string()); let logs = compile::accumulated::(&db, input); expect![[r#"[]"#]].assert_eq(&format!("{logs:#?}")); input.set_content(&mut db).to("a".to_string()); let logs = compile::accumulated::(&db, input); expect![[r#" [ Log( "invalid digit found in string", ), ]"#]] .assert_eq(&format!("{logs:#?}")); } #[test] fn backdate_no_diagnostics() { let mut db = common::LoggerDatabase::default(); let input = File::new(&db, "a".to_string()); let logs = compile::accumulated::(&db, input); expect![[r#" [ Log( "invalid digit found in string", ), ]"#]] .assert_eq(&format!("{logs:#?}")); input.set_content(&mut db).to("0".to_string()); let logs = compile::accumulated::(&db, input); expect![[r#"[]"#]].assert_eq(&format!("{logs:#?}")); } salsa-0.26.2/tests/backdate_untracked_db_field.rs000064400000000000000000000036541046102023000201440ustar 00000000000000#![cfg(feature = "inventory")] use salsa::Setter; #[salsa::input(debug)] struct MyInput { field: u32, } #[salsa::db] trait Db: salsa::Database { /// Untracked database state. Reading this inside a tracked query is a footgun. fn extra(&self) -> u32; } #[salsa::db] #[derive(Default, Clone)] struct ExtraFieldDatabase { storage: salsa::Storage, extra: u32, } #[salsa::db] impl salsa::Database for ExtraFieldDatabase {} #[salsa::db] impl Db for ExtraFieldDatabase { fn extra(&self) -> u32 { self.extra } } impl ExtraFieldDatabase { fn set_extra(&mut self, value: u32) { self.extra = value; } } #[salsa::tracked] fn dep_a_db(db: &dyn Db, input: MyInput) -> u32 { input.field(db) } #[salsa::tracked] fn dep_b_db(db: &dyn Db, input: MyInput) -> u32 { input.field(db) } #[salsa::tracked] fn db_field_branch_query(db: &dyn Db, a: MyInput, b: MyInput) -> u32 { if db.extra() == 0 { dep_a_db(db, a) } else { dep_b_db(db, b) } } #[test] #[cfg_attr(debug_assertions, should_panic(expected = "returned the same value"))] fn db_field_branch_can_trip_backdate_assertion() { let mut db = ExtraFieldDatabase::default(); db.set_extra(0); let a = MyInput::new(&db, 0); let b = MyInput::new(&db, 0); // R1: depends on `a`, returns 0 assert_eq!(db_field_branch_query(&db, a, b), 0); // R2: force the memo to have a recent changed_at. a.set_field(&mut db).to(1); assert_eq!(db_field_branch_query(&db, a, b), 1); // R3: return to 0 (still depending on `a`). a.set_field(&mut db).to(0); assert_eq!(db_field_branch_query(&db, a, b), 0); // R4/R5: switch branch via untracked db field, and force re-execution by changing `a`. // New execution returns 0 (equal) but depends only on older `b`, triggering the backdate check. db.set_extra(1); a.set_field(&mut db).to(1); let _ = db_field_branch_query(&db, a, b); } salsa-0.26.2/tests/backtrace.rs000064400000000000000000000067341046102023000144570ustar 00000000000000#![cfg(feature = "inventory")] use expect_test::expect; use salsa::{Backtrace, Database, DatabaseImpl}; use test_log::test; #[salsa::input(debug)] struct Thing { detailed: bool, } #[salsa::tracked] fn query_a(db: &dyn Database, thing: Thing) -> String { query_b(db, thing) } #[salsa::tracked] fn query_b(db: &dyn Database, thing: Thing) -> String { query_c(db, thing) } #[salsa::tracked] fn query_c(db: &dyn Database, thing: Thing) -> String { query_d(db, thing) } #[salsa::tracked] fn query_d(db: &dyn Database, thing: Thing) -> String { query_e(db, thing) } #[salsa::tracked] fn query_e(db: &dyn Database, thing: Thing) -> String { if thing.detailed(db) { format!("{:#}", Backtrace::capture().unwrap()) } else { format!("{}", Backtrace::capture().unwrap()) } } #[salsa::tracked] fn query_f(db: &dyn Database, thing: Thing) -> String { query_cycle(db, thing) } #[salsa::tracked(cycle_initial=cycle_initial)] fn query_cycle(db: &dyn Database, thing: Thing) -> String { let backtrace = query_cycle(db, thing); if backtrace.is_empty() { query_e(db, thing) } else { backtrace } } fn cycle_initial(_db: &dyn salsa::Database, _id: salsa::Id, _thing: Thing) -> String { String::new() } #[test] fn backtrace_works() { let db = DatabaseImpl::default(); let backtrace = query_a(&db, Thing::new(&db, false)).replace("\\", "/"); expect![[r#" query stacktrace: 0: query_e(Id(0)) at tests/backtrace.rs:32 1: query_d(Id(0)) at tests/backtrace.rs:27 2: query_c(Id(0)) at tests/backtrace.rs:22 3: query_b(Id(0)) at tests/backtrace.rs:17 4: query_a(Id(0)) at tests/backtrace.rs:12 "#]] .assert_eq(&backtrace); let backtrace = query_a(&db, Thing::new(&db, true)).replace("\\", "/"); expect![[r#" query stacktrace: 0: query_e(Id(1)) -> (R1, Durability::LOW) at tests/backtrace.rs:32 1: query_d(Id(1)) -> (R1, Durability::HIGH) at tests/backtrace.rs:27 2: query_c(Id(1)) -> (R1, Durability::HIGH) at tests/backtrace.rs:22 3: query_b(Id(1)) -> (R1, Durability::HIGH) at tests/backtrace.rs:17 4: query_a(Id(1)) -> (R1, Durability::HIGH) at tests/backtrace.rs:12 "#]] .assert_eq(&backtrace); let backtrace = query_f(&db, Thing::new(&db, false)).replace("\\", "/"); expect![[r#" query stacktrace: 0: query_e(Id(2)) at tests/backtrace.rs:32 1: query_cycle(Id(2)) at tests/backtrace.rs:45 cycle heads: query_cycle(Id(2)) -> iteration = 0 2: query_f(Id(2)) at tests/backtrace.rs:40 "#]] .assert_eq(&backtrace); let backtrace = query_f(&db, Thing::new(&db, true)).replace("\\", "/"); expect![[r#" query stacktrace: 0: query_e(Id(3)) -> (R1, Durability::LOW) at tests/backtrace.rs:32 1: query_cycle(Id(3)) -> (R1, Durability::HIGH, iteration = 0) at tests/backtrace.rs:45 cycle heads: query_cycle(Id(3)) -> iteration = 0 2: query_f(Id(3)) -> (R1, Durability::HIGH) at tests/backtrace.rs:40 "#]] .assert_eq(&backtrace); } salsa-0.26.2/tests/cancellation_token.rs000064400000000000000000000031061046102023000163620ustar 00000000000000#![cfg(feature = "inventory")] //! Test that `DeriveWithDb` is correctly derived. mod common; use std::{sync::Barrier, thread}; use expect_test::expect; use salsa::{Cancelled, Database}; use crate::common::LogDatabase; #[salsa::input(debug)] struct MyInput { field: u32, } #[salsa::tracked] fn a(db: &dyn Database, input: MyInput) -> u32 { BARRIER.wait(); BARRIER2.wait(); b(db, input) } #[salsa::tracked] fn b(db: &dyn Database, input: MyInput) -> u32 { input.field(db) } static BARRIER: Barrier = Barrier::new(2); static BARRIER2: Barrier = Barrier::new(2); #[test] fn cancellation_token() { let db = common::EventLoggerDatabase::default(); let token = db.cancellation_token(); let input = MyInput::new(&db, 22); let res = Cancelled::catch(|| { thread::scope(|s| { s.spawn(|| { BARRIER.wait(); token.cancel(); BARRIER2.wait(); }); a(&db, input) }) }); assert!(matches!(res, Err(Cancelled::Local)), "{res:?}"); drop(res); db.assert_logs(expect![[r#" [ "WillCheckCancellation", "WillExecute { database_key: a(Id(0)) }", "WillCheckCancellation", ]"#]]); thread::spawn(|| { BARRIER.wait(); BARRIER2.wait(); }); a(&db, input); db.assert_logs(expect![[r#" [ "WillCheckCancellation", "WillExecute { database_key: a(Id(0)) }", "WillCheckCancellation", "WillExecute { database_key: b(Id(0)) }", ]"#]]); } salsa-0.26.2/tests/check_auto_traits.rs000064400000000000000000000017651046102023000162320ustar 00000000000000#![cfg(feature = "inventory")] //! Test that auto trait impls exist as expected. use std::panic::UnwindSafe; use salsa::Database; use test_log::test; #[salsa::input] struct MyInput { field: String, } #[salsa::tracked] struct MyTracked<'db> { field: MyInterned<'db>, } #[salsa::interned] struct MyInterned<'db> { field: String, } #[salsa::tracked] fn test(db: &dyn Database, input: MyInput) { let input = is_send(is_sync(input)); let interned = is_send(is_sync(MyInterned::new(db, input.field(db).clone()))); let _tracked_struct = is_send(is_sync(MyTracked::new(db, interned))); } fn is_send(t: T) -> T { t } fn is_sync(t: T) -> T { t } fn is_unwind_safe(t: T) -> T { t } #[test] fn execute() { let db = is_send(salsa::DatabaseImpl::new()); let _handle = is_send(is_sync(is_unwind_safe( db.storage().clone().into_zalsa_handle(), ))); let input = MyInput::new(&db, "Hello".to_string()); test(&db, input); } salsa-0.26.2/tests/common/mod.rs000064400000000000000000000127761046102023000146120ustar 00000000000000//! Utility for tests that lets us log when notable events happen. #![allow(dead_code, unused_imports)] use std::sync::{Arc, Mutex}; use salsa::{Database, Storage}; /// Logging userdata: provides [`LogDatabase`][] trait. /// /// If you wish to use it along with other userdata, /// you can also embed it in another struct and implement [`HasLogger`][] for that struct. #[derive(Clone, Default)] pub struct Logger { logs: Arc>>, } impl Logger { pub fn push_log(&self, string: String) { self.logs.lock().unwrap().push(string); } } /// Trait implemented by databases that lets them log events. pub trait HasLogger { /// Return a reference to the logger from the database. fn logger(&self) -> &Logger; } #[salsa::db] pub trait LogDatabase: HasLogger + Database { /// Log an event from inside a tracked function. fn push_log(&self, string: String) { self.logger().logs.lock().unwrap().push(string); } fn clear_logs(&self) { std::mem::take(&mut *self.logger().logs.lock().unwrap()); } /// Asserts what the (formatted) logs should look like, /// clearing the logged events. This takes `&mut self` because /// it is meant to be run from outside any tracked functions. #[track_caller] fn assert_logs(&self, expected: expect_test::Expect) { let logs = std::mem::take(&mut *self.logger().logs.lock().unwrap()); expected.assert_eq(&format!("{logs:#?}")); } /// Asserts the length of the logs, /// clearing the logged events. This takes `&mut self` because /// it is meant to be run from outside any tracked functions. #[track_caller] fn assert_logs_len(&self, expected: usize) { let logs = std::mem::take(&mut *self.logger().logs.lock().unwrap()); assert_eq!(logs.len(), expected, "Actual logs: {logs:#?}"); } } #[salsa::db] impl LogDatabase for Db {} /// Database that provides logging but does not log salsa event. #[salsa::db] #[derive(Clone, Default)] pub struct LoggerDatabase { storage: Storage, logger: Logger, } impl HasLogger for LoggerDatabase { fn logger(&self) -> &Logger { &self.logger } } #[salsa::db] impl Database for LoggerDatabase {} /// Database that provides logging and logs salsa events. #[salsa::db] #[derive(Clone)] pub struct EventLoggerDatabase { storage: Storage, logger: Logger, } impl Default for EventLoggerDatabase { fn default() -> Self { let logger = Logger::default(); Self { storage: Storage::new(Some(Box::new({ let logger = logger.clone(); move |event| logger.push_log(format!("{:?}", event.kind)) }))), logger, } } } #[salsa::db] impl Database for EventLoggerDatabase {} impl HasLogger for EventLoggerDatabase { fn logger(&self) -> &Logger { &self.logger } } #[salsa::db] #[derive(Clone)] pub struct DiscardLoggerDatabase { storage: Storage, logger: Logger, } impl Default for DiscardLoggerDatabase { fn default() -> Self { let logger = Logger::default(); Self { storage: Storage::new(Some(Box::new({ let logger = logger.clone(); move |event| match event.kind { salsa::EventKind::WillDiscardStaleOutput { .. } | salsa::EventKind::DidDiscard { .. } => { logger.push_log(format!("salsa_event({:?})", event.kind)); } _ => {} } }))), logger, } } } #[salsa::db] impl Database for DiscardLoggerDatabase {} impl HasLogger for DiscardLoggerDatabase { fn logger(&self) -> &Logger { &self.logger } } #[salsa::db] #[derive(Clone)] pub struct ExecuteValidateLoggerDatabase { storage: Storage, logger: Logger, } impl Default for ExecuteValidateLoggerDatabase { fn default() -> Self { let logger = Logger::default(); Self { storage: Storage::new(Some(Box::new({ let logger = logger.clone(); move |event| match event.kind { salsa::EventKind::WillExecute { .. } | salsa::EventKind::WillIterateCycle { .. } | salsa::EventKind::DidFinalizeCycle { .. } | salsa::EventKind::DidValidateInternedValue { .. } | salsa::EventKind::DidValidateMemoizedValue { .. } => { logger.push_log(format!("salsa_event({:?})", event.kind)); } _ => {} } }))), logger, } } } impl Database for ExecuteValidateLoggerDatabase {} impl HasLogger for ExecuteValidateLoggerDatabase { fn logger(&self) -> &Logger { &self.logger } } /// Trait implemented by databases that lets them provide a fixed u32 value. pub trait HasValue { fn get_value(&self) -> u32; } #[salsa::db] pub trait ValueDatabase: HasValue + Database {} #[salsa::db] impl ValueDatabase for Db {} #[salsa::db] #[derive(Clone, Default)] pub struct DatabaseWithValue { storage: Storage, value: u32, } impl HasValue for DatabaseWithValue { fn get_value(&self) -> u32 { self.value } } #[salsa::db] impl Database for DatabaseWithValue {} impl DatabaseWithValue { pub fn new(value: u32) -> Self { Self { storage: Default::default(), value, } } } salsa-0.26.2/tests/compile-fail/accumulator_incompatibles.rs000064400000000000000000000012241046102023000223160ustar 00000000000000#[salsa::accumulator(returns(ref))] struct AccWithRetRef(u32); #[salsa::accumulator(specify)] struct AccWithSpecify(u32); #[salsa::accumulator(no_eq)] struct AccWithNoEq(u32); #[salsa::accumulator(data = MyAcc)] struct AccWithData(u32); #[salsa::accumulator(db = Db)] struct AcWithcDb(u32); #[salsa::accumulator(recover_fn = recover)] struct AccWithRecover(u32); #[salsa::accumulator(lru = 12)] struct AccWithLru(u32); #[salsa::accumulator(revisions = 12)] struct AccWithRevisions(u32); #[salsa::accumulator(constructor = Constructor)] struct AccWithConstructor(u32); #[salsa::accumulator(heap_size = size)] struct AccWithHeapSize(u32); fn main() {} salsa-0.26.2/tests/compile-fail/accumulator_incompatibles.stderr000064400000000000000000000034101046102023000231740ustar 00000000000000error: `returns` option not allowed here --> tests/compile-fail/accumulator_incompatibles.rs:1:22 | 1 | #[salsa::accumulator(returns(ref))] | ^^^^^^^ error: `specify` option not allowed here --> tests/compile-fail/accumulator_incompatibles.rs:4:22 | 4 | #[salsa::accumulator(specify)] | ^^^^^^^ error: `no_eq` option not allowed here --> tests/compile-fail/accumulator_incompatibles.rs:7:22 | 7 | #[salsa::accumulator(no_eq)] | ^^^^^ error: `data` option not allowed here --> tests/compile-fail/accumulator_incompatibles.rs:10:22 | 10 | #[salsa::accumulator(data = MyAcc)] | ^^^^ error: `db` option not allowed here --> tests/compile-fail/accumulator_incompatibles.rs:13:22 | 13 | #[salsa::accumulator(db = Db)] | ^^ error: unrecognized option `recover_fn` --> tests/compile-fail/accumulator_incompatibles.rs:16:22 | 16 | #[salsa::accumulator(recover_fn = recover)] | ^^^^^^^^^^ error: `lru` option not allowed here --> tests/compile-fail/accumulator_incompatibles.rs:19:22 | 19 | #[salsa::accumulator(lru = 12)] | ^^^ error: `revisions` option not allowed here --> tests/compile-fail/accumulator_incompatibles.rs:22:22 | 22 | #[salsa::accumulator(revisions = 12)] | ^^^^^^^^^ error: `constructor` option not allowed here --> tests/compile-fail/accumulator_incompatibles.rs:25:22 | 25 | #[salsa::accumulator(constructor = Constructor)] | ^^^^^^^^^^^ error: `heap_size` option not allowed here --> tests/compile-fail/accumulator_incompatibles.rs:28:22 | 28 | #[salsa::accumulator(heap_size = size)] | ^^^^^^^^^ salsa-0.26.2/tests/compile-fail/derive_update_expansion_failure.rs000064400000000000000000000003311046102023000234770ustar 00000000000000#[derive(salsa::Update)] union U { field: i32, } #[derive(salsa::Update)] struct S { #[update(with(missing_unsafe))] bad: i32, } fn missing_unsafe(_: *mut i32, _: i32) -> bool { true } fn main() {} salsa-0.26.2/tests/compile-fail/derive_update_expansion_failure.stderr000064400000000000000000000004501046102023000243600ustar 00000000000000error: `derive(Update)` does not support `union` --> tests/compile-fail/derive_update_expansion_failure.rs:2:1 | 2 | union U { | ^^^^^ error: expected `unsafe` --> tests/compile-fail/derive_update_expansion_failure.rs:8:14 | 8 | #[update(with(missing_unsafe))] | ^^^^ salsa-0.26.2/tests/compile-fail/get-on-private-interned-field.rs000064400000000000000000000003201046102023000226120ustar 00000000000000mod a { #[salsa::interned] pub struct MyInterned<'db> { field: u32, } } fn test<'db>(db: &'db dyn salsa::Database, interned: a::MyInterned<'db>) { interned.field(db); } fn main() {} salsa-0.26.2/tests/compile-fail/get-on-private-interned-field.stderr000064400000000000000000000004021046102023000234720ustar 00000000000000error[E0624]: method `field` is private --> tests/compile-fail/get-on-private-interned-field.rs:9:14 | 2 | #[salsa::interned] | ------------------ private method defined here ... 9 | interned.field(db); | ^^^^^ private method salsa-0.26.2/tests/compile-fail/get-on-private-tracked-field.rs000064400000000000000000000003131046102023000224210ustar 00000000000000mod a { #[salsa::tracked] pub struct MyTracked<'db> { field: u32, } } fn test<'db>(db: &'db dyn salsa::Database, tracked: a::MyTracked<'db>) { tracked.field(db); } fn main() {} salsa-0.26.2/tests/compile-fail/get-on-private-tracked-field.stderr000064400000000000000000000003751046102023000233100ustar 00000000000000error[E0624]: method `field` is private --> tests/compile-fail/get-on-private-tracked-field.rs:9:13 | 2 | #[salsa::tracked] | ----------------- private method defined here ... 9 | tracked.field(db); | ^^^^^ private method salsa-0.26.2/tests/compile-fail/get-set-on-private-input-field.rs000064400000000000000000000003671046102023000227450ustar 00000000000000mod a { #[salsa::input] pub struct MyInput { field: u32, } } fn main() { let mut db = salsa::DatabaseImpl::new(); let input = a::MyInput::new(&mut db, 22); input.field(&db); input.set_field(&mut db).to(23); } salsa-0.26.2/tests/compile-fail/get-set-on-private-input-field.stderr000064400000000000000000000010261046102023000236150ustar 00000000000000error[E0624]: method `field` is private --> tests/compile-fail/get-set-on-private-input-field.rs:12:11 | 2 | #[salsa::input] | --------------- private method defined here ... 12 | input.field(&db); | ^^^^^ private method error[E0624]: method `set_field` is private --> tests/compile-fail/get-set-on-private-input-field.rs:13:11 | 2 | #[salsa::input] | --------------- private method defined here ... 13 | input.set_field(&mut db).to(23); | ^^^^^^^^^ private method salsa-0.26.2/tests/compile-fail/incomplete_persistence.rs000064400000000000000000000004101046102023000216250ustar 00000000000000#[salsa::tracked(persist)] struct Persistable<'db> { field: NotPersistable<'db>, } #[salsa::tracked] struct NotPersistable<'db> { field: usize, } #[salsa::tracked(persist)] fn query(_db: &dyn salsa::Database, _input: NotPersistable<'_>) {} fn main() {} salsa-0.26.2/tests/compile-fail/incomplete_persistence.stderr000064400000000000000000000124411046102023000225130ustar 00000000000000error[E0277]: the trait bound `NotPersistable<'_>: serde::Serialize` is not satisfied --> tests/compile-fail/incomplete_persistence.rs:1:1 | 1 | #[salsa::tracked(persist)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | unsatisfied trait bound | required by a bound introduced by this call | help: the trait `Serialize` is not implemented for `NotPersistable<'_>` --> tests/compile-fail/incomplete_persistence.rs:6:1 | 6 | #[salsa::tracked] | ^^^^^^^^^^^^^^^^^ = note: for local types consider adding `#[derive(serde::Serialize)]` to your `NotPersistable<'_>` type = note: for types from other crates check whether the crate offers a `serde` feature flag = help: the following other types implement trait `Serialize`: &'a T &'a mut T () (T,) (T0, T1) (T0, T1, T2) (T0, T1, T2, T3) (T0, T1, T2, T3, T4) and $N others = note: required for `(NotPersistable<'_>,)` to implement `Serialize` = note: this error originates in the macro `salsa::plumbing::setup_tracked_struct` which comes from the expansion of the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `NotPersistable<'_>: serde::Deserialize<'de>` is not satisfied --> tests/compile-fail/incomplete_persistence.rs:1:1 | 1 | #[salsa::tracked(persist)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound | help: the trait `Deserialize<'_>` is not implemented for `NotPersistable<'_>` --> tests/compile-fail/incomplete_persistence.rs:6:1 | 6 | #[salsa::tracked] | ^^^^^^^^^^^^^^^^^ = note: for local types consider adding `#[derive(serde::Deserialize)]` to your `NotPersistable<'_>` type = note: for types from other crates check whether the crate offers a `serde` feature flag = help: the following other types implement trait `Deserialize<'de>`: &'a Path &'a [u8] &'a str () (T,) (T0, T1) (T0, T1, T2) (T0, T1, T2, T3) and $N others = note: required for `(NotPersistable<'_>,)` to implement `Deserialize<'_>` = note: this error originates in the macro `salsa::plumbing::setup_tracked_struct` which comes from the expansion of the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `NotPersistable<'db>: serde::Serialize` is not satisfied --> tests/compile-fail/incomplete_persistence.rs:12:45 | 12 | fn query(_db: &dyn salsa::Database, _input: NotPersistable<'_>) {} | ^^^^^^^^^^^^^^^^^^ unsatisfied trait bound | help: the trait `Serialize` is not implemented for `NotPersistable<'db>` --> tests/compile-fail/incomplete_persistence.rs:6:1 | 6 | #[salsa::tracked] | ^^^^^^^^^^^^^^^^^ = note: for local types consider adding `#[derive(serde::Serialize)]` to your `NotPersistable<'db>` type = note: for types from other crates check whether the crate offers a `serde` feature flag = help: the following other types implement trait `Serialize`: &'a T &'a mut T () (T,) (T0, T1) (T0, T1, T2) (T0, T1, T2, T3) (T0, T1, T2, T3, T4) and $N others note: required by a bound in `query_input_is_persistable` --> tests/compile-fail/incomplete_persistence.rs:11:1 | 11 | #[salsa::tracked(persist)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | required by a bound in this function | required by this bound in `query_input_is_persistable` = note: this error originates in the macro `salsa::plumbing::setup_tracked_struct` which comes from the expansion of the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `NotPersistable<'db>: serde::Deserialize<'de>` is not satisfied --> tests/compile-fail/incomplete_persistence.rs:12:45 | 12 | fn query(_db: &dyn salsa::Database, _input: NotPersistable<'_>) {} | ^^^^^^^^^^^^^^^^^^ unsatisfied trait bound | help: the trait `for<'de> Deserialize<'de>` is not implemented for `NotPersistable<'db>` --> tests/compile-fail/incomplete_persistence.rs:6:1 | 6 | #[salsa::tracked] | ^^^^^^^^^^^^^^^^^ = note: for local types consider adding `#[derive(serde::Deserialize)]` to your `NotPersistable<'db>` type = note: for types from other crates check whether the crate offers a `serde` feature flag = help: the following other types implement trait `Deserialize<'de>`: &'a Path &'a [u8] &'a str () (T,) (T0, T1) (T0, T1, T2) (T0, T1, T2, T3) and $N others note: required by a bound in `query_input_is_persistable` --> tests/compile-fail/incomplete_persistence.rs:11:1 | 11 | #[salsa::tracked(persist)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | required by a bound in this function | required by this bound in `query_input_is_persistable` = note: this error originates in the macro `salsa::plumbing::setup_tracked_struct` which comes from the expansion of the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) salsa-0.26.2/tests/compile-fail/input_struct_incompatibles.rs000064400000000000000000000007621046102023000225500ustar 00000000000000#[salsa::input(returns(ref))] struct InputWithRetRef(u32); #[salsa::input(specify)] struct InputWithSpecify(u32); #[salsa::input(no_eq)] struct InputNoWithEq(u32); #[salsa::input(db = Db)] struct InputWithDb(u32); #[salsa::input(recover_fn = recover)] struct InputWithRecover(u32); #[salsa::input(lru =12)] struct InputWithLru(u32); #[salsa::input(revisions = 12)] struct InputWithRevisions(u32); #[salsa::input] struct InputWithTrackedField { #[tracked] field: u32, } fn main() {} salsa-0.26.2/tests/compile-fail/input_struct_incompatibles.stderr000064400000000000000000000027361046102023000234320ustar 00000000000000error: `returns` option not allowed here --> tests/compile-fail/input_struct_incompatibles.rs:1:16 | 1 | #[salsa::input(returns(ref))] | ^^^^^^^ error: `specify` option not allowed here --> tests/compile-fail/input_struct_incompatibles.rs:4:16 | 4 | #[salsa::input(specify)] | ^^^^^^^ error: `no_eq` option not allowed here --> tests/compile-fail/input_struct_incompatibles.rs:7:16 | 7 | #[salsa::input(no_eq)] | ^^^^^ error: `db` option not allowed here --> tests/compile-fail/input_struct_incompatibles.rs:10:16 | 10 | #[salsa::input(db = Db)] | ^^ error: unrecognized option `recover_fn` --> tests/compile-fail/input_struct_incompatibles.rs:13:16 | 13 | #[salsa::input(recover_fn = recover)] | ^^^^^^^^^^ error: `lru` option not allowed here --> tests/compile-fail/input_struct_incompatibles.rs:16:16 | 16 | #[salsa::input(lru =12)] | ^^^ error: `revisions` option not allowed here --> tests/compile-fail/input_struct_incompatibles.rs:19:16 | 19 | #[salsa::input(revisions = 12)] | ^^^^^^^^^ error: `#[tracked]` cannot be used with `#[salsa::input]` --> tests/compile-fail/input_struct_incompatibles.rs:24:5 | 24 | / #[tracked] 25 | | field: u32, | |______________^ error: cannot find attribute `tracked` in this scope --> tests/compile-fail/input_struct_incompatibles.rs:24:7 | 24 | #[tracked] | ^^^^^^^ salsa-0.26.2/tests/compile-fail/input_struct_unknown_attributes.rs000064400000000000000000000002041046102023000236530ustar 00000000000000#[salsa::input] struct InputWithUnknownAttrs { /// Doc comment field: u32, #[anything] field2: u32, } fn main() {} salsa-0.26.2/tests/compile-fail/input_struct_unknown_attributes.stderr000064400000000000000000000002401046102023000245320ustar 00000000000000error: cannot find attribute `anything` in this scope --> tests/compile-fail/input_struct_unknown_attributes.rs:5:7 | 5 | #[anything] | ^^^^^^^^ salsa-0.26.2/tests/compile-fail/interned_not_update.rs000064400000000000000000000003361046102023000211230ustar 00000000000000use salsa::Database as Db; #[salsa::input] struct MyInput {} #[salsa::tracked] fn tracked_fn<'db>(_db: &'db dyn Db, _: (), _: &'db str) {} #[salsa::interned] struct Interned<'db> { _field: &'db str, } fn main() {} salsa-0.26.2/tests/compile-fail/interned_not_update.stderr000064400000000000000000000010201046102023000217710ustar 00000000000000error: lifetime may not live long enough --> tests/compile-fail/interned_not_update.rs:7:48 | 7 | fn tracked_fn<'db>(_db: &'db dyn Db, _: (), _: &'db str) {} | --- lifetime `'db` defined here ^ requires that `'db` must outlive `'static` error: lifetime may not live long enough --> tests/compile-fail/interned_not_update.rs:11:13 | 10 | struct Interned<'db> { | --- lifetime `'db` defined here 11 | _field: &'db str, | ^ requires that `'db` must outlive `'static` salsa-0.26.2/tests/compile-fail/interned_struct_incompatibles.rs000064400000000000000000000012101046102023000232060ustar 00000000000000#[salsa::interned(returns(ref))] struct InternedWithRetRef { field: u32, } #[salsa::interned(specify)] struct InternedWithSpecify { field: u32, } #[salsa::interned(no_eq)] struct InternedWithNoEq { field: u32, } #[salsa::interned(db = Db)] struct InternedWithDb { field: u32, } #[salsa::interned(recover_fn = recover)] struct InternedWithRecover { field: u32, } #[salsa::interned(lru = 12)] struct InternedWithLru { field: u32, } #[salsa::interned] struct InternedWithTrackedField { #[tracked] field: u32, } #[salsa::interned(revisions = 0)] struct InternedWithZeroRevisions { field: u32, } fn main() {} salsa-0.26.2/tests/compile-fail/interned_struct_incompatibles.stderr000064400000000000000000000025621046102023000241000ustar 00000000000000error: `returns` option not allowed here --> tests/compile-fail/interned_struct_incompatibles.rs:1:19 | 1 | #[salsa::interned(returns(ref))] | ^^^^^^^ error: `specify` option not allowed here --> tests/compile-fail/interned_struct_incompatibles.rs:6:19 | 6 | #[salsa::interned(specify)] | ^^^^^^^ error: `no_eq` option not allowed here --> tests/compile-fail/interned_struct_incompatibles.rs:11:19 | 11 | #[salsa::interned(no_eq)] | ^^^^^ error: `db` option not allowed here --> tests/compile-fail/interned_struct_incompatibles.rs:16:19 | 16 | #[salsa::interned(db = Db)] | ^^ error: unrecognized option `recover_fn` --> tests/compile-fail/interned_struct_incompatibles.rs:21:19 | 21 | #[salsa::interned(recover_fn = recover)] | ^^^^^^^^^^ error: `lru` option not allowed here --> tests/compile-fail/interned_struct_incompatibles.rs:26:19 | 26 | #[salsa::interned(lru = 12)] | ^^^ error: `#[tracked]` cannot be used with `#[salsa::interned]` --> tests/compile-fail/interned_struct_incompatibles.rs:33:5 | 33 | / #[tracked] 34 | | field: u32, | |______________^ error: cannot find attribute `tracked` in this scope --> tests/compile-fail/interned_struct_incompatibles.rs:33:7 | 33 | #[tracked] | ^^^^^^^ salsa-0.26.2/tests/compile-fail/interned_struct_unknown_attribute.rs000064400000000000000000000003041046102023000241420ustar 00000000000000#[salsa::interned] struct UnknownAttributeInterned { /// Test doc comment field: bool, #[unknown_attr] field2: bool, #[salsa::tracked] wrong_tracked: bool, } fn main() {} salsa-0.26.2/tests/compile-fail/interned_struct_unknown_attribute.stderr000064400000000000000000000007421046102023000250270ustar 00000000000000error: only a single lifetime parameter is accepted --> tests/compile-fail/interned_struct_unknown_attribute.rs:1:1 | 1 | #[salsa::interned] | ^^^^^^^^^^^^^^^^^^ | = note: this error originates in the attribute macro `salsa::interned` (in Nightly builds, run with -Z macro-backtrace for more info) error: cannot find attribute `unknown_attr` in this scope --> tests/compile-fail/interned_struct_unknown_attribute.rs:5:7 | 5 | #[unknown_attr] | ^^^^^^^^^^^^ salsa-0.26.2/tests/compile-fail/invalid_persist_options.rs000064400000000000000000000025261046102023000220460ustar 00000000000000#[salsa::input(persist)] struct Input { text: String, } #[salsa::input(persist())] struct Input2 { text: String, } #[salsa::input(persist(serialize = serde::Serialize::serialize))] struct Input3 { text: String, } #[salsa::input(persist(deserialize = serde::Deserialize::deserialize))] struct Input4 { text: String, } #[salsa::input(persist(serialize = serde::Serialize::serialize, deserialize = serde::Deserialize::deserialize))] struct Input5 { text: String, } #[salsa::input(persist(serialize = serde::Serialize::serialize, serialize = serde::Serialize::serialize))] struct InvalidInput { text: String, } #[salsa::input(persist(deserialize = serde::Deserialize::deserialize, deserialize = serde::Deserialize::deserialize))] struct InvalidInput2 { text: String, } #[salsa::input(persist(not_an_option = std::convert::identity))] struct InvalidInput3 { text: String, } #[salsa::tracked(persist)] fn tracked_fn(db: &dyn salsa::Database, input: Input) -> String { input.text(db) } #[salsa::tracked(persist())] fn tracked_fn2(db: &dyn salsa::Database, input: Input) -> String { input.text(db) } #[salsa::tracked(persist(serialize = serde::Serialize::serialize, deserialize = serde::Deserialize::deserialize))] fn invalid_tracked_fn(db: &dyn salsa::Database, input: Input) -> String { input.text(db) } fn main() {} salsa-0.26.2/tests/compile-fail/invalid_persist_options.stderr000064400000000000000000000020531046102023000227200ustar 00000000000000error: option `serialize` provided twice --> tests/compile-fail/invalid_persist_options.rs:26:65 | 26 | #[salsa::input(persist(serialize = serde::Serialize::serialize, serialize = serde::Serialize::serialize))] | ^^^^^^^^^ error: option `deserialize` provided twice --> tests/compile-fail/invalid_persist_options.rs:31:71 | 31 | #[salsa::input(persist(deserialize = serde::Deserialize::deserialize, deserialize = serde::Deserialize::deserialize))] | ^^^^^^^^^^^ error: unexpected argument --> tests/compile-fail/invalid_persist_options.rs:36:24 | 36 | #[salsa::input(persist(not_an_option = std::convert::identity))] | ^^^^^^^^^^^^^ error: unexpected argument --> tests/compile-fail/invalid_persist_options.rs:51:26 | 51 | #[salsa::tracked(persist(serialize = serde::Serialize::serialize, deserialize = serde::Deserialize::deserialize))] | ^^^^^^^^^ salsa-0.26.2/tests/compile-fail/invalid_return_mode.rs000064400000000000000000000005431046102023000211220ustar 00000000000000use salsa::Database as Db; #[salsa::input] struct MyInput { #[returns(clone)] text: String, } #[salsa::tracked(returns(not_a_return_mode))] fn tracked_fn_invalid_return_mode(db: &dyn Db, input: MyInput) -> String { input.text(db) } #[salsa::input] struct MyInvalidInput { #[returns(not_a_return_mode)] text: String, } fn main() { }salsa-0.26.2/tests/compile-fail/invalid_return_mode.stderr000064400000000000000000000012311046102023000217740ustar 00000000000000error: Invalid return mode. Allowed modes are: ["copy", "clone", "ref", "deref", "as_ref", "as_deref"] --> tests/compile-fail/invalid_return_mode.rs:9:26 | 9 | #[salsa::tracked(returns(not_a_return_mode))] | ^^^^^^^^^^^^^^^^^ error: Invalid return mode. Allowed modes are: ["copy", "clone", "ref", "deref", "as_ref", "as_deref"] --> tests/compile-fail/invalid_return_mode.rs:16:15 | 16 | #[returns(not_a_return_mode)] | ^^^^^^^^^^^^^^^^^ error: cannot find attribute `returns` in this scope --> tests/compile-fail/invalid_return_mode.rs:16:7 | 16 | #[returns(not_a_return_mode)] | ^^^^^^^ salsa-0.26.2/tests/compile-fail/invalid_update_field.rs000064400000000000000000000001141046102023000212160ustar 00000000000000#[derive(salsa::Update)] struct S2<'a> { bad2: &'a str, } fn main() {} salsa-0.26.2/tests/compile-fail/invalid_update_field.stderr000064400000000000000000000006131046102023000221010ustar 00000000000000error: lifetime may not live long enough --> tests/compile-fail/invalid_update_field.rs:1:10 | 1 | #[derive(salsa::Update)] | ^^^^^^^^^^^^^ requires that `'a` must outlive `'static` 2 | struct S2<'a> { | -- lifetime `'a` defined here | = note: this error originates in the derive macro `salsa::Update` (in Nightly builds, run with -Z macro-backtrace for more info) salsa-0.26.2/tests/compile-fail/invalid_update_with.rs000064400000000000000000000006461046102023000211200ustar 00000000000000#[derive(salsa::Update)] struct S2 { #[update(unsafe(with(my_wrong_update)))] bad: i32, #[update(unsafe(with(my_wrong_update2)))] bad2: i32, #[update(unsafe(with(my_wrong_update3)))] bad3: i32, #[update(unsafe(with(true)))] bad4: &'static str, } fn my_wrong_update() {} fn my_wrong_update2(_: (), _: ()) -> bool { true } fn my_wrong_update3(_: *mut i32, _: i32) -> () {} fn main() {} salsa-0.26.2/tests/compile-fail/invalid_update_with.stderr000064400000000000000000000032121046102023000217670ustar 00000000000000error[E0308]: mismatched types --> tests/compile-fail/invalid_update_with.rs:3:26 | 3 | #[update(unsafe(with(my_wrong_update)))] | ---- ^^^^^^^^^^^^^^^ incorrect number of function parameters | | | expected due to this | = note: expected fn pointer `unsafe fn(*mut i32, i32) -> bool` found fn item `fn() -> () {my_wrong_update}` error[E0308]: mismatched types --> tests/compile-fail/invalid_update_with.rs:5:26 | 5 | #[update(unsafe(with(my_wrong_update2)))] | ---- ^^^^^^^^^^^^^^^^ expected fn pointer, found fn item | | | expected due to this | = note: expected fn pointer `unsafe fn(*mut i32, i32) -> bool` found fn item `fn((), ()) -> bool {my_wrong_update2}` error[E0308]: mismatched types --> tests/compile-fail/invalid_update_with.rs:7:26 | 7 | #[update(unsafe(with(my_wrong_update3)))] | ---- ^^^^^^^^^^^^^^^^ expected fn pointer, found fn item | | | expected due to this | = note: expected fn pointer `unsafe fn(*mut i32, i32) -> bool` found fn item `fn(*mut i32, i32) -> () {my_wrong_update3}` error[E0308]: mismatched types --> tests/compile-fail/invalid_update_with.rs:9:26 | 9 | #[update(unsafe(with(true)))] | ---- ^^^^ expected fn pointer, found `bool` | | | expected due to this | = note: expected fn pointer `unsafe fn(*mut &'static str, &'static str) -> bool` found type `bool` salsa-0.26.2/tests/compile-fail/lru_can_not_be_used_with_specify.rs000064400000000000000000000003231046102023000236330ustar 00000000000000#[salsa::input] struct MyInput { field: u32, } #[salsa::tracked(lru = 3, specify)] fn lru_can_not_be_used_with_specify(db: &dyn salsa::Database, input: MyInput) -> u32 { input.field(db) } fn main() {} salsa-0.26.2/tests/compile-fail/lru_can_not_be_used_with_specify.stderr000064400000000000000000000003221046102023000245110ustar 00000000000000error: the `specify` and `lru` options cannot be used together --> tests/compile-fail/lru_can_not_be_used_with_specify.rs:6:27 | 6 | #[salsa::tracked(lru = 3, specify)] | ^^^^^^^ salsa-0.26.2/tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs000064400000000000000000000010151046102023000315750ustar 00000000000000use salsa::prelude::*; #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyTracked<'db> { MyTracked::new(db, input.field(db) / 2) } fn main() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 22); let tracked = tracked_fn(&db, input); input.set_field(&mut db).to(24); tracked.field(&db); // tracked comes from prior revision } ././@LongLink00006440000000000000000000000151000000000000007770Lustar salsa-0.26.2/tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.stderrsalsa-0.26.2/tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.st000064400000000000000000000010121046102023000315740ustar 00000000000000error[E0502]: cannot borrow `db` as mutable because it is also borrowed as immutable --> tests/compile-fail/panic-when-reading-fields-of-tracked-structs-from-older-revisions.rs:22:21 | 21 | let tracked = tracked_fn(&db, input); | --- immutable borrow occurs here 22 | input.set_field(&mut db).to(24); | ^^^^^^^ mutable borrow occurs here 23 | tracked.field(&db); // tracked comes from prior revision | ------- immutable borrow later used here salsa-0.26.2/tests/compile-fail/salsa_fields_incompatibles.rs000064400000000000000000000003051046102023000224270ustar 00000000000000// Banned field name: `from` #[salsa::input] struct InputWithBannedName1 { from: u32, } // Banned field name: `new` #[salsa::input] struct InputWithBannedName2 { new: u32, } fn main() {} salsa-0.26.2/tests/compile-fail/salsa_fields_incompatibles.stderr000064400000000000000000000004671046102023000233170ustar 00000000000000error: the field name `from` is disallowed in salsa structs --> tests/compile-fail/salsa_fields_incompatibles.rs:4:5 | 4 | from: u32, | ^^^^ error: the field name `new` is disallowed in salsa structs --> tests/compile-fail/salsa_fields_incompatibles.rs:10:5 | 10 | new: u32, | ^^^ salsa-0.26.2/tests/compile-fail/singleton_only_for_input.rs000064400000000000000000000010631046102023000222170ustar 00000000000000//! Compile Singleton struct test: //! //! Singleton flags are only allowed for input structs. If applied on any other Salsa struct compilation must fail #[salsa::input(singleton)] struct MyInput { field: u32, } #[salsa::tracked(singleton)] struct MyTracked<'db> { field: u32, } #[salsa::tracked(singleton)] fn create_tracked_structs(db: &dyn salsa::Database, input: MyInput) -> Vec { (0..input.field(db)) .map(|i| MyTracked::new(db, i)) .collect() } #[salsa::accumulator(singleton)] struct Integers(u32); fn main() {} salsa-0.26.2/tests/compile-fail/singleton_only_for_input.stderr000064400000000000000000000005431046102023000231000ustar 00000000000000error: `singleton` option not allowed here --> tests/compile-fail/singleton_only_for_input.rs:15:18 | 15 | #[salsa::tracked(singleton)] | ^^^^^^^^^ error: `singleton` option not allowed here --> tests/compile-fail/singleton_only_for_input.rs:22:22 | 22 | #[salsa::accumulator(singleton)] | ^^^^^^^^^ salsa-0.26.2/tests/compile-fail/span-input-setter.rs000064400000000000000000000003151046102023000204700ustar 00000000000000#[salsa::input] pub struct MyInput { field: u32, } fn main() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&mut db, 22); input.field(&db); input.set_field(22); } salsa-0.26.2/tests/compile-fail/span-input-setter.stderr000064400000000000000000000012051046102023000213460ustar 00000000000000error[E0308]: mismatched types --> tests/compile-fail/span-input-setter.rs:10:21 | 10 | input.set_field(22); | --------- ^^ expected `&mut _`, found integer | | | arguments to this method are incorrect | = note: expected mutable reference `&mut _` found type `{integer}` note: method defined here --> tests/compile-fail/span-input-setter.rs:3:5 | 1 | #[salsa::input] | --------------- 2 | pub struct MyInput { 3 | field: u32, | ^^^^^ help: consider mutably borrowing here | 10 | input.set_field(&mut 22); | ++++ salsa-0.26.2/tests/compile-fail/span-tracked-getter.rs000064400000000000000000000003731046102023000207360ustar 00000000000000#[salsa::tracked] pub struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn my_fn(db: &dyn salsa::Database) { let x = MyTracked::new(db, 22); x.field(22); } fn main() { let mut db = salsa::DatabaseImpl::new(); my_fn(&db); } salsa-0.26.2/tests/compile-fail/span-tracked-getter.stderr000064400000000000000000000015441046102023000216160ustar 00000000000000error[E0308]: mismatched types --> tests/compile-fail/span-tracked-getter.rs:9:13 | 9 | x.field(22); | ----- ^^ expected `&_`, found integer | | | arguments to this method are incorrect | = note: expected reference `&_` found type `{integer}` note: method defined here --> tests/compile-fail/span-tracked-getter.rs:3:5 | 1 | #[salsa::tracked] | ----------------- 2 | pub struct MyTracked<'db> { 3 | field: u32, | ^^^^^ help: consider borrowing here | 9 | x.field(&22); | + warning: variable does not need to be mutable --> tests/compile-fail/span-tracked-getter.rs:13:9 | 13 | let mut db = salsa::DatabaseImpl::new(); | ----^^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default salsa-0.26.2/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.rs000064400000000000000000000006151046102023000262660ustar 00000000000000//! Test that `specify` does not work if the key is a `salsa::input` //! compilation fails #![allow(warnings)] #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked(specify)] fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyTracked<'db> { MyTracked::new(db, input.field(db) * 2) } fn main() {} salsa-0.26.2/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.stderr000064400000000000000000000027021046102023000271440ustar 00000000000000error[E0277]: the trait bound `MyInput: salsa::plumbing::TrackedStructInDb` is not satisfied --> tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.rs:15:1 | 15 | #[salsa::tracked(specify)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound | help: the trait `salsa::plumbing::TrackedStructInDb` is not implemented for `MyInput` --> tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.rs:5:1 | 5 | #[salsa::input] | ^^^^^^^^^^^^^^^ help: the trait `salsa::plumbing::TrackedStructInDb` is implemented for `MyTracked<'_>` --> tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-input.rs:10:1 | 10 | #[salsa::tracked] | ^^^^^^^^^^^^^^^^^ note: required by a bound in `salsa::function::specify::>::specify_and_record` --> src/function/specify.rs | | pub fn specify_and_record<'db>(&'db self, db: &'db C::DbView, key: Id, value: C::Output<'db>) | ------------------ required by a bound in this associated function | where | C::Input<'db>: TrackedStructInDb, | ^^^^^^^^^^^^^^^^^ required by this bound in `salsa::function::specify::>::specify_and_record` = note: this error originates in the macro `salsa::plumbing::setup_tracked_fn` which comes from the expansion of the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) salsa-0.26.2/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs000064400000000000000000000006431046102023000267400ustar 00000000000000//! Test that `specify` does not work if the key is a `salsa::interned` //! compilation fails #![allow(warnings)] #[salsa::interned] struct MyInterned<'db> { field: u32, } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked(specify)] fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInterned<'db>) -> MyTracked<'db> { MyTracked::new(db, input.field(db) * 2) } fn main() {} salsa-0.26.2/tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.stderr000064400000000000000000000027371046102023000276250ustar 00000000000000error[E0277]: the trait bound `MyInterned<'_>: salsa::plumbing::TrackedStructInDb` is not satisfied --> tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs:15:1 | 15 | #[salsa::tracked(specify)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound | help: the trait `salsa::plumbing::TrackedStructInDb` is not implemented for `MyInterned<'_>` --> tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs:5:1 | 5 | #[salsa::interned] | ^^^^^^^^^^^^^^^^^^ help: the trait `salsa::plumbing::TrackedStructInDb` is implemented for `MyTracked<'_>` --> tests/compile-fail/specify-does-not-work-if-the-key-is-a-salsa-interned.rs:10:1 | 10 | #[salsa::tracked] | ^^^^^^^^^^^^^^^^^ note: required by a bound in `salsa::function::specify::>::specify_and_record` --> src/function/specify.rs | | pub fn specify_and_record<'db>(&'db self, db: &'db C::DbView, key: Id, value: C::Output<'db>) | ------------------ required by a bound in this associated function | where | C::Input<'db>: TrackedStructInDb, | ^^^^^^^^^^^^^^^^^ required by this bound in `salsa::function::specify::>::specify_and_record` = note: this error originates in the macro `salsa::plumbing::setup_tracked_fn` which comes from the expansion of the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) salsa-0.26.2/tests/compile-fail/tracked_fn_incompatibles.rs000064400000000000000000000031511046102023000221000ustar 00000000000000use salsa::Database as Db; #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked(data = Data)] fn tracked_fn_with_data(db: &dyn Db, input: MyInput) -> u32 { input.field(db) * 2 } #[salsa::tracked(db = Db)] fn tracked_fn_with_db(db: &dyn Db, input: MyInput) -> u32 { input.field(db) * 2 } #[salsa::tracked(revisions = 12)] fn tracked_fn_with_revisions(db: &dyn Db, input: MyInput) -> u32 { input.field(db) * 2 } #[salsa::tracked(constructor = TrackedFn3)] fn tracked_fn_with_constructor(db: &dyn Db, input: MyInput) -> u32 { input.field(db) * 2 } #[salsa::tracked] fn tracked_fn_with_one_input(db: &dyn Db) -> u32 {} #[salsa::tracked] fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {} #[salsa::tracked(specify)] fn tracked_fn_with_too_many_arguments_for_specify( db: &dyn Db, input: MyInput, input: MyInput, ) -> u32 { } #[salsa::interned] struct MyInterned<'db> { field: u32, } #[salsa::tracked] fn tracked_fn_with_lt_param_and_elided_lt_on_db_arg1<'db>( db: &dyn Db, interned: MyInterned<'db>, ) -> u32 { interned.field(db) * 2 } #[salsa::tracked] fn tracked_fn_with_lt_param_and_elided_lt_on_db_arg2<'db_lifetime>( db: &dyn Db, interned: MyInterned<'db_lifetime>, ) -> u32 { interned.field(db) * 2 } #[salsa::tracked] fn tracked_fn_with_lt_param_and_elided_lt_on_input<'db>( db: &'db dyn Db, interned: MyInterned, ) -> u32 { interned.field(db) * 2 } #[salsa::tracked] fn tracked_fn_with_multiple_lts<'db1, 'db2>(db: &'db1 dyn Db, interned: MyInterned<'db2>) -> u32 { interned.field(db) * 2 } fn main() {} salsa-0.26.2/tests/compile-fail/tracked_fn_incompatibles.stderr000064400000000000000000000072741046102023000227710ustar 00000000000000error: `data` option not allowed here --> tests/compile-fail/tracked_fn_incompatibles.rs:8:18 | 8 | #[salsa::tracked(data = Data)] | ^^^^ error: `db` option not allowed here --> tests/compile-fail/tracked_fn_incompatibles.rs:13:18 | 13 | #[salsa::tracked(db = Db)] | ^^ error: `revisions` option not allowed here --> tests/compile-fail/tracked_fn_incompatibles.rs:18:18 | 18 | #[salsa::tracked(revisions = 12)] | ^^^^^^^^^ error: `constructor` option not allowed here --> tests/compile-fail/tracked_fn_incompatibles.rs:23:18 | 23 | #[salsa::tracked(constructor = TrackedFn3)] | ^^^^^^^^^^^ error: #[salsa::tracked] must also be applied to the impl block for tracked methods --> tests/compile-fail/tracked_fn_incompatibles.rs:32:55 | 32 | fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {} | ^^^^^ error: only functions with a single salsa struct as their input can be specified --> tests/compile-fail/tracked_fn_incompatibles.rs:34:18 | 34 | #[salsa::tracked(specify)] | ^^^^^^^ error: must have a `'db` lifetime --> tests/compile-fail/tracked_fn_incompatibles.rs:49:9 | 49 | db: &dyn Db, | ^ error: must have a `'db_lifetime` lifetime --> tests/compile-fail/tracked_fn_incompatibles.rs:57:9 | 57 | db: &dyn Db, | ^ error: only a single lifetime parameter is accepted --> tests/compile-fail/tracked_fn_incompatibles.rs:72:39 | 72 | fn tracked_fn_with_multiple_lts<'db1, 'db2>(db: &'db1 dyn Db, interned: MyInterned<'db2>) -> u32 { | ^^^^ error: `self` parameter is only allowed in associated functions --> tests/compile-fail/tracked_fn_incompatibles.rs:32:55 | 32 | fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {} | ^^^^^ not semantically valid as function parameter | = note: associated functions are those in `impl` or `trait` definitions error[E0415]: identifier `input` is bound more than once in this parameter list --> tests/compile-fail/tracked_fn_incompatibles.rs:38:5 | 38 | input: MyInput, | ^^^^^ used as parameter more than once error[E0106]: missing lifetime specifier --> tests/compile-fail/tracked_fn_incompatibles.rs:66:15 | 66 | interned: MyInterned, | ^^^^^^^^^^ expected named lifetime parameter | help: consider using the `'db` lifetime | 66 | interned: MyInterned<'db>, | +++++ error[E0308]: mismatched types --> tests/compile-fail/tracked_fn_incompatibles.rs:29:46 | 28 | #[salsa::tracked] | ----------------- implicitly returns `()` as its body has no tail or `return` expression 29 | fn tracked_fn_with_one_input(db: &dyn Db) -> u32 {} | ^^^ expected `u32`, found `()` error[E0308]: mismatched types --> tests/compile-fail/tracked_fn_incompatibles.rs:32:78 | 32 | fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {} | -------------------------------------------------- ^^^ expected `u32`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression error[E0308]: mismatched types --> tests/compile-fail/tracked_fn_incompatibles.rs:39:6 | 35 | fn tracked_fn_with_too_many_arguments_for_specify( | ---------------------------------------------- implicitly returns `()` as its body has no tail or `return` expression ... 39 | ) -> u32 { | ^^^ expected `u32`, found `()` salsa-0.26.2/tests/compile-fail/tracked_fn_return_not_update.rs000064400000000000000000000003621046102023000230110ustar 00000000000000use salsa::Database as Db; #[salsa::input] struct MyInput {} #[derive(Clone, Debug)] struct NotUpdate; #[salsa::tracked] fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> NotUpdate { _ = (db, input); NotUpdate } fn main() {} salsa-0.26.2/tests/compile-fail/tracked_fn_return_not_update.stderr000064400000000000000000000035531046102023000236750ustar 00000000000000error[E0369]: binary operation `==` cannot be applied to type `&NotUpdate` --> tests/compile-fail/tracked_fn_return_not_update.rs:10:56 | 10 | fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> NotUpdate { | ^^^^^^^^^ | note: an implementation of `PartialEq` might be missing for `NotUpdate` --> tests/compile-fail/tracked_fn_return_not_update.rs:7:1 | 7 | struct NotUpdate; | ^^^^^^^^^^^^^^^^ must implement `PartialEq` help: consider annotating `NotUpdate` with `#[derive(PartialEq)]` | 7 + #[derive(PartialEq)] 8 | struct NotUpdate; | error[E0599]: the function or associated item `maybe_update` exists for struct `salsa::plumbing::UpdateDispatch`, but its trait bounds were not satisfied --> tests/compile-fail/tracked_fn_return_not_update.rs:10:56 | 7 | struct NotUpdate; | ---------------- doesn't satisfy `NotUpdate: PartialEq` or `NotUpdate: Update` ... 10 | fn tracked_fn<'db>(db: &'db dyn Db, input: MyInput) -> NotUpdate { | ^^^^^^^^^ function or associated item cannot be called on `salsa::plumbing::UpdateDispatch` due to unsatisfied trait bounds | ::: src/update.rs | | pub struct Dispatch(PhantomData); | ---------------------- doesn't satisfy `_: UpdateFallback` | = note: the following trait bounds were not satisfied: `NotUpdate: Update` `NotUpdate: PartialEq` which is required by `salsa::plumbing::UpdateDispatch: salsa::plumbing::UpdateFallback` note: the trait `Update` must be implemented --> src/update.rs | | pub unsafe trait Update { | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider annotating `NotUpdate` with `#[derive(PartialEq)]` | 7 + #[derive(PartialEq)] 8 | struct NotUpdate; | salsa-0.26.2/tests/compile-fail/tracked_fn_return_ref.rs000064400000000000000000000016211046102023000214220ustar 00000000000000use salsa::Database as Db; #[salsa::input] struct MyInput { #[returns(ref)] text: String, } #[derive(Clone, Debug, PartialEq, Eq)] struct ContainsRef<'db> { text: &'db str, } #[salsa::tracked] fn tracked_fn_return_ref<'db>(db: &'db dyn Db, input: MyInput) -> &'db str { input.text(db) } #[salsa::tracked] fn tracked_fn_return_struct_containing_ref<'db>( db: &'db dyn Db, input: MyInput, ) -> ContainsRef<'db> { ContainsRef { text: input.text(db), } } #[salsa::tracked] fn tracked_fn_return_struct_containing_ref_elided_implicit<'db>( db: &'db dyn Db, input: MyInput, ) -> ContainsRef { ContainsRef { text: input.text(db), } } #[salsa::tracked] fn tracked_fn_return_struct_containing_ref_elided_explicit<'db>( db: &'db dyn Db, input: MyInput, ) -> ContainsRef<'_> { ContainsRef { text: input.text(db), } } fn main() {} salsa-0.26.2/tests/compile-fail/tracked_fn_return_ref.stderr000064400000000000000000000024071046102023000223040ustar 00000000000000error[E0106]: missing lifetime specifier --> tests/compile-fail/tracked_fn_return_ref.rs:33:6 | 33 | ) -> ContainsRef { | ^^^^^^^^^^^ expected named lifetime parameter | help: consider using the `'db` lifetime | 33 | ) -> ContainsRef<'db> { | +++++ error: lifetime may not live long enough --> tests/compile-fail/tracked_fn_return_ref.rs:15:67 | 15 | fn tracked_fn_return_ref<'db>(db: &'db dyn Db, input: MyInput) -> &'db str { | --- lifetime `'db` defined here ^ requires that `'db` must outlive `'static` error: lifetime may not live long enough --> tests/compile-fail/tracked_fn_return_ref.rs:23:6 | 20 | fn tracked_fn_return_struct_containing_ref<'db>( | --- lifetime `'db` defined here ... 23 | ) -> ContainsRef<'db> { | ^^^^^^^^^^^ requires that `'db` must outlive `'static` error: lifetime may not live long enough --> tests/compile-fail/tracked_fn_return_ref.rs:43:6 | 40 | fn tracked_fn_return_struct_containing_ref_elided_explicit<'db>( | --- lifetime `'db` defined here ... 43 | ) -> ContainsRef<'_> { | ^^^^^^^^^^^ requires that `'db` must outlive `'static` salsa-0.26.2/tests/compile-fail/tracked_impl_incompatibles.rs000064400000000000000000000023131046102023000224350ustar 00000000000000#[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked(returns(ref))] impl<'db> std::default::Default for MyTracked<'db> { fn default() -> Self {} } #[salsa::tracked(specify)] impl<'db> std::default::Default for MyTracked<'db> { fn default() -> Self {} } #[salsa::tracked(no_eq)] impl<'db> std::default::Default for MyTracked<'db> { fn default() -> Self {} } #[salsa::tracked(data = Data)] impl<'db> std::default::Default for MyTracked<'db> { fn default() -> Self {} } #[salsa::tracked(db = Db)] impl<'db> std::default::Default for MyTracked<'db> { fn default() -> Self {} } #[salsa::tracked(recover_fn = recover)] impl<'db> std::default::Default for MyTracked<'db> { fn default() -> Self {} } #[salsa::tracked(lru = 32)] impl<'db> std::default::Default for MyTracked<'db> { fn default() -> Self {} } #[salsa::tracked(revisions = 32)] impl<'db> std::default::Default for MyTracked<'db> { fn default() -> Self {} } #[salsa::tracked(constructor = Constructor)] impl<'db> std::default::Default for MyTracked<'db> { fn default() -> Self {} } #[salsa::tracked] impl<'db> std::default::Default for [MyTracked<'db>; 12] { fn default() -> Self {} } fn main() {} salsa-0.26.2/tests/compile-fail/tracked_impl_incompatibles.stderr000064400000000000000000000211471046102023000233220ustar 00000000000000error: unexpected token --> tests/compile-fail/tracked_impl_incompatibles.rs:6:18 | 6 | #[salsa::tracked(returns(ref))] | ^^^^^^^ error: unexpected token --> tests/compile-fail/tracked_impl_incompatibles.rs:11:18 | 11 | #[salsa::tracked(specify)] | ^^^^^^^ error: unexpected token --> tests/compile-fail/tracked_impl_incompatibles.rs:16:18 | 16 | #[salsa::tracked(no_eq)] | ^^^^^ error: unexpected token --> tests/compile-fail/tracked_impl_incompatibles.rs:21:18 | 21 | #[salsa::tracked(data = Data)] | ^^^^ error: unexpected token --> tests/compile-fail/tracked_impl_incompatibles.rs:26:18 | 26 | #[salsa::tracked(db = Db)] | ^^ error: unexpected token --> tests/compile-fail/tracked_impl_incompatibles.rs:31:18 | 31 | #[salsa::tracked(recover_fn = recover)] | ^^^^^^^^^^ error: unexpected token --> tests/compile-fail/tracked_impl_incompatibles.rs:36:18 | 36 | #[salsa::tracked(lru = 32)] | ^^^ error: unexpected token --> tests/compile-fail/tracked_impl_incompatibles.rs:41:18 | 41 | #[salsa::tracked(revisions = 32)] | ^^^^^^^^^ error: unexpected token --> tests/compile-fail/tracked_impl_incompatibles.rs:46:18 | 46 | #[salsa::tracked(constructor = Constructor)] | ^^^^^^^^^^^ error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>` --> tests/compile-fail/tracked_impl_incompatibles.rs:12:1 | 7 | impl<'db> std::default::Default for MyTracked<'db> { | -------------------------------------------------- first implementation here ... 12 | impl<'db> std::default::Default for MyTracked<'db> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>` error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>` --> tests/compile-fail/tracked_impl_incompatibles.rs:17:1 | 7 | impl<'db> std::default::Default for MyTracked<'db> { | -------------------------------------------------- first implementation here ... 17 | impl<'db> std::default::Default for MyTracked<'db> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>` error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>` --> tests/compile-fail/tracked_impl_incompatibles.rs:22:1 | 7 | impl<'db> std::default::Default for MyTracked<'db> { | -------------------------------------------------- first implementation here ... 22 | impl<'db> std::default::Default for MyTracked<'db> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>` error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>` --> tests/compile-fail/tracked_impl_incompatibles.rs:27:1 | 7 | impl<'db> std::default::Default for MyTracked<'db> { | -------------------------------------------------- first implementation here ... 27 | impl<'db> std::default::Default for MyTracked<'db> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>` error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>` --> tests/compile-fail/tracked_impl_incompatibles.rs:32:1 | 7 | impl<'db> std::default::Default for MyTracked<'db> { | -------------------------------------------------- first implementation here ... 32 | impl<'db> std::default::Default for MyTracked<'db> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>` error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>` --> tests/compile-fail/tracked_impl_incompatibles.rs:37:1 | 7 | impl<'db> std::default::Default for MyTracked<'db> { | -------------------------------------------------- first implementation here ... 37 | impl<'db> std::default::Default for MyTracked<'db> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>` error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>` --> tests/compile-fail/tracked_impl_incompatibles.rs:42:1 | 7 | impl<'db> std::default::Default for MyTracked<'db> { | -------------------------------------------------- first implementation here ... 42 | impl<'db> std::default::Default for MyTracked<'db> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>` error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>` --> tests/compile-fail/tracked_impl_incompatibles.rs:47:1 | 7 | impl<'db> std::default::Default for MyTracked<'db> { | -------------------------------------------------- first implementation here ... 47 | impl<'db> std::default::Default for MyTracked<'db> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>` error[E0117]: only traits defined in the current crate can be implemented for arbitrary types --> tests/compile-fail/tracked_impl_incompatibles.rs:52:1 | 52 | impl<'db> std::default::Default for [MyTracked<'db>; 12] { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-------------------- | | | this is not defined in the current crate because arrays are always foreign | = note: impl doesn't have any local type before any uncovered type parameters = note: for more information see https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules = note: define and implement a trait or new type instead error[E0308]: mismatched types --> tests/compile-fail/tracked_impl_incompatibles.rs:8:21 | 8 | fn default() -> Self {} | ------- ^^^^ expected `MyTracked<'_>`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression error[E0308]: mismatched types --> tests/compile-fail/tracked_impl_incompatibles.rs:13:21 | 13 | fn default() -> Self {} | ------- ^^^^ expected `MyTracked<'_>`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression error[E0308]: mismatched types --> tests/compile-fail/tracked_impl_incompatibles.rs:18:21 | 18 | fn default() -> Self {} | ------- ^^^^ expected `MyTracked<'_>`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression error[E0308]: mismatched types --> tests/compile-fail/tracked_impl_incompatibles.rs:23:21 | 23 | fn default() -> Self {} | ------- ^^^^ expected `MyTracked<'_>`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression error[E0308]: mismatched types --> tests/compile-fail/tracked_impl_incompatibles.rs:28:21 | 28 | fn default() -> Self {} | ------- ^^^^ expected `MyTracked<'_>`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression error[E0308]: mismatched types --> tests/compile-fail/tracked_impl_incompatibles.rs:33:21 | 33 | fn default() -> Self {} | ------- ^^^^ expected `MyTracked<'_>`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression error[E0308]: mismatched types --> tests/compile-fail/tracked_impl_incompatibles.rs:38:21 | 38 | fn default() -> Self {} | ------- ^^^^ expected `MyTracked<'_>`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression error[E0308]: mismatched types --> tests/compile-fail/tracked_impl_incompatibles.rs:43:21 | 43 | fn default() -> Self {} | ------- ^^^^ expected `MyTracked<'_>`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression error[E0308]: mismatched types --> tests/compile-fail/tracked_impl_incompatibles.rs:48:21 | 48 | fn default() -> Self {} | ------- ^^^^ expected `MyTracked<'_>`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression error[E0308]: mismatched types --> tests/compile-fail/tracked_impl_incompatibles.rs:53:21 | 53 | fn default() -> Self {} | ------- ^^^^ expected `[MyTracked<'_>; 12]`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression | = note: expected array `[MyTracked<'db>; 12]` found unit type `()` salsa-0.26.2/tests/compile-fail/tracked_method_incompatibles.rs000064400000000000000000000011601046102023000227530ustar 00000000000000#[salsa::tracked] struct Tracked<'db> { field: u32, } #[salsa::tracked] impl<'db> Tracked<'db> { #[salsa::tracked] fn ref_self(&self, db: &dyn salsa::Database) {} } #[salsa::tracked] impl<'db> Tracked<'db> { #[salsa::tracked] fn ref_mut_self(&mut self, db: &dyn salsa::Database) {} } #[salsa::tracked] impl<'db> Tracked<'db> { #[salsa::tracked] fn multiple_lifetimes<'db1>(&mut self, db: &'db1 dyn salsa::Database) {} } #[salsa::tracked] impl<'db> Tracked<'db> { #[salsa::tracked] fn type_generics(&mut self, db: &dyn salsa::Database) -> T { panic!() } } fn main() {} salsa-0.26.2/tests/compile-fail/tracked_method_incompatibles.stderr000064400000000000000000000061231046102023000236360ustar 00000000000000error: #[salsa::tracked] must also be applied to the impl block for tracked methods --> tests/compile-fail/tracked_method_incompatibles.rs:9:17 | 9 | fn ref_self(&self, db: &dyn salsa::Database) {} | ^^^^^ error: tracked methods's first argument must be declared as `self`, not `&self` or `&mut self` --> tests/compile-fail/tracked_method_incompatibles.rs:9:17 | 9 | fn ref_self(&self, db: &dyn salsa::Database) {} | ^ error: #[salsa::tracked] must also be applied to the impl block for tracked methods --> tests/compile-fail/tracked_method_incompatibles.rs:15:21 | 15 | fn ref_mut_self(&mut self, db: &dyn salsa::Database) {} | ^^^^^^^^^ error: tracked methods's first argument must be declared as `self`, not `&self` or `&mut self` --> tests/compile-fail/tracked_method_incompatibles.rs:15:21 | 15 | fn ref_mut_self(&mut self, db: &dyn salsa::Database) {} | ^ error: #[salsa::tracked] must also be applied to the impl block for tracked methods --> tests/compile-fail/tracked_method_incompatibles.rs:21:33 | 21 | fn multiple_lifetimes<'db1>(&mut self, db: &'db1 dyn salsa::Database) {} | ^^^^^^^^^ error: tracked method already has a lifetime parameter in scope --> tests/compile-fail/tracked_method_incompatibles.rs:21:27 | 21 | fn multiple_lifetimes<'db1>(&mut self, db: &'db1 dyn salsa::Database) {} | ^^^^ error: only a single lifetime parameter is accepted --> tests/compile-fail/tracked_method_incompatibles.rs:27:22 | 27 | fn type_generics(&mut self, db: &dyn salsa::Database) -> T { | ^ error: tracked methods cannot have non-lifetime generic parameters --> tests/compile-fail/tracked_method_incompatibles.rs:27:22 | 27 | fn type_generics(&mut self, db: &dyn salsa::Database) -> T { | ^ warning: unused variable: `db` --> tests/compile-fail/tracked_method_incompatibles.rs:9:24 | 9 | fn ref_self(&self, db: &dyn salsa::Database) {} | ^^ help: if this is intentional, prefix it with an underscore: `_db` | = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default warning: unused variable: `db` --> tests/compile-fail/tracked_method_incompatibles.rs:15:32 | 15 | fn ref_mut_self(&mut self, db: &dyn salsa::Database) {} | ^^ help: if this is intentional, prefix it with an underscore: `_db` warning: unused variable: `db` --> tests/compile-fail/tracked_method_incompatibles.rs:21:44 | 21 | fn multiple_lifetimes<'db1>(&mut self, db: &'db1 dyn salsa::Database) {} | ^^ help: if this is intentional, prefix it with an underscore: `_db` warning: unused variable: `db` --> tests/compile-fail/tracked_method_incompatibles.rs:27:36 | 27 | fn type_generics(&mut self, db: &dyn salsa::Database) -> T { | ^^ help: if this is intentional, prefix it with an underscore: `_db` salsa-0.26.2/tests/compile-fail/tracked_method_on_untracked_impl.rs000064400000000000000000000003131046102023000236160ustar 00000000000000#[salsa::input] struct MyInput { field: u32, } impl MyInput { #[salsa::tracked] fn tracked_method_on_untracked_impl(self, db: &dyn Db) -> u32 { input.field(db) } } fn main() {} salsa-0.26.2/tests/compile-fail/tracked_method_on_untracked_impl.stderr000064400000000000000000000013521046102023000245010ustar 00000000000000error: #[salsa::tracked] must also be applied to the impl block for tracked methods --> tests/compile-fail/tracked_method_on_untracked_impl.rs:8:41 | 8 | fn tracked_method_on_untracked_impl(self, db: &dyn Db) -> u32 { | ^^^^ error[E0405]: cannot find trait `Db` in this scope --> tests/compile-fail/tracked_method_on_untracked_impl.rs:8:56 | 8 | fn tracked_method_on_untracked_impl(self, db: &dyn Db) -> u32 { | ^^ not found in this scope error[E0425]: cannot find value `input` in this scope --> tests/compile-fail/tracked_method_on_untracked_impl.rs:9:9 | 9 | input.field(db) | ^^^^^ not found in this scope salsa-0.26.2/tests/compile-fail/tracked_struct_incompatibles.rs000064400000000000000000000011031046102023000230140ustar 00000000000000#[salsa::tracked(returns(ref))] struct TrackedWithRetRef { field: u32, } #[salsa::tracked(specify)] struct TrackedSructWithSpecify { field: u32, } #[salsa::tracked(no_eq)] struct TrackedStructWithNoEq { field: u32, } #[salsa::tracked(db = Db)] struct TrackedStructWithDb { field: u32, } #[salsa::tracked(recover_fn = recover)] struct TrackedStructWithRecover { field: u32, } #[salsa::tracked(lru = 12)] struct TrackedStructWithLru { field: u32, } #[salsa::tracked(revisions = 12)] struct TrackedStructWithRevisions { field: u32, } fn main() {} salsa-0.26.2/tests/compile-fail/tracked_struct_incompatibles.stderr000064400000000000000000000022611046102023000237010ustar 00000000000000error: `returns` option not allowed here --> tests/compile-fail/tracked_struct_incompatibles.rs:1:18 | 1 | #[salsa::tracked(returns(ref))] | ^^^^^^^ error: `specify` option not allowed here --> tests/compile-fail/tracked_struct_incompatibles.rs:6:18 | 6 | #[salsa::tracked(specify)] | ^^^^^^^ error: `no_eq` option not allowed here --> tests/compile-fail/tracked_struct_incompatibles.rs:11:18 | 11 | #[salsa::tracked(no_eq)] | ^^^^^ error: `db` option not allowed here --> tests/compile-fail/tracked_struct_incompatibles.rs:16:18 | 16 | #[salsa::tracked(db = Db)] | ^^ error: unrecognized option `recover_fn` --> tests/compile-fail/tracked_struct_incompatibles.rs:21:18 | 21 | #[salsa::tracked(recover_fn = recover)] | ^^^^^^^^^^ error: `lru` option not allowed here --> tests/compile-fail/tracked_struct_incompatibles.rs:26:18 | 26 | #[salsa::tracked(lru = 12)] | ^^^ error: `revisions` option not allowed here --> tests/compile-fail/tracked_struct_incompatibles.rs:31:18 | 31 | #[salsa::tracked(revisions = 12)] | ^^^^^^^^^ salsa-0.26.2/tests/compile-fail/tracked_struct_not_update.rs000064400000000000000000000001771046102023000223370ustar 00000000000000#[salsa::tracked] struct MyInput<'db> { field: NotUpdate, } #[derive(Clone, Debug, Hash)] struct NotUpdate; fn main() {} salsa-0.26.2/tests/compile-fail/tracked_struct_not_update.stderr000064400000000000000000000030401046102023000232060ustar 00000000000000error[E0599]: the function or associated item `maybe_update` exists for struct `salsa::plumbing::UpdateDispatch`, but its trait bounds were not satisfied --> tests/compile-fail/tracked_struct_not_update.rs:1:1 | 1 | #[salsa::tracked] | ^^^^^^^^^^^^^^^^^ function or associated item cannot be called on `salsa::plumbing::UpdateDispatch` due to unsatisfied trait bounds ... 7 | struct NotUpdate; | ---------------- doesn't satisfy `NotUpdate: PartialEq` or `NotUpdate: Update` | ::: src/update.rs | | pub struct Dispatch(PhantomData); | ---------------------- doesn't satisfy `_: UpdateFallback` | note: if you're trying to build a new `salsa::plumbing::UpdateDispatch`, consider using `salsa::plumbing::UpdateDispatch::::new` which returns `salsa::plumbing::UpdateDispatch<_>` --> src/update.rs | | pub fn new() -> Self { | ^^^^^^^^^^^^^^^^^^^^ = note: the following trait bounds were not satisfied: `NotUpdate: Update` `NotUpdate: PartialEq` which is required by `salsa::plumbing::UpdateDispatch: salsa::plumbing::UpdateFallback` note: the trait `Update` must be implemented --> src/update.rs | | pub unsafe trait Update { | ^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `NotUpdate` with `#[derive(PartialEq)]` | 7 + #[derive(PartialEq)] 8 | struct NotUpdate; | salsa-0.26.2/tests/compile-fail/tracked_struct_unknown_attribute.rs000064400000000000000000000004131046102023000237500ustar 00000000000000#[salsa::tracked] struct UnknownAttributeTrackedStruct<'db> { #[tracked] tracked: bool, #[unknown_attr] field: bool, #[salsa::tracked] wrong_tracked: bool, /// TestDocComment /// TestDocComment field_with_doc: bool } fn main() {} salsa-0.26.2/tests/compile-fail/tracked_struct_unknown_attribute.stderr000064400000000000000000000007351046102023000246360ustar 00000000000000error: only a single lifetime parameter is accepted --> tests/compile-fail/tracked_struct_unknown_attribute.rs:1:1 | 1 | #[salsa::tracked] | ^^^^^^^^^^^^^^^^^ | = note: this error originates in the attribute macro `salsa::tracked` (in Nightly builds, run with -Z macro-backtrace for more info) error: cannot find attribute `unknown_attr` in this scope --> tests/compile-fail/tracked_struct_unknown_attribute.rs:5:7 | 5 | #[unknown_attr] | ^^^^^^^^^^^^ salsa-0.26.2/tests/compile-pass/redefine-result-input-struct-derive.rs000064400000000000000000000005441046102023000241550ustar 00000000000000// Ensure the `salsa::tracked` attribute macro doesn't conflict with local // redefinition of the `Result` type. // // See: https://github.com/salsa-rs/salsa/pull/1025 type Result = std::result::Result; #[salsa::tracked] fn example_query(_db: &dyn salsa::Database) -> Result<()> { Ok(()) } fn main() { println!("Hello, world!"); } salsa-0.26.2/tests/compile_fail.rs000064400000000000000000000003341046102023000151510ustar 00000000000000#![cfg(all(feature = "inventory", feature = "persistence"))] #[rustversion::all(stable, since(1.90))] #[test] fn compile_fail() { let t = trybuild::TestCases::new(); t.compile_fail("tests/compile-fail/*.rs"); } salsa-0.26.2/tests/compile_pass.rs000064400000000000000000000003241046102023000152030ustar 00000000000000#![cfg(all(feature = "inventory", feature = "persistence"))] #[rustversion::all(stable, since(1.90))] #[test] fn compile_pass() { let t = trybuild::TestCases::new(); t.pass("tests/compile-pass/*.rs"); } salsa-0.26.2/tests/cycle.rs000064400000000000000000001331241046102023000136310ustar 00000000000000#![cfg(feature = "inventory")] //! Test cases for fixpoint iteration cycle resolution. //! //! These test cases use a generic query setup that allows constructing arbitrary dependency //! graphs, and attempts to achieve good coverage of various cases. mod common; use common::{ExecuteValidateLoggerDatabase, LogDatabase}; use expect_test::expect; use salsa::{Database as Db, DatabaseImpl as DbImpl, Durability, Setter}; #[cfg(not(miri))] use test_log::test; #[derive(Clone, Copy, Debug, PartialEq, Eq, salsa::Update)] enum Value { N(u8), OutOfBounds, TooManyIterations, } impl Value { fn to_value(self) -> Option { if let Self::N(val) = self { Some(val) } else { None } } } /// A vector of inputs a query can evaluate to get an iterator of values to operate on. /// /// This allows creating arbitrary query graphs between the four queries below (`min_iterate`, /// `max_iterate`, `min_panic`, `max_panic`) for testing cycle behaviors. #[salsa::input] struct Inputs { #[returns(ref)] inputs: Vec, } impl Inputs { fn values(self, db: &dyn Db) -> impl Iterator + use<'_> { self.inputs(db).iter().map(|input| input.eval(db)) } } /// A single input, evaluating to a single [`Value`]. #[derive(Clone)] enum Input { /// a simple value Value(Value), /// a simple value, reported as an untracked read UntrackedRead(Value), /// minimum of the given inputs, with fixpoint iteration on cycles MinIterate(Inputs), /// maximum of the given inputs, with fixpoint iteration on cycles MaxIterate(Inputs), /// minimum of the given inputs, panicking on cycles MinPanic(Inputs), /// maximum of the given inputs, panicking on cycles MaxPanic(Inputs), /// value of the given input, plus one; propagates error values Successor(Box), /// successor, converts error values to zero SuccessorOrZero(Box), } impl Input { fn eval(&self, db: &dyn Db) -> Value { match *self { Self::Value(value) => value, Self::UntrackedRead(value) => { db.report_untracked_read(); value } Self::MinIterate(inputs) => min_iterate(db, inputs), Self::MaxIterate(inputs) => max_iterate(db, inputs), Self::MinPanic(inputs) => min_panic(db, inputs), Self::MaxPanic(inputs) => max_panic(db, inputs), Self::Successor(ref input) => match input.eval(db) { Value::N(num) => Value::N(num + 1), other => other, }, Self::SuccessorOrZero(ref input) => match input.eval(db) { Value::N(num) => Value::N(num + 1), _ => Value::N(0), }, } } #[track_caller] fn assert(&self, db: &dyn Db, expected: Value) { assert_eq!(self.eval(db), expected) } #[track_caller] fn assert_value(&self, db: &dyn Db, expected: u8) { self.assert(db, Value::N(expected)) } #[track_caller] fn assert_bounds(&self, db: &dyn Db) { self.assert(db, Value::OutOfBounds) } #[track_caller] fn assert_count(&self, db: &dyn Db) { self.assert(db, Value::TooManyIterations) } } const MIN_VALUE: u8 = 10; const MAX_VALUE: u8 = 245; const MAX_ITERATIONS: u32 = 3; /// Recover from a cycle by falling back to `Value::OutOfBounds` if the value is out of bounds, /// `Value::TooManyIterations` if we've iterated more than `MAX_ITERATIONS` times, or else /// returning the computed value to continue iterating. fn cycle_recover( _db: &dyn Db, cycle: &salsa::Cycle, last_provisional_value: &Value, value: Value, _inputs: Inputs, ) -> Value { if &value == last_provisional_value { value } else if value .to_value() .is_some_and(|val| val <= MIN_VALUE || val >= MAX_VALUE) { Value::OutOfBounds } else if cycle.iteration() > MAX_ITERATIONS { Value::TooManyIterations } else { value } } /// Fold an iterator of `Value` into a `Value`, given some binary operator to apply to two `u8`. /// `Value::TooManyIterations` and `Value::OutOfBounds` will always propagate, with /// `Value::TooManyIterations` taking precedence. fn fold_values(values: impl IntoIterator, op: F) -> Value where F: Fn(u8, u8) -> u8, { values .into_iter() .fold(None, |accum, elem| { let Some(accum) = accum else { return Some(elem); }; match (accum, elem) { (Value::TooManyIterations, _) | (_, Value::TooManyIterations) => { Some(Value::TooManyIterations) } (Value::OutOfBounds, _) | (_, Value::OutOfBounds) => Some(Value::OutOfBounds), (Value::N(val1), Value::N(val2)) => Some(Value::N(op(val1, val2))), } }) .expect("inputs should not be empty") } /// Query minimum value of inputs, with cycle recovery. #[salsa::tracked(cycle_fn=cycle_recover, cycle_initial=min_initial)] fn min_iterate(db: &dyn Db, inputs: Inputs) -> Value { fold_values(inputs.values(db), u8::min) } fn min_initial(_db: &dyn Db, _id: salsa::Id, _inputs: Inputs) -> Value { Value::N(255) } /// Query maximum value of inputs, with cycle recovery. #[salsa::tracked(cycle_fn=cycle_recover, cycle_initial=max_initial)] fn max_iterate(db: &dyn Db, inputs: Inputs) -> Value { fold_values(inputs.values(db), u8::max) } fn max_initial(_db: &dyn Db, _id: salsa::Id, _inputs: Inputs) -> Value { Value::N(0) } /// Query minimum value of inputs, without cycle recovery. #[salsa::tracked] fn min_panic(db: &dyn Db, inputs: Inputs) -> Value { fold_values(inputs.values(db), u8::min) } /// Query maximum value of inputs, without cycle recovery. #[salsa::tracked] fn max_panic(db: &dyn Db, inputs: Inputs) -> Value { fold_values(inputs.values(db), u8::max) } fn untracked(num: u8) -> Input { Input::UntrackedRead(Value::N(num)) } fn value(num: u8) -> Input { Input::Value(Value::N(num)) } // Diagram nomenclature for nodes: Each node is represented as a:xx(ii), where `a` is a sequential // identifier from `a`, `b`, `c`..., xx is one of the four query kinds: // - `Ni` for `min_iterate` // - `Xi` for `max_iterate` // - `Np` for `min_panic` // - `Xp` for `max_panic` //\ // and `ii` is the inputs for that query, represented as a comma-separated list, with each // component representing an input: // - `a`, `b`, `c`... where the input is another node, // - `uXX` for `UntrackedRead(XX)` // - `vXX` for `Value(XX)` // - `sY` for `Successor(Y)` // - `zY` for `SuccessorOrZero(Y)` // // We always enter from the top left node in the diagram. /// a:Np(a) -+ /// ^ | /// +--------+ /// /// Simple self-cycle, no iteration, should panic. #[test] #[should_panic(expected = "dependency graph cycle")] fn self_panic() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let a = Input::MinPanic(a_in); a_in.set_inputs(&mut db).to(vec![a.clone()]); a.eval(&db); } /// a:Np(u10, a) -+ /// ^ | /// +-------------+ /// /// Simple self-cycle with untracked read, no iteration, should panic. #[test] #[should_panic(expected = "dependency graph cycle")] fn self_untracked_panic() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let a = Input::MinPanic(a_in); a_in.set_inputs(&mut db).to(vec![untracked(10), a.clone()]); a.eval(&db); } /// a:Ni(a) -+ /// ^ | /// +--------+ /// /// Simple self-cycle, iteration converges on initial value. #[test] fn self_converge_initial_value() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); a_in.set_inputs(&mut db).to(vec![a.clone()]); a.assert_value(&db, 255); } /// a:Ni(b) --> b:Np(a) /// ^ | /// +-----------------+ /// /// Two-query cycle, one with iteration and one without. /// If we enter from the one with iteration, we converge on its initial value. #[test] fn two_mixed_converge_initial_value() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MinPanic(b_in); a_in.set_inputs(&mut db).to(vec![b]); b_in.set_inputs(&mut db).to(vec![a.clone()]); a.assert_value(&db, 255); } /// a:Np(b) --> b:Ni(a) /// ^ | /// +-----------------+ /// /// Two-query cycle, one with iteration and one without. /// If we enter from the one with no iteration, we panic. #[test] #[should_panic(expected = "dependency graph cycle")] fn two_mixed_panic() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let a = Input::MinPanic(b_in); let b = Input::MinIterate(a_in); a_in.set_inputs(&mut db).to(vec![b]); b_in.set_inputs(&mut db).to(vec![a.clone()]); a.eval(&db); } /// a:Ni(b) --> b:Xi(a) /// ^ | /// +-----------------+ /// /// Two-query cycle, both with iteration. /// We converge on the initial value of whichever we first enter from. #[test] fn two_iterate_converge_initial_value() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MaxIterate(b_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![a.clone()]); a.assert_value(&db, 255); b.assert_value(&db, 255); } /// a:Xi(b) --> b:Ni(a) /// ^ | /// +-----------------+ /// /// Two-query cycle, both with iteration. /// We converge on the initial value of whichever we enter from. /// (Same setup as above test, different query order.) #[test] fn two_iterate_converge_initial_value_2() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let a = Input::MaxIterate(a_in); let b = Input::MinIterate(b_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![a.clone()]); a.assert_value(&db, 0); b.assert_value(&db, 0); } /// a:Np(b) --> b:Ni(c) --> c:Xp(b) /// ^ | /// +-----------------+ /// /// Two-query cycle, enter indirectly at node with iteration, converge on its initial value. #[test] fn two_indirect_iterate_converge_initial_value() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinPanic(a_in); let b = Input::MinIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![c]); c_in.set_inputs(&mut db).to(vec![b]); a.assert_value(&db, 255); } /// a:Xp(b) --> b:Np(c) --> c:Xi(b) /// ^ | /// +-----------------+ /// /// Two-query cycle, enter indirectly at node without iteration, panic. #[test] #[should_panic(expected = "dependency graph cycle")] fn two_indirect_panic() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinPanic(a_in); let b = Input::MinPanic(b_in); let c = Input::MaxIterate(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![c]); c_in.set_inputs(&mut db).to(vec![b]); a.eval(&db); } /// a:Np(b) -> b:Ni(v200,c) -> c:Xp(b) /// ^ | /// +---------------------+ /// /// Two-query cycle, converges to non-initial value. #[test] fn two_converge() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinPanic(a_in); let b = Input::MinIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![value(200), c]); c_in.set_inputs(&mut db).to(vec![b]); a.assert_value(&db, 200); } /// a:Xp(b) -> b:Xi(v20,c) -> c:Xp(sb) /// ^ | /// +---------------------+ /// /// Two-query cycle, falls back due to >3 iterations. #[test] fn two_fallback_count() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MaxPanic(a_in); let b = Input::MaxIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![value(20), c]); c_in.set_inputs(&mut db) .to(vec![Input::Successor(Box::new(b))]); a.assert_count(&db); } /// a:Xp(b) -> b:Xi(v20,c) -> c:Xp(zb) /// ^ | /// +---------------------+ /// /// Two-query cycle, falls back but fallback does not converge. #[test] fn two_fallback_diverge() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MaxPanic(a_in); let b = Input::MaxIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![value(20), c.clone()]); c_in.set_inputs(&mut db) .to(vec![Input::SuccessorOrZero(Box::new(b))]); a.assert_count(&db); } /// a:Xp(b) -> b:Xi(v244,c) -> c:Xp(sb) /// ^ | /// +---------------------+ /// /// Two-query cycle, falls back due to value reaching >MAX_VALUE (we start at 244 and each /// iteration increments until we reach >245). #[test] fn two_fallback_value() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MaxPanic(a_in); let b = Input::MaxIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![value(244), c]); c_in.set_inputs(&mut db) .to(vec![Input::Successor(Box::new(b))]); a.assert_bounds(&db); } /// a:Ni(b) -> b:Np(a, c) -> c:Np(v25, a) /// ^ | | /// +----------+------------------------+ /// /// Three-query cycle, (b) and (c) both depend on (a). We converge on 25. #[test] fn three_fork_converge() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MinPanic(b_in); let c = Input::MinPanic(c_in); a_in.set_inputs(&mut db).to(vec![b]); b_in.set_inputs(&mut db).to(vec![a.clone(), c]); c_in.set_inputs(&mut db).to(vec![value(25), a.clone()]); a.assert_value(&db, 25); } /// a:Ni(b) -> b:Ni(a, c) -> c:Np(v25, b) /// ^ | ^ | /// +----------+ +----------+ /// /// Layered cycles. We converge on 25. #[test] fn layered_converge() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MinIterate(b_in); let c = Input::MinPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![a.clone(), c]); c_in.set_inputs(&mut db).to(vec![value(25), b]); a.assert_value(&db, 25); } /// a:Xi(b) -> b:Xi(a, c) -> c:Xp(v25, sb) /// ^ | ^ | /// +----------+ +----------+ /// /// Layered cycles. We hit max iterations and fall back. #[test] fn layered_fallback_count() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MaxIterate(a_in); let b = Input::MaxIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![a.clone(), c]); c_in.set_inputs(&mut db) .to(vec![value(25), Input::Successor(Box::new(b))]); a.assert_count(&db); } /// a:Xi(b) -> b:Xi(a, c) -> c:Xp(v243, sb) /// ^ | ^ | /// +----------+ +----------+ /// /// Layered cycles. We hit max value and fall back. #[test] fn layered_fallback_value() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MaxIterate(a_in); let b = Input::MaxIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![a.clone(), c]); c_in.set_inputs(&mut db) .to(vec![value(243), Input::Successor(Box::new(b))]); a.assert_bounds(&db); } /// a:Ni(b) -> b:Ni(c) -> c:Np(v25, a, b) /// ^ ^ | /// +----------+------------------------+ /// /// Nested cycles. We converge on 25. #[test] fn nested_converge() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MinIterate(b_in); let c = Input::MinPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![c]); c_in.set_inputs(&mut db).to(vec![value(25), a.clone(), b]); a.assert_value(&db, 25); } /// a:Ni(b) -> b:Ni(c) -> c:Np(v25, b, a) /// ^ ^ | /// +----------+------------------------+ /// /// Nested cycles, inner first. We converge on 25. #[test] fn nested_inner_first_converge() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MinIterate(b_in); let c = Input::MinPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![c]); c_in.set_inputs(&mut db).to(vec![value(25), b, a.clone()]); a.assert_value(&db, 25); } /// a:Xi(b) -> b:Xi(c) -> c:Xp(v25, a, sb) /// ^ ^ | /// +----------+-------------------------+ /// /// Nested cycles. We hit max iterations and fall back. #[test] fn nested_fallback_count() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MaxIterate(a_in); let b = Input::MaxIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![c]); c_in.set_inputs(&mut db) .to(vec![value(25), a.clone(), Input::Successor(Box::new(b))]); a.assert_count(&db); } /// a:Xi(b) -> b:Xi(c) -> c:Xp(v25, b, sa) /// ^ ^ | /// +----------+-------------------------+ /// /// Nested cycles, inner first. We hit max iterations and fall back. #[test] fn nested_inner_first_fallback_count() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MaxIterate(a_in); let b = Input::MaxIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![c]); c_in.set_inputs(&mut db) .to(vec![value(25), b, Input::Successor(Box::new(a.clone()))]); a.assert_count(&db); } /// a:Xi(b) -> b:Xi(c) -> c:Xp(v243, a, sb) /// ^ ^ | /// +----------+--------------------------+ /// /// Nested cycles. We hit max value and fall back. #[test] fn nested_fallback_value() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MaxIterate(a_in); let b = Input::MaxIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![c.clone()]); c_in.set_inputs(&mut db).to(vec![ value(243), a.clone(), Input::Successor(Box::new(b.clone())), ]); a.assert_bounds(&db); b.assert_bounds(&db); c.assert_bounds(&db); } /// a:Xi(b) -> b:Xi(c) -> c:Xp(v243, b, sa) /// ^ ^ | /// +----------+--------------------------+ /// /// Nested cycles, inner first. We hit max value and fall back. #[test] fn nested_inner_first_fallback_value() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MaxIterate(a_in); let b = Input::MaxIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![c]); c_in.set_inputs(&mut db) .to(vec![value(243), b, Input::Successor(Box::new(a.clone()))]); a.assert_bounds(&db); } /// a:Ni(b) -> b:Ni(c, a) -> c:Np(v25, a, b) /// ^ ^ | | /// +----------+--------|------------------+ /// | | /// +-------------------+ /// /// Nested cycles, double head. We converge on 25. #[test] fn nested_double_converge() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MinIterate(b_in); let c = Input::MinPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![c, a.clone()]); c_in.set_inputs(&mut db).to(vec![value(25), a.clone(), b]); a.assert_value(&db, 25); } // Multiple-revision cycles /// a:Ni(b) --> b:Np(a) /// ^ | /// +-----------------+ /// /// a:Ni(b) --> b:Np(v30) /// /// Cycle becomes not-a-cycle in next revision. #[test] fn cycle_becomes_non_cycle() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MinPanic(b_in); a_in.set_inputs(&mut db).to(vec![b]); b_in.set_inputs(&mut db).to(vec![a.clone()]); a.assert_value(&db, 255); b_in.set_inputs(&mut db).to(vec![value(30)]); a.assert_value(&db, 30); } /// a:Ni(b) --> b:Np(v30) /// /// a:Ni(b) --> b:Np(a) /// ^ | /// +-----------------+ /// /// Non-cycle becomes a cycle in next revision. #[test] fn non_cycle_becomes_cycle() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MinPanic(b_in); a_in.set_inputs(&mut db).to(vec![b]); b_in.set_inputs(&mut db).to(vec![value(30)]); a.assert_value(&db, 30); b_in.set_inputs(&mut db).to(vec![a.clone()]); a.assert_value(&db, 255); } /// a:Xi(b) -> b:Xi(c, a) -> c:Xp(v25, a, sb) /// ^ ^ | | /// +----------+--------|-------------------+ /// | | /// +-------------------+ /// /// Nested cycles, double head. We hit max iterations and fall back, then max value on the next /// revision, then converge on the next. #[test] fn nested_double_multiple_revisions() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MaxIterate(a_in); let b = Input::MaxIterate(b_in); let c = Input::MaxPanic(c_in); a_in.set_inputs(&mut db).to(vec![b.clone()]); b_in.set_inputs(&mut db).to(vec![c, a.clone()]); c_in.set_inputs(&mut db).to(vec![ value(25), a.clone(), Input::Successor(Box::new(b.clone())), ]); a.assert_count(&db); tracing::info!("New revision, expect max value"); // next revision, we hit max value instead c_in.set_inputs(&mut db).to(vec![ value(243), a.clone(), Input::Successor(Box::new(b.clone())), ]); a.assert_bounds(&db); // and next revision, we converge tracing::info!("New revision, expect converge"); c_in.set_inputs(&mut db) .to(vec![value(240), a.clone(), b.clone()]); a.assert_value(&db, 240); tracing::info!("New revision, expect converge again"); // one more revision, without relevant changes a_in.set_inputs(&mut db).to(vec![b]); a.assert_value(&db, 240); } /// a:Ni(b) -> b:Ni(c) -> c:Ni(a) /// ^ | /// +---------------------------+ /// /// In a cycle with some LOW durability and some HIGH durability inputs, changing a LOW durability /// input still re-executes the full cycle in the next revision. #[test] fn cycle_durability() { let mut db = DbImpl::new(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MinIterate(b_in); let c = Input::MinIterate(c_in); a_in.set_inputs(&mut db) .with_durability(Durability::LOW) .to(vec![b.clone()]); b_in.set_inputs(&mut db) .with_durability(Durability::HIGH) .to(vec![c]); c_in.set_inputs(&mut db) .with_durability(Durability::HIGH) .to(vec![a.clone()]); a.assert_value(&db, 255); // next revision, we converge instead a_in.set_inputs(&mut db) .with_durability(Durability::LOW) .to(vec![value(45), b]); a.assert_value(&db, 45); } /// a:Np(v59, b) -> b:Ni(v60, c) -> c:Np(b) /// ^ | /// +---------------------+ /// /// If nothing in a cycle changed in the new revision, no part of the cycle should re-execute. #[test] fn cycle_unchanged() { let mut db = ExecuteValidateLoggerDatabase::default(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinPanic(a_in); let b = Input::MinIterate(b_in); let c = Input::MinPanic(c_in); a_in.set_inputs(&mut db).to(vec![value(59), b.clone()]); b_in.set_inputs(&mut db).to(vec![value(60), c]); c_in.set_inputs(&mut db).to(vec![b.clone()]); a.assert_value(&db, 59); b.assert_value(&db, 60); db.assert_logs_len(6); // next revision, we change only A, which is not part of the cycle and the cycle does not // depend on. a_in.set_inputs(&mut db).to(vec![value(45), b.clone()]); b.assert_value(&db, 60); db.assert_logs(expect![[r#" [ "salsa_event(DidValidateMemoizedValue { database_key: min_iterate(Id(1)) })", ]"#]]); a.assert_value(&db, 45); } /// a:Np(v59, b) -> b:Ni(v60, c) -> c:Np(d) -> d:Ni(v61, b, e) -> e:Np(d) /// ^ | ^ | /// +--------------------------+ +--------------+ /// /// If nothing in a nested cycle changed in the new revision, no part of the cycle should /// re-execute. #[test_log::test] fn cycle_unchanged_nested() { let mut db = ExecuteValidateLoggerDatabase::default(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let d_in = Inputs::new(&db, vec![]); let e_in = Inputs::new(&db, vec![]); let a = Input::MinPanic(a_in); let b = Input::MinIterate(b_in); let c = Input::MinPanic(c_in); let d = Input::MinIterate(d_in); let e = Input::MinPanic(e_in); a_in.set_inputs(&mut db).to(vec![value(59), b.clone()]); b_in.set_inputs(&mut db).to(vec![value(60), c.clone()]); c_in.set_inputs(&mut db).to(vec![d.clone()]); d_in.set_inputs(&mut db) .to(vec![value(61), b.clone(), e.clone()]); e_in.set_inputs(&mut db).to(vec![d.clone()]); a.assert_value(&db, 59); b.assert_value(&db, 60); db.assert_logs_len(14); // next revision, we change only A, which is not part of the cycle and the cycle does not // depend on. a_in.set_inputs(&mut db).to(vec![value(45), b.clone()]); b.assert_value(&db, 60); db.assert_logs(expect![[r#" [ "salsa_event(DidValidateMemoizedValue { database_key: min_iterate(Id(1)) })", ]"#]]); a.assert_value(&db, 45); } /// +--------------------------------+ /// | v /// a:Np(v59, b) -> b:Ni(v60, c) -> c:Np(d, e) -> d:Ni(v61, b, e) -> e:Ni(d) /// ^ | ^ | /// +-----------------------------+ +--------------+ /// /// If nothing in a nested cycle changed in the new revision, no part of the cycle should /// re-execute. #[test_log::test] fn cycle_unchanged_nested_intertwined() { // We run this test twice in order to catch some subtly different cases; see below. for i in 0..1 { let mut db = ExecuteValidateLoggerDatabase::default(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let d_in = Inputs::new(&db, vec![]); let e_in = Inputs::new(&db, vec![]); let a = Input::MinPanic(a_in); let b = Input::MinIterate(b_in); let c = Input::MinPanic(c_in); let d = Input::MinIterate(d_in); let e = Input::MinIterate(e_in); a_in.set_inputs(&mut db).to(vec![value(59), b.clone()]); b_in.set_inputs(&mut db).to(vec![value(60), c.clone()]); c_in.set_inputs(&mut db).to(vec![d.clone(), e.clone()]); d_in.set_inputs(&mut db) .to(vec![value(61), b.clone(), e.clone()]); e_in.set_inputs(&mut db).to(vec![d.clone()]); a.assert_value(&db, 59); b.assert_value(&db, 60); // First time we run this test, don't fetch c/d/e here; this means they won't get marked // `verified_final` in R6 (this revision), which will leave us in the next revision (R7) // with a chain of could-be-provisional memos from the previous revision which should be // final but were never confirmed as such; this triggers the case in `deep_verify_memo` // where we need to double-check `validate_provisional` after traversing dependencies. // // Second time we run this test, fetch everything in R6, to check the behavior of // `maybe_changed_after` with all validated-final memos. if i == 1 { c.assert_value(&db, 60); d.assert_value(&db, 60); e.assert_value(&db, 60); } db.assert_logs_len(14 + i); // next revision, we change only A, which is not part of the cycle and the cycle does not // depend on. a_in.set_inputs(&mut db).to(vec![value(45), b.clone()]); b.assert_value(&db, 60); db.assert_logs(expect![[r#" [ "salsa_event(DidValidateMemoizedValue { database_key: min_iterate(Id(1)) })", ]"#]]); a.assert_value(&db, 45); } } /// Test that cycle heads from one dependency don't interfere with sibling verification. /// /// a:Ni(b, c, d) -> b:Ni(a) [cycle with a, unchanged] /// \-> c:Np(v100) [no cycle, unchanged] /// \-> d:Np(v200->v201) [no cycle, changes] /// /// When verifying a in a new revision: /// 1. b goes through deep verification (detects b->a cycle, adds cycle heads, returns unchanged) /// 2. c gets verified (should not be affected by b's cycle heads with the fix) /// 3. d returns changed, causing a to re-execute /// /// Without the fix: cycle heads from b's verification remain in shared context and interfere with c /// With the fix: c gets fresh cycle head context and verifies cleanly #[test] fn cycle_sibling_interference() { let mut db = ExecuteValidateLoggerDatabase::default(); let a_in = Inputs::new(&db, vec![]); // a = min_iterate(Id(0)) let b_in = Inputs::new(&db, vec![]); // b = min_iterate(Id(1)) let c_in = Inputs::new(&db, vec![]); // c = min_panic(Id(2)) let d_in = Inputs::new(&db, vec![]); // d = min_panic(Id(3)) let a = Input::MinIterate(a_in); let b = Input::MinIterate(b_in); let c = Input::MinPanic(c_in); let d = Input::MinPanic(d_in); a_in.set_inputs(&mut db) .to(vec![b.clone(), c.clone(), d.clone()]); // a depends on b, c, d (in that order) b_in.set_inputs(&mut db).to(vec![a.clone()]); // b depends on a (forming a->b->a cycle) c_in.set_inputs(&mut db).to(vec![value(100)]); // c is independent, no cycles d_in.set_inputs(&mut db).to(vec![value(200)]); // d is independent, no cycles // First execution - this will establish the cycle and memos // The cycle: a depends on b, b depends on a // During fixpoint iteration, initial values are 255 // a computes min(255, 100, 200) = 100 // b computes min(100) = 100 // Next iteration: a computes min(100, 100, 200) = 100 (converged) a.assert_value(&db, 100); b.assert_value(&db, 100); c.assert_value(&db, 100); d.assert_value(&db, 200); // Clear logs to prepare for the next revision db.clear_logs(); // Change d's input to trigger a new revision // This forces verification of all dependencies in the new revision d_in.set_inputs(&mut db).to(vec![value(201)]); // Verify a - this should trigger: // 1. b: deep verification (cycle detected, cycle heads added to context, but b unchanged) // 2. c: verification (should be clean without cycle head interference) // 3. d: changed, causing a to re-execute a.assert_value(&db, 100); // min(255, 100, 201) = 100 // Query mapping: a=min_iterate(Id(0)), b=min_iterate(Id(1)), c=min_panic(Id(2)), d=min_panic(Id(3)) // - c gets validated cleanly during verification of `a`. The fact that `a` and `b` form a cycle shouldn't prevent that // - a re-executes (due to d changing) // - b re-executes (as part of a-b cycle) // - d re-executes (input changed) // - cycle iteration continues // - b re-executes again during cycle iteration db.assert_logs(expect![[r#" [ "salsa_event(DidValidateMemoizedValue { database_key: min_panic(Id(2)) })", "salsa_event(WillExecute { database_key: min_panic(Id(3)) })", "salsa_event(WillExecute { database_key: min_iterate(Id(0)) })", "salsa_event(WillExecute { database_key: min_iterate(Id(1)) })", "salsa_event(WillIterateCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })", "salsa_event(WillExecute { database_key: min_iterate(Id(1)) })", "salsa_event(DidFinalizeCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })", ]"#]]); } /// Provisional query results in a cycle should still be cached within a single iteration. /// /// a:Ni(v59, b) -> b:Np(v60, c, c, c) -> c:Np(a) /// ^ | /// +------------------------------------------+ #[test] fn repeat_provisional_query() { let mut db = ExecuteValidateLoggerDatabase::default(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MinPanic(b_in); let c = Input::MinPanic(c_in); a_in.set_inputs(&mut db).to(vec![value(59), b.clone()]); b_in.set_inputs(&mut db) .to(vec![value(60), c.clone(), c.clone(), c]); c_in.set_inputs(&mut db).to(vec![a.clone()]); a.assert_value(&db, 59); db.assert_logs(expect![[r#" [ "salsa_event(WillExecute { database_key: min_iterate(Id(0)) })", "salsa_event(WillExecute { database_key: min_panic(Id(1)) })", "salsa_event(WillExecute { database_key: min_panic(Id(2)) })", "salsa_event(WillIterateCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })", "salsa_event(WillExecute { database_key: min_panic(Id(1)) })", "salsa_event(WillExecute { database_key: min_panic(Id(2)) })", "salsa_event(DidFinalizeCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })", ]"#]]); } #[test] fn repeat_provisional_query_incremental() { let mut db = ExecuteValidateLoggerDatabase::default(); let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in); let b = Input::MinPanic(b_in); let c = Input::MinPanic(c_in); a_in.set_inputs(&mut db).to(vec![value(59), b.clone()]); b_in.set_inputs(&mut db) .to(vec![value(60), c.clone(), c.clone(), c]); c_in.set_inputs(&mut db).to(vec![a.clone()]); a.assert_value(&db, 59); db.clear_logs(); c_in.set_inputs(&mut db).to(vec![a.clone()]); a.assert_value(&db, 59); // `min_panic(Id(2)) should only run twice: // * Once before iterating // * Once as part of iterating // // If it runs more than once before iterating, than this suggests that // `validate_same_iteration` incorrectly returns `false`. db.assert_logs(expect![[r#" [ "salsa_event(WillExecute { database_key: min_iterate(Id(0)) })", "salsa_event(WillExecute { database_key: min_panic(Id(1)) })", "salsa_event(WillExecute { database_key: min_panic(Id(2)) })", "salsa_event(WillIterateCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })", "salsa_event(WillExecute { database_key: min_panic(Id(1)) })", "salsa_event(WillExecute { database_key: min_panic(Id(2)) })", "salsa_event(DidFinalizeCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })", ]"#]]); } /// Tests a situation where a query participating in a cycle gets called many times (think thousands of times). /// /// We want to avoid calling `deep_verify_memo` for that query over and over again. /// This isn't an issue for regular queries because a non-cyclic query is guaranteed to be verified /// after `maybe_changed_after` because: /// * It can be shallow verified /// * `deep_verify_memo` returns `unchanged` and it updates the `verified_at` revision. /// * `deep_verify_memo` returns `changed` and Salsa re-executes the query. The query is verified once `execute` completes. /// /// The same guarantee doesn't exist for queries participating in cycles because: /// /// * Salsa update `verified_at` because it depends on the cycle head if the query didn't change. /// * Salsa doesn't run `execute` because some inputs may not have been verified yet (which can lead to all sort of pancis). #[test] fn repeat_query_participating_in_cycle() { #[salsa::input] struct Input { value: u32, } #[salsa::interned] struct Interned { value: u32, } #[salsa::tracked(cycle_initial=initial)] fn head(db: &dyn Db, input: Input) -> u32 { let a = query_a(db, input); a.min(2) } fn initial(_db: &dyn Db, _id: salsa::Id, _input: Input) -> u32 { 0 } #[salsa::tracked] fn query_a(db: &dyn Db, input: Input) -> u32 { let _ = query_b(db, input); query_hot(db, input) } #[salsa::tracked] fn query_b(db: &dyn Db, input: Input) -> u32 { let _ = query_c(db, input); query_hot(db, input) } #[salsa::tracked] fn query_c(db: &dyn Db, input: Input) -> u32 { let _ = query_d(db, input); query_hot(db, input) } #[salsa::tracked] fn query_d(db: &dyn Db, input: Input) -> u32 { query_hot(db, input) } #[salsa::tracked] fn query_hot(db: &dyn Db, input: Input) -> u32 { let value = head(db, input); let _ = Interned::new(db, 2); let _ = input.value(db); value + 1 } let mut db = ExecuteValidateLoggerDatabase::default(); let input = Input::new(&db, 1); assert_eq!(head(&db, input), 2); db.clear_logs(); input.set_value(&mut db).to(10); assert_eq!(head(&db, input), 2); // The interned value should only be validate once. We otherwise have a // run-away situation where `deep_verify_memo` of `query_hot` is called over and over again. // * First: when checking if `head` has changed // * Second: when checking if `query_a` has changed // * Third: when checking if `query_b` has changed // * ... // Ultimately, this can easily be more expensive than running the cycle head again. db.assert_logs(expect![[r#" [ "salsa_event(DidValidateInternedValue { key: Interned(Id(400)), revision: R2 })", "salsa_event(WillExecute { database_key: head(Id(0)) })", "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillExecute { database_key: query_b(Id(0)) })", "salsa_event(WillExecute { database_key: query_c(Id(0)) })", "salsa_event(WillExecute { database_key: query_d(Id(0)) })", "salsa_event(WillExecute { database_key: query_hot(Id(0)) })", "salsa_event(WillIterateCycle { database_key: head(Id(0)), iteration_count: IterationCount(1) })", "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillExecute { database_key: query_b(Id(0)) })", "salsa_event(WillExecute { database_key: query_c(Id(0)) })", "salsa_event(WillExecute { database_key: query_d(Id(0)) })", "salsa_event(WillExecute { database_key: query_hot(Id(0)) })", "salsa_event(WillIterateCycle { database_key: head(Id(0)), iteration_count: IterationCount(2) })", "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillExecute { database_key: query_b(Id(0)) })", "salsa_event(WillExecute { database_key: query_c(Id(0)) })", "salsa_event(WillExecute { database_key: query_d(Id(0)) })", "salsa_event(WillExecute { database_key: query_hot(Id(0)) })", "salsa_event(DidFinalizeCycle { database_key: head(Id(0)), iteration_count: IterationCount(2) })", ]"#]]); } /// Tests a similar scenario as `repeat_query_participating_in_cycle` with the main difference /// that `query_hot` is called before calling the next `query_xxx`. #[test] fn repeat_query_participating_in_cycle2() { #[salsa::input] struct Input { value: u32, } #[salsa::interned] struct Interned { value: u32, } #[salsa::tracked(cycle_initial=initial)] fn head(db: &dyn Db, input: Input) -> u32 { let a = query_a(db, input); a.min(2) } fn initial(_db: &dyn Db, _id: salsa::Id, _input: Input) -> u32 { 0 } #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn Db, input: Input) -> u32 { let _ = query_hot(db, input); query_b(db, input) } #[salsa::tracked] fn query_b(db: &dyn Db, input: Input) -> u32 { let _ = query_hot(db, input); query_c(db, input) } #[salsa::tracked] fn query_c(db: &dyn Db, input: Input) -> u32 { let _ = query_hot(db, input); query_d(db, input) } #[salsa::tracked] fn query_d(db: &dyn Db, input: Input) -> u32 { let _ = query_hot(db, input); let value = head(db, input); let _ = input.value(db); value + 1 } #[salsa::tracked] fn query_hot(db: &dyn Db, input: Input) -> u32 { let _ = Interned::new(db, 2); let _ = head(db, input); 1 } let mut db = ExecuteValidateLoggerDatabase::default(); let input = Input::new(&db, 1); assert_eq!(head(&db, input), 2); db.clear_logs(); input.set_value(&mut db).to(10); assert_eq!(head(&db, input), 2); // `DidValidateInternedValue { key: Interned(Id(400)), revision: R2 }` should only be logged // once per `maybe_changed_after` root-call (e.g. validating `head` shouldn't validate `query_hot` multiple times). // // This is important to avoid a run-away situation where a query is called many times within a cycle and // Salsa would end up recusively validating the hot query over and over again. db.assert_logs(expect![[r#" [ "salsa_event(DidValidateInternedValue { key: Interned(Id(400)), revision: R2 })", "salsa_event(WillExecute { database_key: head(Id(0)) })", "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillExecute { database_key: query_hot(Id(0)) })", "salsa_event(WillExecute { database_key: query_b(Id(0)) })", "salsa_event(WillExecute { database_key: query_c(Id(0)) })", "salsa_event(WillExecute { database_key: query_d(Id(0)) })", "salsa_event(WillIterateCycle { database_key: head(Id(0)), iteration_count: IterationCount(1) })", "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillExecute { database_key: query_hot(Id(0)) })", "salsa_event(WillExecute { database_key: query_b(Id(0)) })", "salsa_event(WillExecute { database_key: query_c(Id(0)) })", "salsa_event(WillExecute { database_key: query_d(Id(0)) })", "salsa_event(WillIterateCycle { database_key: head(Id(0)), iteration_count: IterationCount(2) })", "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillExecute { database_key: query_hot(Id(0)) })", "salsa_event(WillExecute { database_key: query_b(Id(0)) })", "salsa_event(WillExecute { database_key: query_c(Id(0)) })", "salsa_event(WillExecute { database_key: query_d(Id(0)) })", "salsa_event(DidFinalizeCycle { database_key: head(Id(0)), iteration_count: IterationCount(2) })", ]"#]]); } salsa-0.26.2/tests/cycle_accumulate.rs000064400000000000000000000245301046102023000160340ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] //! Some historical tests related to accumulated values and fixpoint iteration. //! //! All these tests are currently expected to panic because accumulated values //! require preserving the query's dependency tree in full, but fixpoint iteration //! now flattens the cycle head dependencies, breaking accumulated values. //! //! We keep this tests around as they're a good starting point if we decide //! to support accumulated values in fixpoint queries. //! //! One test that should be added which is a case that was broken even before //! we migrated fixpoint iteration to flatten the cycle head dependencies is //! a case roughly like this: //! //! * `a` (outer most cycle head, calls `c` in every iteration) //! * `b` (inner cycle), calls `c` only in the first iteration //! * `c` calls `a` and `b` and creates an accumulated value in each iteration. //! //! The query `b` finalizes after the first iteration. The accumulated values of //! `b` should only include the accumulated values of `c` from the **first** iteration. //! //! Using today's dependency traversal, this would require expressing the iteration count //! in the dependency tree, so that the accumulator knows from which iteration //! to aggregate the accumulated values from. use std::collections::HashSet; mod common; use common::{LogDatabase, LoggerDatabase}; use expect_test::expect; use salsa::{Accumulator, Setter}; use test_log::test; #[salsa::input(debug)] struct File { name: String, dependencies: Vec, issues: Vec, } #[salsa::accumulator] #[derive(Debug)] struct Diagnostic(#[allow(dead_code)] String); #[salsa::tracked(cycle_fn = cycle_fn, cycle_initial = cycle_initial)] fn check_file(db: &dyn LogDatabase, file: File) -> Vec { db.push_log(format!( "check_file(name = {}, issues = {:?})", file.name(db), file.issues(db) )); let mut collected_issues = HashSet::::from_iter(file.issues(db).iter().copied()); for dep in file.dependencies(db) { let issues = check_file(db, dep); collected_issues.extend(issues); } let mut sorted_issues = collected_issues.iter().copied().collect::>(); sorted_issues.sort(); for issue in &sorted_issues { Diagnostic(format!("file {}: issue {}", file.name(db), issue)).accumulate(db); } sorted_issues } fn cycle_initial(_db: &dyn LogDatabase, _id: salsa::Id, _file: File) -> Vec { vec![] } fn cycle_fn( _db: &dyn LogDatabase, _cycle: &salsa::Cycle, _last_provisional_value: &[u32], value: Vec, _file: File, ) -> Vec { value } #[test] fn accumulate_once() { let db = LoggerDatabase::default(); let file = File::new(&db, "fn".to_string(), vec![], vec![1]); let diagnostics = check_file::accumulated::(&db, file); db.assert_logs(expect![[r#" [ "check_file(name = fn, issues = [1])", ]"#]]); expect![[r#" [ Diagnostic( "file fn: issue 1", ), ]"#]] .assert_eq(&format!("{diagnostics:#?}")); } #[test] fn accumulate_with_dep() { let db = LoggerDatabase::default(); let file_a = File::new(&db, "file_a".to_string(), vec![], vec![1]); let file_b = File::new(&db, "file_b".to_string(), vec![file_a], vec![2]); let diagnostics = check_file::accumulated::(&db, file_b); db.assert_logs(expect![[r#" [ "check_file(name = file_b, issues = [2])", "check_file(name = file_a, issues = [1])", ]"#]]); expect![[r#" [ Diagnostic( "file file_b: issue 1", ), Diagnostic( "file file_b: issue 2", ), Diagnostic( "file file_a: issue 1", ), ]"#]] .assert_eq(&format!("{diagnostics:#?}")); } #[test] #[should_panic(expected = "doesn't support accumulated values")] fn accumulate_with_cycle() { let mut db = LoggerDatabase::default(); let file_a = File::new(&db, "file_a".to_string(), vec![], vec![1]); let file_b = File::new(&db, "file_b".to_string(), vec![file_a], vec![2]); file_a.set_dependencies(&mut db).to(vec![file_b]); let diagnostics = check_file::accumulated::(&db, file_b); db.assert_logs(expect![[r#" [ "check_file(name = file_b, issues = [2])", "check_file(name = file_a, issues = [1])", "check_file(name = file_b, issues = [2])", "check_file(name = file_a, issues = [1])", ]"#]]); expect![[r#" [ Diagnostic( "file file_b: issue 1", ), Diagnostic( "file file_b: issue 2", ), Diagnostic( "file file_a: issue 1", ), Diagnostic( "file file_a: issue 2", ), ]"#]] .assert_eq(&format!("{diagnostics:#?}")); } #[test] #[should_panic(expected = "doesn't support accumulated values")] fn accumulate_with_cycle_second_revision() { let mut db = LoggerDatabase::default(); let file_a = File::new(&db, "file_a".to_string(), vec![], vec![1]); let file_b = File::new(&db, "file_b".to_string(), vec![file_a], vec![2]); file_a.set_dependencies(&mut db).to(vec![file_b]); let diagnostics = check_file::accumulated::(&db, file_b); db.assert_logs(expect![[r#" [ "check_file(name = file_b, issues = [2])", "check_file(name = file_a, issues = [1])", "check_file(name = file_b, issues = [2])", "check_file(name = file_a, issues = [1])", ]"#]]); expect![[r#" [ Diagnostic( "file file_b: issue 1", ), Diagnostic( "file file_b: issue 2", ), Diagnostic( "file file_a: issue 1", ), Diagnostic( "file file_a: issue 2", ), ]"#]] .assert_eq(&format!("{diagnostics:#?}")); file_b.set_issues(&mut db).to(vec![2, 3]); let diagnostics = check_file::accumulated::(&db, file_a); db.assert_logs(expect![[r#" [ "check_file(name = file_a, issues = [1])", "check_file(name = file_b, issues = [2, 3])", "check_file(name = file_a, issues = [1])", "check_file(name = file_b, issues = [2, 3])", ]"#]]); expect![[r#" [ Diagnostic( "file file_a: issue 1", ), Diagnostic( "file file_a: issue 2", ), Diagnostic( "file file_a: issue 3", ), Diagnostic( "file file_b: issue 1", ), Diagnostic( "file file_b: issue 2", ), Diagnostic( "file file_b: issue 3", ), ]"#]] .assert_eq(&format!("{diagnostics:#?}")); } #[test] #[should_panic(expected = "doesn't support accumulated values")] fn accumulate_add_cycle() { let mut db = LoggerDatabase::default(); let file_a = File::new(&db, "file_a".to_string(), vec![], vec![1]); let file_b = File::new(&db, "file_b".to_string(), vec![file_a], vec![2]); let diagnostics = check_file::accumulated::(&db, file_b); db.assert_logs(expect![[r#" [ "check_file(name = file_b, issues = [2])", "check_file(name = file_a, issues = [1])", ]"#]]); expect![[r#" [ Diagnostic( "file file_b: issue 1", ), Diagnostic( "file file_b: issue 2", ), Diagnostic( "file file_a: issue 1", ), ]"#]] .assert_eq(&format!("{diagnostics:#?}")); file_a.set_dependencies(&mut db).to(vec![file_b]); let diagnostics = check_file::accumulated::(&db, file_a); db.assert_logs(expect![[r#" [ "check_file(name = file_a, issues = [1])", "check_file(name = file_b, issues = [2])", "check_file(name = file_a, issues = [1])", "check_file(name = file_b, issues = [2])", ]"#]]); expect![[r#" [ Diagnostic( "file file_a: issue 1", ), Diagnostic( "file file_a: issue 2", ), Diagnostic( "file file_b: issue 1", ), Diagnostic( "file file_b: issue 2", ), ]"#]] .assert_eq(&format!("{diagnostics:#?}")); } #[test] #[should_panic(expected = "doesn't support accumulated values")] fn accumulate_remove_cycle() { let mut db = LoggerDatabase::default(); let file_a = File::new(&db, "file_a".to_string(), vec![], vec![1]); let file_b = File::new(&db, "file_b".to_string(), vec![file_a], vec![2]); file_a.set_dependencies(&mut db).to(vec![file_b]); let diagnostics = check_file::accumulated::(&db, file_b); db.assert_logs(expect![[r#" [ "check_file(name = file_b, issues = [2])", "check_file(name = file_a, issues = [1])", "check_file(name = file_b, issues = [2])", "check_file(name = file_a, issues = [1])", ]"#]]); expect![[r#" [ Diagnostic( "file file_b: issue 1", ), Diagnostic( "file file_b: issue 2", ), Diagnostic( "file file_a: issue 1", ), Diagnostic( "file file_a: issue 2", ), ]"#]] .assert_eq(&format!("{diagnostics:#?}")); file_a.set_dependencies(&mut db).to(vec![]); let diagnostics = check_file::accumulated::(&db, file_b); db.assert_logs(expect![[r#" [ "check_file(name = file_b, issues = [2])", "check_file(name = file_a, issues = [1])", ]"#]]); expect![[r#" [ Diagnostic( "file file_b: issue 1", ), Diagnostic( "file file_b: issue 2", ), Diagnostic( "file file_a: issue 1", ), ]"#]] .assert_eq(&format!("{diagnostics:#?}")); } salsa-0.26.2/tests/cycle_dependency_order_different_entry_queries.rs000064400000000000000000000043211046102023000242220ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] use crate::common::{ExecuteValidateLoggerDatabase, LogDatabase}; use expect_test::expect; use salsa::{Database, Durability}; mod common; #[salsa::tracked(cycle_initial=a_cycle_initial)] fn query_a(db: &dyn Database) { let b = query_b(db); query_d(db, b); } fn a_cycle_initial(_db: &dyn Database, _id: salsa::Id) {} #[salsa::interned] struct Interned { value: u32, } #[salsa::tracked(cycle_initial=|db, _| Interned::new(db, 0))] fn query_b(db: &dyn Database) -> Interned<'_> { query_c(db); Interned::new(db, 2) } #[salsa::tracked] fn query_c(db: &dyn Database) { query_a(db); } #[salsa::tracked] fn query_d<'db>(_db: &'db dyn Database, _i: Interned<'db>) { // reads some input } #[test_log::test] fn the_test() { let mut db = ExecuteValidateLoggerDatabase::default(); // We compute the result starting from query a... query_a(&db); db.clear_logs(); db.synthetic_write(Durability::HIGH); // ...but we now verify query_b query_b(&db); // What this test captures is that `Interned(Id(c00))` must be verified **before** `query_d(Id(c00))` // as we would when starting from `query_a` db.assert_logs(expect![[r#" [ "salsa_event(DidValidateInternedValue { key: query_b::interned_arguments(Id(400)), revision: R2 })", "salsa_event(WillExecute { database_key: query_b(Id(400)) })", "salsa_event(DidValidateInternedValue { key: query_c::interned_arguments(Id(800)), revision: R2 })", "salsa_event(WillExecute { database_key: query_c(Id(800)) })", "salsa_event(DidValidateInternedValue { key: query_b::interned_arguments(Id(400)), revision: R2 })", "salsa_event(DidValidateInternedValue { key: query_c::interned_arguments(Id(800)), revision: R2 })", "salsa_event(DidValidateInternedValue { key: query_a::interned_arguments(Id(0)), revision: R2 })", "salsa_event(DidValidateInternedValue { key: Interned(Id(c00)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_d(Id(c00)) })", "salsa_event(DidValidateMemoizedValue { database_key: query_a(Id(0)) })", ]"#]]); } salsa-0.26.2/tests/cycle_fallback_immediate.rs000064400000000000000000000020511046102023000174600ustar 00000000000000#![cfg(feature = "inventory")] //! It is possible to omit the `cycle_fn`, only specifying `cycle_result` in which case //! an immediate fallback value is used as the cycle handling opposed to doing a fixpoint resolution. #[salsa::tracked(cycle_result=cycle_result)] fn one_o_one(db: &dyn salsa::Database) -> u32 { let val = one_o_one(db); val + 1 } fn cycle_result(_db: &dyn salsa::Database, _id: salsa::Id) -> u32 { 100 } #[test_log::test] fn simple() { let db = salsa::DatabaseImpl::default(); assert_eq!(one_o_one(&db), 100); } #[salsa::tracked(cycle_result=two_queries_cycle_result)] fn two_queries1(db: &dyn salsa::Database) -> i32 { two_queries2(db) + 1 } #[salsa::tracked(cycle_result=two_queries_cycle_result)] fn two_queries2(db: &dyn salsa::Database) -> i32 { two_queries1(db) } fn two_queries_cycle_result(_db: &dyn salsa::Database, _id: salsa::Id) -> i32 { 1 } #[test] fn two_queries() { let db = salsa::DatabaseImpl::default(); assert_eq!(two_queries1(&db), 1); assert_eq!(two_queries2(&db), 1); } salsa-0.26.2/tests/cycle_initial_call_back_into_cycle.rs000064400000000000000000000011551046102023000215230ustar 00000000000000#![cfg(feature = "inventory")] //! Calling back into the same cycle from your cycle initial function will trigger another cycle. #[salsa::tracked] fn initial_value(db: &dyn salsa::Database) -> u32 { query(db) } #[salsa::tracked(cycle_initial=cycle_initial)] fn query(db: &dyn salsa::Database) -> u32 { let val = query(db); if val < 5 { val + 1 } else { val } } fn cycle_initial(db: &dyn salsa::Database, _id: salsa::Id) -> u32 { initial_value(db) } #[test_log::test] #[should_panic(expected = "dependency graph cycle")] fn the_test() { let db = salsa::DatabaseImpl::default(); query(&db); } salsa-0.26.2/tests/cycle_initial_call_query.rs000064400000000000000000000007261046102023000175630ustar 00000000000000#![cfg(feature = "inventory")] //! It's possible to call a Salsa query from within a cycle initial fn. #[salsa::tracked] fn initial_value(_db: &dyn salsa::Database) -> u32 { 0 } #[salsa::tracked(cycle_initial= |db, _| initial_value(db))] fn query(db: &dyn salsa::Database) -> u32 { let val = query(db); if val < 5 { val + 1 } else { val } } #[test_log::test] fn the_test() { let db = salsa::DatabaseImpl::default(); assert_eq!(query(&db), 5); } salsa-0.26.2/tests/cycle_input_different_cycle_head.rs000064400000000000000000000024561046102023000212410ustar 00000000000000#![cfg(feature = "inventory")] //! Tests that the durability correctly propagates //! to all cycle heads. use salsa::Setter as _; #[test_log::test] fn low_durability_cycle_enter_from_different_head() { let mut db = MyDbImpl::default(); // Start with 0, the same as returned by cycle initial let input = Input::builder(0).new(&db); db.input = Some(input); assert_eq!(query_a(&db), 0); // Prime the Db input.set_value(&mut db).to(10); assert_eq!(query_b(&db), 10); } #[salsa::input] struct Input { value: u32, } #[salsa::db] trait MyDb: salsa::Database { fn input(&self) -> Input; } #[salsa::db] #[derive(Clone, Default)] struct MyDbImpl { storage: salsa::Storage, input: Option, } #[salsa::db] impl salsa::Database for MyDbImpl {} #[salsa::db] impl MyDb for MyDbImpl { fn input(&self) -> Input { self.input.unwrap() } } #[salsa::tracked(cycle_initial=cycle_initial)] fn query_a(db: &dyn MyDb) -> u32 { query_b(db); db.input().value(db) } fn cycle_initial(_db: &dyn MyDb, _id: salsa::Id) -> u32 { 0 } #[salsa::interned] struct Interned { value: u32, } #[salsa::tracked(cycle_initial=cycle_initial)] fn query_b(db: &dyn MyDb) -> u32 { query_c(db) } #[salsa::tracked] fn query_c(db: &dyn MyDb) -> u32 { query_a(db) } salsa-0.26.2/tests/cycle_left_recursive_query.rs000064400000000000000000000071021046102023000201530ustar 00000000000000#![cfg(all(feature = "inventory", feature = "accumulator"))] use crate::common::{ExecuteValidateLoggerDatabase, LogDatabase}; use expect_test::expect; use salsa::{Database, Durability, Id}; mod common; #[salsa::tracked(cycle_initial=cycle_initial)] fn query_a(db: &dyn salsa::Database) -> Interned<'_> { let interned = query_b(db); let value = interned.value(db); if value < 10 { Interned::new(db, value + 1) } else { interned } } #[salsa::tracked] fn query_b(db: &dyn Database) -> Interned<'_> { let interned = query_a(db); query_x(db, interned); interned } #[salsa::tracked] fn query_x<'db>(_db: &'db dyn Database, _i: Interned<'db>) {} fn cycle_initial(db: &dyn Database, _id: Id) -> Interned<'_> { Interned::new(db, 0) } #[salsa::interned] struct Interned { value: u32, } #[test_log::test] fn the_test() { let mut db = ExecuteValidateLoggerDatabase::default(); let result = query_a(&db); assert_eq!(result.value(&db), 10); db.clear_logs(); db.synthetic_write(Durability::HIGH); let result = query_a(&db); assert_eq!(result.value(&db), 10); // What this test captures is that the interned values **must** be validated before validating their corresponding `query_x` call. db.assert_logs(expect![[r#" [ "salsa_event(DidValidateInternedValue { key: query_b::interned_arguments(Id(400)), revision: R2 })", "salsa_event(DidValidateInternedValue { key: query_a::interned_arguments(Id(0)), revision: R2 })", "salsa_event(DidValidateInternedValue { key: Interned(Id(800)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_x(Id(800)) })", "salsa_event(DidValidateInternedValue { key: Interned(Id(801)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_x(Id(801)) })", "salsa_event(DidValidateInternedValue { key: Interned(Id(802)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_x(Id(802)) })", "salsa_event(DidValidateInternedValue { key: Interned(Id(803)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_x(Id(803)) })", "salsa_event(DidValidateInternedValue { key: Interned(Id(804)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_x(Id(804)) })", "salsa_event(DidValidateInternedValue { key: Interned(Id(805)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_x(Id(805)) })", "salsa_event(DidValidateInternedValue { key: Interned(Id(806)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_x(Id(806)) })", "salsa_event(DidValidateInternedValue { key: Interned(Id(807)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_x(Id(807)) })", "salsa_event(DidValidateInternedValue { key: Interned(Id(808)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_x(Id(808)) })", "salsa_event(DidValidateInternedValue { key: Interned(Id(809)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_x(Id(809)) })", "salsa_event(DidValidateInternedValue { key: Interned(Id(80a)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_x(Id(80a)) })", "salsa_event(DidValidateMemoizedValue { database_key: query_a(Id(0)) })", ]"#]]); } salsa-0.26.2/tests/cycle_maybe_changed_after.rs000064400000000000000000000120541046102023000176360ustar 00000000000000#![cfg(feature = "inventory")] //! Tests for incremental validation for queries involved in a cycle. mod common; use crate::common::EventLoggerDatabase; use salsa::{Database, Durability, Setter}; #[salsa::input(debug)] struct Input { value: u32, max: u32, } #[salsa::interned(debug)] struct Output<'db> { value: u32, } #[salsa::tracked(cycle_initial=query_a_initial)] fn query_c(db: &dyn salsa::Database, input: Input) -> u32 { query_d(db, input) } #[salsa::tracked] fn query_d(db: &dyn salsa::Database, input: Input) -> u32 { let value = query_c(db, input); if value < input.max(db) * 2 { // Only the first iteration depends on value but the entire // cycle must re-run if input changes. let result = value + input.value(db); Output::new(db, result); result } else { value } } fn query_a_initial(_db: &dyn Database, _id: salsa::Id, _input: Input) -> u32 { 0 } /// Only the first iteration depends on `input.value`. It's important that the entire query /// reruns if `input.value` changes. That's why salsa has to carry-over the inputs and outputs /// from the previous iteration. #[test_log::test] fn first_iteration_input_only() { #[salsa::tracked(cycle_initial=query_a_initial)] fn query_a(db: &dyn salsa::Database, input: Input) -> u32 { query_b(db, input) } #[salsa::tracked] fn query_b(db: &dyn salsa::Database, input: Input) -> u32 { let value = query_a(db, input); if value < input.max(db) { // Only the first iteration depends on value but the entire // cycle must re-run if input changes. value + input.value(db) } else { value } } let mut db = EventLoggerDatabase::default(); let input = Input::builder(4, 5).durability(Durability::MEDIUM).new(&db); { let result = query_a(&db, input); assert_eq!(result, 8); } { input.set_value(&mut db).to(3); let result = query_a(&db, input); assert_eq!(result, 6); } } /// Very similar to the previous test, but the difference is that the called function /// isn't the cycle head and that `cycle_participant` is called from /// both the `cycle_head` and the `entry` function. #[test_log::test] fn nested_cycle_fewer_dependencies_in_first_iteration() { #[salsa::interned(debug)] struct ClassLiteral<'db> { scope: Scope<'db>, } #[salsa::tracked] impl<'db> ClassLiteral<'db> { #[salsa::tracked] fn context(self, db: &'db dyn salsa::Database) -> u32 { let scope = self.scope(db); // Access a field on `scope` that changed in the new revision. scope.field(db) } } #[salsa::tracked(debug)] struct Scope<'db> { field: u32, } #[salsa::tracked] fn create_interned<'db>(db: &'db dyn salsa::Database, scope: Scope<'db>) -> ClassLiteral<'db> { ClassLiteral::new(db, scope) } #[derive(Eq, PartialEq, Debug, salsa::Update)] struct Index<'db> { scope: Scope<'db>, } #[salsa::tracked(cycle_initial=head_initial)] fn cycle_head(db: &dyn salsa::Database, input: Input) -> Option> { let b = cycle_outer(db, input); tracing::info!("query_b = {b:?}"); b.or_else(|| { let index = index(db, input); Some(create_interned(db, index.scope)) }) } fn head_initial(_db: &dyn Database, _id: salsa::Id, _input: Input) -> Option> { None } #[salsa::tracked] fn cycle_outer(db: &dyn salsa::Database, input: Input) -> Option> { cycle_participant(db, input) } #[salsa::tracked] fn cycle_participant(db: &dyn salsa::Database, input: Input) -> Option> { let value = cycle_head(db, input); tracing::info!("cycle_head = {value:?}"); if let Some(value) = value { value.context(db); Some(value) } else { None } } #[salsa::tracked(returns(ref))] fn index(db: &dyn salsa::Database, input: Input) -> Index<'_> { Index { scope: Scope::new(db, input.value(db) * 2), } } #[salsa::tracked] fn entry(db: &dyn salsa::Database, input: Input) -> u32 { let _ = input.value(db); let head = cycle_head(db, input); let participant = cycle_participant(db, input); tracing::debug!("head: {head:?}, participant: {participant:?}"); head.or(participant) .map(|class| class.scope(db).field(db)) .unwrap_or(0) } let mut db = EventLoggerDatabase::default(); let input = Input::builder(3, 5) .max_durability(Durability::HIGH) .value_durability(Durability::LOW) .new(&db); { let result = entry(&db, input); assert_eq!(result, 6); } db.synthetic_write(Durability::MEDIUM); { input.set_value(&mut db).to(4); let result = entry(&db, input); assert_eq!(result, 8); } } salsa-0.26.2/tests/cycle_nested_converge_early.rs000064400000000000000000000056321046102023000202610ustar 00000000000000#![cfg(feature = "inventory")] //! A nested cycle where the inner cycle (`query_b`) stops depending on the outer cycle (`query_a`) after one iteration. use crate::common::{ExecuteValidateLoggerDatabase, LogDatabase}; use expect_test::expect; mod common; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); impl std::ops::Add for CycleValue { type Output = Self; fn add(self, other: CycleValue) -> CycleValue { CycleValue(self.0 + other.0) } } const MIN: CycleValue = CycleValue(0); const MAX: CycleValue = CycleValue(3); #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn salsa::Database) -> CycleValue { let b = query_b(db); let c = query_d(db); (b + c).min(MAX) } #[salsa::tracked(cycle_initial=initial)] fn query_b(db: &dyn salsa::Database) -> CycleValue { let c_value = query_c(db); if c_value == CycleValue(0) { query_a(db) + CycleValue(1) } else { c_value } } #[salsa::tracked(cycle_initial=initial)] fn query_c(db: &dyn salsa::Database) -> CycleValue { let b_value = query_b(db); if b_value == CycleValue(0) { query_d(db) } else { b_value } } #[salsa::tracked(cycle_initial=initial)] fn query_d(db: &dyn salsa::Database) -> CycleValue { let a_value = query_a(db); if a_value == CycleValue(0) { query_c(db) } else { a_value } } fn initial(_db: &dyn salsa::Database, _id: salsa::Id) -> CycleValue { MIN } #[test] fn the_test() { let db = ExecuteValidateLoggerDatabase::default(); let result = query_a(&db); assert_eq!(result, MAX); db.assert_logs(expect![[r#" [ "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillExecute { database_key: query_b(Id(400)) })", "salsa_event(WillExecute { database_key: query_c(Id(800)) })", "salsa_event(WillExecute { database_key: query_d(Id(c00)) })", "salsa_event(WillIterateCycle { database_key: query_a(Id(0)), iteration_count: IterationCount(1) })", "salsa_event(WillExecute { database_key: query_b(Id(400)) })", "salsa_event(WillExecute { database_key: query_c(Id(800)) })", "salsa_event(DidFinalizeCycle { database_key: query_b(Id(400)), iteration_count: IterationCount(1) })", "salsa_event(WillExecute { database_key: query_d(Id(c00)) })", "salsa_event(WillIterateCycle { database_key: query_a(Id(0)), iteration_count: IterationCount(2) })", "salsa_event(WillExecute { database_key: query_d(Id(c00)) })", "salsa_event(WillIterateCycle { database_key: query_a(Id(0)), iteration_count: IterationCount(3) })", "salsa_event(WillExecute { database_key: query_d(Id(c00)) })", "salsa_event(DidFinalizeCycle { database_key: query_a(Id(0)), iteration_count: IterationCount(3) })", ]"#]]); } salsa-0.26.2/tests/cycle_nested_converges_never.rs000064400000000000000000000040641046102023000204450ustar 00000000000000#![cfg(feature = "inventory")] //! A nested cycle where the inner cycle (`query_c`) never converges but `query_a` only depends on it in the first iteration. use crate::common::{ExecuteValidateLoggerDatabase, LogDatabase}; use expect_test::expect; mod common; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); impl std::ops::Add for CycleValue { type Output = Self; fn add(self, other: CycleValue) -> CycleValue { CycleValue(self.0 + other.0) } } const MIN: CycleValue = CycleValue(0); #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn salsa::Database) -> CycleValue { let b = query_b(db); tracing::info!("query_b: {b:?}"); if b < CycleValue(1) { query_c(db) } else { b } } #[salsa::tracked(cycle_initial=initial)] fn query_b(db: &dyn salsa::Database) -> CycleValue { query_a(db) } #[salsa::tracked(cycle_initial=initial)] fn query_c(db: &dyn salsa::Database) -> CycleValue { let a_value = query_a(db); let d_value = query_d(db); a_value + d_value + CycleValue(1) } #[salsa::tracked(cycle_initial=initial)] fn query_d(db: &dyn salsa::Database) -> CycleValue { query_c(db) } fn initial(_db: &dyn salsa::Database, _id: salsa::Id) -> CycleValue { MIN } #[test] fn the_test() { let db = ExecuteValidateLoggerDatabase::default(); let result = query_a(&db); assert_eq!(result, CycleValue(1)); db.assert_logs(expect![[r#" [ "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillExecute { database_key: query_b(Id(400)) })", "salsa_event(WillExecute { database_key: query_c(Id(800)) })", "salsa_event(WillExecute { database_key: query_d(Id(c00)) })", "salsa_event(WillIterateCycle { database_key: query_a(Id(0)), iteration_count: IterationCount(1) })", "salsa_event(WillExecute { database_key: query_b(Id(400)) })", "salsa_event(DidFinalizeCycle { database_key: query_a(Id(0)), iteration_count: IterationCount(1) })", ]"#]]); } salsa-0.26.2/tests/cycle_output.rs000064400000000000000000000147561046102023000152620ustar 00000000000000#![cfg(feature = "inventory")] //! Test tracked struct output from a query in a cycle. mod common; use common::{HasLogger, LogDatabase, Logger}; use expect_test::expect; use salsa::{Setter, Storage}; #[salsa::tracked] struct Output<'db> { value: u32, } #[salsa::input] struct InputValue { value: u32, } #[salsa::tracked] fn read_value<'db>(db: &'db dyn Db, output: Output<'db>) -> u32 { output.value(db) } #[salsa::tracked] fn query_a(db: &dyn Db, input: InputValue) -> u32 { let val = query_b(db, input); let output = Output::new(db, val); let read = read_value(db, output); assert_eq!(read, val); query_d(db); if val > 2 { val } else { val + input.value(db) } } #[salsa::tracked(cycle_initial=cycle_initial)] fn query_b(db: &dyn Db, input: InputValue) -> u32 { query_a(db, input) } fn cycle_initial(_db: &dyn Db, _id: salsa::Id, _input: InputValue) -> u32 { 0 } #[salsa::tracked] fn query_c(db: &dyn Db, input: InputValue) -> u32 { input.value(db) } #[salsa::tracked] fn query_d(db: &dyn Db) -> u32 { db.get_input().map(|input| input.value(db)).unwrap_or(0) } trait HasOptionInput { fn get_input(&self) -> Option; fn set_input(&mut self, input: InputValue); } #[salsa::db] trait Db: HasOptionInput + salsa::Database {} #[salsa::db] #[derive(Clone)] struct Database { storage: salsa::Storage, logger: Logger, input: Option, } impl HasLogger for Database { fn logger(&self) -> &Logger { &self.logger } } impl Default for Database { fn default() -> Self { let logger = Logger::default(); Self { storage: Storage::new(Some(Box::new({ let logger = logger.clone(); move |event| match event.kind { salsa::EventKind::WillExecute { .. } | salsa::EventKind::DidValidateMemoizedValue { .. } => { logger.push_log(format!("salsa_event({:?})", event.kind)); } salsa::EventKind::WillCheckCancellation => {} _ => { logger.push_log(format!("salsa_event({:?})", event.kind)); } } }))), logger, input: Default::default(), } } } impl HasOptionInput for Database { fn get_input(&self) -> Option { self.input } fn set_input(&mut self, input: InputValue) { self.input.replace(input); } } #[salsa::db] impl salsa::Database for Database {} #[salsa::db] impl Db for Database {} #[test_log::test] fn single_revision() { let db = Database::default(); let input = InputValue::new(&db, 1); assert_eq!(query_b(&db, input), 3); } #[test_log::test] fn revalidate_no_changes() { let mut db = Database::default(); let ab_input = InputValue::new(&db, 1); let c_input = InputValue::new(&db, 10); assert_eq!(query_c(&db, c_input), 10); assert_eq!(query_b(&db, ab_input), 3); db.assert_logs_len(16); // trigger a new revision, but one that doesn't touch the query_a/query_b cycle c_input.set_value(&mut db).to(20); assert_eq!(query_b(&db, ab_input), 3); db.assert_logs(expect![[r#" [ "salsa_event(DidSetCancellationFlag)", "salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(400)) })", "salsa_event(DidValidateInternedValue { key: query_d::interned_arguments(Id(800)), revision: R2 })", "salsa_event(DidValidateMemoizedValue { database_key: query_d(Id(800)) })", "salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(401)) })", "salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(402)) })", "salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(403)) })", "salsa_event(DidValidateMemoizedValue { database_key: query_b(Id(0)) })", ]"#]]); } #[test_log::test] fn revalidate_with_change_after_output_read() { let mut db = Database::default(); let ab_input = InputValue::new(&db, 1); let d_input = InputValue::new(&db, 10); db.set_input(d_input); assert_eq!(query_b(&db, ab_input), 3); db.assert_logs_len(15); // trigger a new revision that changes the output of query_d d_input.set_value(&mut db).to(20); assert_eq!(query_b(&db, ab_input), 3); db.assert_logs(expect![[r#" [ "salsa_event(DidSetCancellationFlag)", "salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(400)) })", "salsa_event(DidValidateInternedValue { key: query_d::interned_arguments(Id(800)), revision: R2 })", "salsa_event(WillExecute { database_key: query_d(Id(800)) })", "salsa_event(WillExecute { database_key: query_b(Id(0)) })", "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillDiscardStaleOutput { execute_key: query_a(Id(0)), output_key: Output(Id(401)) })", "salsa_event(DidDiscard { key: Output(Id(401)) })", "salsa_event(DidDiscard { key: read_value(Id(401)) })", "salsa_event(WillDiscardStaleOutput { execute_key: query_a(Id(0)), output_key: Output(Id(402)) })", "salsa_event(DidDiscard { key: Output(Id(402)) })", "salsa_event(DidDiscard { key: read_value(Id(402)) })", "salsa_event(WillDiscardStaleOutput { execute_key: query_a(Id(0)), output_key: Output(Id(403)) })", "salsa_event(DidDiscard { key: Output(Id(403)) })", "salsa_event(DidDiscard { key: read_value(Id(403)) })", "salsa_event(WillIterateCycle { database_key: query_b(Id(0)), iteration_count: IterationCount(1) })", "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillExecute { database_key: read_value(Id(401g1)) })", "salsa_event(WillIterateCycle { database_key: query_b(Id(0)), iteration_count: IterationCount(2) })", "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillExecute { database_key: read_value(Id(402g1)) })", "salsa_event(WillIterateCycle { database_key: query_b(Id(0)), iteration_count: IterationCount(3) })", "salsa_event(WillExecute { database_key: query_a(Id(0)) })", "salsa_event(WillExecute { database_key: read_value(Id(403g1)) })", "salsa_event(DidFinalizeCycle { database_key: query_b(Id(0)), iteration_count: IterationCount(3) })", ]"#]]); } salsa-0.26.2/tests/cycle_recovery_call_back_into_cycle.rs000064400000000000000000000017551046102023000217360ustar 00000000000000#![cfg(feature = "inventory")] //! Calling back into the same cycle from your cycle recovery function _can_ work out, as long as //! the overall cycle still converges. mod common; use common::{DatabaseWithValue, ValueDatabase}; #[salsa::tracked] fn fallback_value(db: &dyn ValueDatabase) -> u32 { query(db) + db.get_value() } #[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=cycle_initial)] fn query(db: &dyn ValueDatabase) -> u32 { let val = query(db); if val < 5 { val + 1 } else { val } } fn cycle_initial(_db: &dyn ValueDatabase, _id: salsa::Id) -> u32 { 0 } fn cycle_fn( db: &dyn ValueDatabase, _cycle: &salsa::Cycle, last_provisional_value: &u32, value: u32, ) -> u32 { if &value == last_provisional_value { value } else { fallback_value(db) } } #[test] fn converges() { let db = DatabaseWithValue::new(10); assert_eq!(query(&db), 10); } #[test] fn diverges() { let db = DatabaseWithValue::new(3); query(&db); } salsa-0.26.2/tests/cycle_recovery_call_query.rs000064400000000000000000000007671046102023000177750ustar 00000000000000#![cfg(feature = "inventory")] //! It's possible to call a Salsa query from within a cycle recovery fn. #[salsa::tracked] fn fallback_value(_db: &dyn salsa::Database) -> u32 { 10 } #[salsa::tracked(cycle_fn = |db, _, _, _| fallback_value(db), cycle_initial = |_, _| 0)] fn query(db: &dyn salsa::Database) -> u32 { let val = query(db); if val < 5 { val + 1 } else { val } } #[test_log::test] fn the_test() { let db = salsa::DatabaseImpl::default(); assert_eq!(query(&db), 10); } salsa-0.26.2/tests/cycle_recovery_dependencies.rs000064400000000000000000000044361046102023000202600ustar 00000000000000#![cfg(feature = "inventory")] //! Queries or inputs read within the cycle recovery function //! are tracked on the cycle function and don't "leak" into the //! function calling the query with cycle handling. use expect_test::expect; use salsa::Setter as _; use crate::common::LogDatabase; mod common; #[salsa::input] struct Input { value: u32, } #[salsa::tracked] fn entry(db: &dyn salsa::Database, input: Input) -> u32 { query(db, input) } #[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=cycle_initial)] fn query(db: &dyn salsa::Database, input: Input) -> u32 { let val = query(db, input); if val < 5 { val + 1 } else { val } } fn cycle_initial(_db: &dyn salsa::Database, _id: salsa::Id, _input: Input) -> u32 { 0 } fn cycle_fn( db: &dyn salsa::Database, _cycle: &salsa::Cycle, _last_provisional_value: &u32, value: u32, input: Input, ) -> u32 { let _input = input.value(db); value } #[test_log::test] fn the_test() { let mut db = common::EventLoggerDatabase::default(); let input = Input::new(&db, 1); assert_eq!(entry(&db, input), 5); db.assert_logs_len(16); input.set_value(&mut db).to(2); assert_eq!(entry(&db, input), 5); db.assert_logs(expect![[r#" [ "DidSetCancellationFlag", "WillCheckCancellation", "WillCheckCancellation", "WillExecute { database_key: query(Id(0)) }", "WillCheckCancellation", "WillIterateCycle { database_key: query(Id(0)), iteration_count: IterationCount(1) }", "WillCheckCancellation", "WillIterateCycle { database_key: query(Id(0)), iteration_count: IterationCount(2) }", "WillCheckCancellation", "WillIterateCycle { database_key: query(Id(0)), iteration_count: IterationCount(3) }", "WillCheckCancellation", "WillIterateCycle { database_key: query(Id(0)), iteration_count: IterationCount(4) }", "WillCheckCancellation", "WillIterateCycle { database_key: query(Id(0)), iteration_count: IterationCount(5) }", "WillCheckCancellation", "DidFinalizeCycle { database_key: query(Id(0)), iteration_count: IterationCount(5) }", "DidValidateMemoizedValue { database_key: entry(Id(0)) }", ]"#]]); } salsa-0.26.2/tests/cycle_regression_455.rs000064400000000000000000000017731046102023000164720ustar 00000000000000#![cfg(feature = "inventory")] use salsa::{Database, Setter}; #[salsa::tracked] fn memoized(db: &dyn Database, input: MyInput) -> u32 { memoized_a(db, MyTracked::new(db, input.field(db))) } #[salsa::tracked(cycle_initial=cycle_initial)] fn memoized_a<'db>(db: &'db dyn Database, tracked: MyTracked<'db>) -> u32 { MyTracked::new(db, 0); memoized_b(db, tracked) } fn cycle_initial(_db: &dyn Database, _id: salsa::Id, _input: MyTracked) -> u32 { 0 } #[salsa::tracked] fn memoized_b<'db>(db: &'db dyn Database, tracked: MyTracked<'db>) -> u32 { let incr = tracked.field(db); let a = memoized_a(db, tracked); if a > 8 { a } else { a + incr } } #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[test] fn cycle_memoized() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 2); assert_eq!(memoized(&db, input), 10); input.set_field(&mut db).to(3); assert_eq!(memoized(&db, input), 9); } salsa-0.26.2/tests/cycle_result_dependencies.rs000064400000000000000000000011411046102023000177260ustar 00000000000000#![cfg(feature = "inventory")] use salsa::{Database, Setter}; #[salsa::input] struct Input { value: i32, } #[salsa::tracked(cycle_result=cycle_result)] fn has_cycle(db: &dyn Database, input: Input) -> i32 { has_cycle(db, input) } fn cycle_result(db: &dyn Database, _id: salsa::Id, input: Input) -> i32 { input.value(db) } #[test] fn cycle_result_dependencies_are_recorded() { let mut db = salsa::DatabaseImpl::default(); let input = Input::new(&db, 123); assert_eq!(has_cycle(&db, input), 123); input.set_value(&mut db).to(456); assert_eq!(has_cycle(&db, input), 456); } salsa-0.26.2/tests/cycle_stale_cycle_heads.rs000064400000000000000000000052201046102023000173370ustar 00000000000000#![cfg(feature = "inventory")] //! Test for stale cycle heads when nested cycles are discovered incrementally. //! //! Scenario from ty: /// ```txt /// E -> C -> D -> B -> A -> B (cycle) /// -- A completes, heads = [B] /// E -> C -> D -> B -> C (cycle) /// -> D (cycle) /// -- B completes, heads = [B, C, D] /// E -> C -> D -> E (cycle) /// -- D completes, heads = [E, B, C, D] /// E -> C /// -- C completes, heads = [E, B, C, D] /// E -> X -> A /// -- X completes, heads = [B] /// ``` /// /// Note how `X` only depends on `B`, but not on `E`, unless we collect the cycle heads transitively, /// which is what this test is asserting. #[salsa::input] struct Input { value: u32, } // Outer cycle head - should iterate #[salsa::tracked(cycle_initial = initial_zero)] fn query_e(db: &dyn salsa::Database, input: Input) -> u32 { // First call C to establish the nested cycles let c_val = query_c(db, input); // Then later call X which will read A with stale cycle heads // By this point, A has already completed and memoized with cycle_heads=[B] // But E is still on the stack let x_val = query_x(db, input); c_val.min(x_val) } #[salsa::tracked(cycle_initial = initial_zero)] fn query_c(db: &dyn salsa::Database, input: Input) -> u32 { query_d(db, input) } #[salsa::tracked(cycle_initial = initial_zero)] fn query_d(db: &dyn salsa::Database, input: Input) -> u32 { let b_val = query_b(db, input); // Create cycle back to E let e_val = query_e(db, input); b_val.min(e_val) } #[salsa::tracked(cycle_initial = initial_zero)] fn query_b(db: &dyn salsa::Database, input: Input) -> u32 { // First call A - this will detect A<->B cycle and A will complete let a_val = query_a(db, input); let c_val = query_c(db, input); let d_val = query_d(db, input); // Then read C - this reveals B is part of C's cycle (a_val + d_val + c_val).min(50) } #[salsa::tracked(cycle_initial = initial_zero)] fn query_a(db: &dyn salsa::Database, input: Input) -> u32 { // Read B to create A<->B cycle let b_val = query_b(db, input); // Also read input let val = input.value(db); b_val.max(val) } #[salsa::tracked(cycle_initial = initial_zero)] fn query_x(db: &dyn salsa::Database, input: Input) -> u32 { // This reads A's memoized result which has stale cycle_heads query_a(db, input) } fn initial_zero(_db: &dyn salsa::Database, _id: salsa::Id, _input: Input) -> u32 { 0 } #[test] fn run() { let db = salsa::DatabaseImpl::new(); let input = Input::new(&db, 50); let result = query_e(&db, input); assert_eq!(result, 0); } salsa-0.26.2/tests/cycle_tracked.rs000064400000000000000000000262731046102023000153340ustar 00000000000000#![cfg(feature = "inventory")] mod common; use crate::common::{EventLoggerDatabase, LogDatabase}; use expect_test::expect; use salsa::{Database, Setter}; #[derive(Clone, Debug, Eq, PartialEq, Hash, salsa::Update)] struct Graph<'db> { nodes: Vec>, } impl<'db> Graph<'db> { fn find_node(&self, db: &dyn salsa::Database, name: &str) -> Option> { self.nodes .iter() .find(|node| node.name(db) == name) .copied() } } #[derive(Clone, Debug, Eq, PartialEq, Hash)] struct Edge { // Index into `graph.nodes` to: usize, cost: usize, } #[salsa::tracked(debug)] struct Node<'db> { #[returns(ref)] name: String, #[returns(deref)] #[tracked] edges: Vec, graph: GraphInput, } #[salsa::input(debug)] struct GraphInput { simple: bool, fixpoint_variant: usize, } #[salsa::tracked(returns(ref))] fn create_graph(db: &dyn salsa::Database, input: GraphInput) -> Graph<'_> { if input.simple(db) { let a = Node::new(db, "a".to_string(), vec![], input); let b = Node::new(db, "b".to_string(), vec![Edge { to: 0, cost: 20 }], input); let c = Node::new(db, "c".to_string(), vec![Edge { to: 1, cost: 2 }], input); Graph { nodes: vec![a, b, c], } } else { // ``` // flowchart TD // // A("a") // B("b") // C("c") // D{"d"} // // B -- 20 --> D // C -- 4 --> D // D -- 4 --> A // D -- 4 --> B // ``` let a = Node::new(db, "a".to_string(), vec![], input); let b = Node::new(db, "b".to_string(), vec![Edge { to: 3, cost: 20 }], input); let c = Node::new(db, "c".to_string(), vec![Edge { to: 3, cost: 4 }], input); let d = Node::new( db, "d".to_string(), vec![Edge { to: 0, cost: 4 }, Edge { to: 1, cost: 4 }], input, ); Graph { nodes: vec![a, b, c, d], } } } /// Computes the minimum cost from the node with offset `0` to the given node. #[salsa::tracked(cycle_initial=max_initial)] fn cost_to_start<'db>(db: &'db dyn Database, node: Node<'db>) -> usize { let mut min_cost = usize::MAX; let graph = create_graph(db, node.graph(db)); for edge in node.edges(db) { if edge.to == 0 { min_cost = min_cost.min(edge.cost); } let edge_cost_to_start = cost_to_start(db, graph.nodes[edge.to]); // We hit a cycle, never take this edge because it will always be more expensive than // any other edge if edge_cost_to_start == usize::MAX { continue; } min_cost = min_cost.min(edge.cost + edge_cost_to_start); } min_cost } fn max_initial(_db: &dyn Database, _id: salsa::Id, _node: Node) -> usize { usize::MAX } /// Tests for cycles where the cycle head is stored on a tracked struct /// and that tracked struct is freed in a later revision. #[test] fn main() { let mut db = EventLoggerDatabase::default(); let input = GraphInput::new(&db, false, 0); let graph = create_graph(&db, input); let c = graph.find_node(&db, "c").unwrap(); // Query the cost from `c` to `a`. // There's a cycle between `b` and `d`, where `d` becomes the cycle head and `b` is a provisional, non finalized result. assert_eq!(cost_to_start(&db, c), 8); // Change the graph, this will remove `d`, leaving `b` pointing to a cycle head that's now collected. // Querying the cost from `c` to `a` should try to verify the result of `b` and it is important // that `b` doesn't try to dereference the cycle head (because its memo is now stored on a tracked // struct that has been freed). input.set_simple(&mut db).to(true); let graph = create_graph(&db, input); let c = graph.find_node(&db, "c").unwrap(); assert_eq!(cost_to_start(&db, c), 22); db.assert_logs(expect![[r#" [ "WillCheckCancellation", "WillExecute { database_key: create_graph(Id(0)) }", "WillCheckCancellation", "WillExecute { database_key: cost_to_start(Id(402)) }", "WillCheckCancellation", "WillCheckCancellation", "WillExecute { database_key: cost_to_start(Id(403)) }", "WillCheckCancellation", "WillCheckCancellation", "WillExecute { database_key: cost_to_start(Id(400)) }", "WillCheckCancellation", "WillCheckCancellation", "WillExecute { database_key: cost_to_start(Id(401)) }", "WillCheckCancellation", "WillCheckCancellation", "WillIterateCycle { database_key: cost_to_start(Id(403)), iteration_count: IterationCount(1) }", "WillCheckCancellation", "WillCheckCancellation", "WillCheckCancellation", "WillExecute { database_key: cost_to_start(Id(401)) }", "WillCheckCancellation", "WillCheckCancellation", "DidFinalizeCycle { database_key: cost_to_start(Id(403)), iteration_count: IterationCount(1) }", "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: create_graph(Id(0)) }", "WillDiscardStaleOutput { execute_key: create_graph(Id(0)), output_key: Node(Id(403)) }", "DidDiscard { key: Node(Id(403)) }", "DidDiscard { key: cost_to_start(Id(403)) }", "WillCheckCancellation", "WillCheckCancellation", "WillExecute { database_key: cost_to_start(Id(402)) }", "WillCheckCancellation", "WillCheckCancellation", "WillExecute { database_key: cost_to_start(Id(401)) }", "WillCheckCancellation", "WillCheckCancellation", "WillCheckCancellation", "WillExecute { database_key: cost_to_start(Id(400)) }", "WillCheckCancellation", ]"#]]); } #[salsa::tracked] struct IterationNode<'db> { #[returns(ref)] name: String, iteration: usize, } /// A cyclic query that creates more tracked structs in later fixpoint iterations. /// /// The output depends on the input's fixpoint_variant: /// - variant=0: Returns `[base]` (1 struct, no cycle) /// - variant=1: Through fixpoint iteration, returns `[iter_0, iter_1, iter_2]` (3 structs) /// - variant=2: Through fixpoint iteration, returns `[iter_0, iter_1]` (2 structs) /// - variant>2: Through fixpoint iteration, returns `[iter_0, iter_1]` (2 structs, same as variant=2) /// /// When variant > 0, the query creates a cycle by calling itself. The fixpoint iteration /// proceeds as follows: /// 1. Initial: returns empty vector /// 2. First iteration: returns `[iter_0]` /// 3. Second iteration: returns `[iter_0, iter_1]` /// 4. Third iteration (only for variant=1): returns `[iter_0, iter_1, iter_2]` /// 5. Further iterations: no change, fixpoint reached #[salsa::tracked(cycle_initial=initial_with_structs)] fn create_tracked_in_cycle(db: &dyn Database, input: GraphInput) -> Vec> { // Check if we should create more nodes based on the input. let variant = input.fixpoint_variant(db); if variant == 0 { // Base case - no cycle, just return a single node. vec![IterationNode::new(db, "base".to_string(), 0)] } else { // Create a cycle by calling ourselves. let previous = create_tracked_in_cycle(db, input); // In later iterations, create additional tracked structs. if previous.is_empty() { // First iteration - initial returns empty. vec![IterationNode::new(db, "iter_0".to_string(), 0)] } else { // Limit based on variant: variant=1 allows 3 nodes, variant=2 allows 2 nodes. let limit = if variant == 1 { 3 } else { 2 }; if previous.len() < limit { // Subsequent iterations - add more nodes. let mut nodes = previous; nodes.push(IterationNode::new( db, format!("iter_{}", nodes.len()), nodes.len(), )); nodes } else { // Reached the limit. previous } } } } fn initial_with_structs( _db: &dyn Database, _id: salsa::Id, _input: GraphInput, ) -> Vec> { vec![] } #[test_log::test] fn test_cycle_with_fixpoint_structs() { let mut db = EventLoggerDatabase::default(); // Create an input that will trigger the cyclic behavior. let input = GraphInput::new(&db, false, 1); // Initial query - this will create structs across multiple iterations. let nodes = create_tracked_in_cycle(&db, input); assert_eq!(nodes.len(), 3); // First iteration: previous is empty [], so we get [iter_0] // Second iteration: previous is [iter_0], so we get [iter_0, iter_1] // Third iteration: previous is [iter_0, iter_1], so we get [iter_0, iter_1, iter_2] assert_eq!(nodes[0].name(&db), "iter_0"); assert_eq!(nodes[1].name(&db), "iter_1"); assert_eq!(nodes[2].name(&db), "iter_2"); // Clear logs to focus on the change. db.clear_logs(); // Change the input to force re-execution with a different variant. // This will create 2 tracked structs instead of 3 (one fewer than before). input.set_fixpoint_variant(&mut db).to(2); // Re-query - this should handle the tracked struct changes properly. let nodes = create_tracked_in_cycle(&db, input); assert_eq!(nodes.len(), 2); assert_eq!(nodes[0].name(&db), "iter_0"); assert_eq!(nodes[1].name(&db), "iter_1"); // Check the logs to ensure proper execution and struct management. // We should see the third struct (iter_2) being discarded. db.assert_logs(expect![[r#" [ "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: create_tracked_in_cycle(Id(0)) }", "WillCheckCancellation", "WillIterateCycle { database_key: create_tracked_in_cycle(Id(0)), iteration_count: IterationCount(1) }", "WillCheckCancellation", "WillIterateCycle { database_key: create_tracked_in_cycle(Id(0)), iteration_count: IterationCount(2) }", "WillCheckCancellation", "DidFinalizeCycle { database_key: create_tracked_in_cycle(Id(0)), iteration_count: IterationCount(2) }", "WillDiscardStaleOutput { execute_key: create_tracked_in_cycle(Id(0)), output_key: IterationNode(Id(402)) }", "DidDiscard { key: IterationNode(Id(402)) }", ]"#]]); } #[salsa::tracked(debug)] struct NameWithOffset<'db> { name: String, #[tracked] offset: u32, } #[test] fn cycle_tracked_struct_with_tracked_field() { #[salsa::tracked(cycle_initial=|_,_| 0)] fn query_a(db: &dyn salsa::Database) -> u32 { let offset = query_b(db); let tracked = NameWithOffset::new(db, "test".to_string(), offset); tracked.offset(db) } #[salsa::tracked] fn query_b(db: &dyn salsa::Database) -> u32 { let base_offset = query_a(db); (base_offset + 1).min(5) } let db = salsa::DatabaseImpl::default(); let result = query_a(&db); assert_eq!(result, 5); } salsa-0.26.2/tests/cycle_tracked_own_input.rs000064400000000000000000000101171046102023000174240ustar 00000000000000#![cfg(feature = "inventory")] //! Test for cycle handling where a tracked struct created in the first revision //! is stored in the final value of the cycle but isn't recreated in the second //! iteration of the creating query. //! //! It's important that the creating query in the last iteration keeps *owning* the //! tracked struct from the previous iteration, otherwise Salsa will discard it //! and dereferencing the value panics. mod common; use crate::common::{EventLoggerDatabase, LogDatabase}; use expect_test::expect; use salsa::{Database, Setter}; #[salsa::input(debug)] struct ClassNode { name: String, type_params: Option, } #[salsa::input(debug)] struct TypeParamNode { name: String, constraint: Option, } #[salsa::interned(debug)] struct Class<'db> { name: String, type_params: Option>, } #[salsa::tracked(debug)] struct TypeParam<'db> { name: String, constraint: Option>, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)] enum Type<'db> { Class(Class<'db>), Unknown, } impl Type<'_> { fn class(&self) -> Option> { match self { Type::Class(class) => Some(*class), Type::Unknown => None, } } } #[salsa::tracked(cycle_initial=infer_class_initial)] fn infer_class(db: &dyn salsa::Database, node: ClassNode) -> Type<'_> { Type::Class(Class::new( db, node.name(db), node.type_params(db).map(|tp| infer_type_param(db, tp)), )) } #[salsa::tracked] fn infer_type_param(db: &dyn salsa::Database, node: TypeParamNode) -> TypeParam<'_> { if let Some(constraint) = node.constraint(db) { // Reuse the type param from the class if any. // The example is a bit silly, because it's a reduction of what we have in Astral's type checker // but including all the details doesn't make sense. What's important for the test is // that this query doesn't re-create the `TypeParam` tracked struct in the second iteration // and instead returns the one from the first iteration which // then is returned in the overall result (Class). match infer_class(db, constraint) { Type::Class(class) => class .type_params(db) .unwrap_or_else(|| TypeParam::new(db, node.name(db), Some(Type::Unknown))), Type::Unknown => TypeParam::new(db, node.name(db), Some(Type::Unknown)), } } else { TypeParam::new(db, node.name(db), None) } } fn infer_class_initial(_db: &'_ dyn Database, _id: salsa::Id, _node: ClassNode) -> Type<'_> { Type::Unknown } #[test] fn main() { let mut db = EventLoggerDatabase::default(); // Class with a type parameter that's constrained to itself. // class Test[T: Test]: ... let class_node = ClassNode::new(&db, "Test".to_string(), None); let type_param_node = TypeParamNode::new(&db, "T".to_string(), Some(class_node)); class_node .set_type_params(&mut db) .to(Some(type_param_node)); let ty = infer_class(&db, class_node); db.assert_logs(expect![[r#" [ "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: infer_class(Id(0)) }", "WillCheckCancellation", "WillExecute { database_key: infer_type_param(Id(400)) }", "WillCheckCancellation", "DidInternValue { key: Class(Id(c00)), revision: R2 }", "WillIterateCycle { database_key: infer_class(Id(0)), iteration_count: IterationCount(1) }", "WillCheckCancellation", "WillExecute { database_key: infer_type_param(Id(400)) }", "WillCheckCancellation", "DidFinalizeCycle { database_key: infer_class(Id(0)), iteration_count: IterationCount(1) }", ]"#]]); let class = ty.class().unwrap(); let type_param = class.type_params(&db).unwrap(); // Now read the name from the type param struct that was created in the first iteration of // `infer_type_param`. This should not panic! assert_eq!(type_param.name(&db), "T"); } salsa-0.26.2/tests/dataflow.rs000064400000000000000000000156661046102023000143450ustar 00000000000000#![cfg(feature = "inventory")] //! Test case for fixpoint iteration cycle resolution. //! //! This test case is intended to simulate a (very simplified) version of a real dataflow analysis //! using fixpoint iteration. use std::collections::BTreeSet; use std::iter::IntoIterator; use salsa::{Database as Db, Setter}; /// A Use of a symbol. #[salsa::input] struct Use { reaching_definitions: Vec, } /// A Definition of a symbol, either of the form `base + increment` or `0 + increment`. #[salsa::input] struct Definition { base: Option, increment: usize, } #[derive(Eq, PartialEq, Clone, Debug, salsa::Update)] enum Type { Bottom, Values(Box<[usize]>), Top, } impl Type { fn join(tys: impl IntoIterator) -> Type { let mut result = Type::Bottom; for ty in tys.into_iter() { result = match (result, ty) { (result, Type::Bottom) => result, (_, Type::Top) => Type::Top, (Type::Top, _) => Type::Top, (Type::Bottom, ty) => ty, (Type::Values(a_ints), Type::Values(b_ints)) => { let mut set = BTreeSet::new(); set.extend(a_ints); set.extend(b_ints); Type::Values(set.into_iter().collect()) } } } result } } #[salsa::tracked(cycle_fn=use_cycle_recover, cycle_initial=use_cycle_initial)] fn infer_use(db: &dyn Db, u: Use) -> Type { let defs = u.reaching_definitions(db); match defs[..] { [] => Type::Bottom, [def] => infer_definition(db, def), _ => Type::join(defs.iter().map(|&def| infer_definition(db, def))), } } #[salsa::tracked(cycle_fn=def_cycle_recover, cycle_initial=def_cycle_initial)] fn infer_definition(db: &dyn Db, def: Definition) -> Type { let increment_ty = Type::Values(Box::from([def.increment(db)])); if let Some(base) = def.base(db) { let base_ty = infer_use(db, base); add(&base_ty, &increment_ty) } else { increment_ty } } fn def_cycle_initial(_db: &dyn Db, _id: salsa::Id, _def: Definition) -> Type { Type::Bottom } fn def_cycle_recover( _db: &dyn Db, cycle: &salsa::Cycle, last_provisional_value: &Type, value: Type, _def: Definition, ) -> Type { if &value == last_provisional_value { value } else { cycle_recover(value, cycle.iteration()) } } fn use_cycle_initial(_db: &dyn Db, _id: salsa::Id, _use: Use) -> Type { Type::Bottom } fn use_cycle_recover( _db: &dyn Db, cycle: &salsa::Cycle, last_provisional_value: &Type, value: Type, _use: Use, ) -> Type { if &value == last_provisional_value { value } else { cycle_recover(value, cycle.iteration()) } } fn cycle_recover(value: Type, count: u32) -> Type { match &value { Type::Bottom => value, Type::Values(_) => { if count > 4 { Type::Top } else { value } } Type::Top => value, } } fn add(a: &Type, b: &Type) -> Type { match (a, b) { (Type::Bottom, _) | (_, Type::Bottom) => Type::Bottom, (Type::Top, _) | (_, Type::Top) => Type::Top, (Type::Values(a_ints), Type::Values(b_ints)) => { let mut set = BTreeSet::new(); set.extend( a_ints .into_iter() .flat_map(|a| b_ints.into_iter().map(move |b| a + b)), ); Type::Values(set.into_iter().collect()) } } } /// x = 1 #[test] fn simple() { let db = salsa::DatabaseImpl::new(); let def = Definition::new(&db, None, 1); let u = Use::new(&db, vec![def]); let ty = infer_use(&db, u); assert_eq!(ty, Type::Values(Box::from([1]))); } /// x = 1 if flag else 2 #[test] fn union() { let db = salsa::DatabaseImpl::new(); let def1 = Definition::new(&db, None, 1); let def2 = Definition::new(&db, None, 2); let u = Use::new(&db, vec![def1, def2]); let ty = infer_use(&db, u); assert_eq!(ty, Type::Values(Box::from([1, 2]))); } /// x = 1 if flag else 2; y = x + 1 #[test] fn union_add() { let db = salsa::DatabaseImpl::new(); let x1 = Definition::new(&db, None, 1); let x2 = Definition::new(&db, None, 2); let x_use = Use::new(&db, vec![x1, x2]); let y_def = Definition::new(&db, Some(x_use), 1); let y_use = Use::new(&db, vec![y_def]); let ty = infer_use(&db, y_use); assert_eq!(ty, Type::Values(Box::from([2, 3]))); } /// x = 1; loop { x = x + 0 } #[test] fn cycle_converges_then_diverges() { let mut db = salsa::DatabaseImpl::new(); let def1 = Definition::new(&db, None, 1); let def2 = Definition::new(&db, None, 0); let u = Use::new(&db, vec![def1, def2]); def2.set_base(&mut db).to(Some(u)); let ty = infer_use(&db, u); // Loop converges on 1 assert_eq!(ty, Type::Values(Box::from([1]))); // Set the increment on x from 0 to 1 let new_increment = 1; def2.set_increment(&mut db).to(new_increment); // Now the loop diverges and we fall back to Top assert_eq!(infer_use(&db, u), Type::Top); } /// x = 1; loop { x = x + 1 } #[test] fn cycle_diverges_then_converges() { let mut db = salsa::DatabaseImpl::new(); let def1 = Definition::new(&db, None, 1); let def2 = Definition::new(&db, None, 1); let u = Use::new(&db, vec![def1, def2]); def2.set_base(&mut db).to(Some(u)); let ty = infer_use(&db, u); // Loop diverges. Cut it off and fallback to Type::Top assert_eq!(ty, Type::Top); // Set the increment from 1 to 0. def2.set_increment(&mut db).to(0); // Now the loop converges on 1. assert_eq!(infer_use(&db, u), Type::Values(Box::from([1]))); } /// x = 0; y = 0; loop { x = y + 0; y = x + 0 } #[test_log::test] fn multi_symbol_cycle_converges_then_diverges() { let mut db = salsa::DatabaseImpl::new(); let defx0 = Definition::new(&db, None, 0); let defy0 = Definition::new(&db, None, 0); let defx1 = Definition::new(&db, None, 0); let defy1 = Definition::new(&db, None, 0); let use_x = Use::new(&db, vec![defx0, defx1]); let use_y = Use::new(&db, vec![defy0, defy1]); defx1.set_base(&mut db).to(Some(use_y)); defy1.set_base(&mut db).to(Some(use_x)); // Both symbols converge on 0 assert_eq!(infer_use(&db, use_x), Type::Values(Box::from([0]))); assert_eq!(infer_use(&db, use_y), Type::Values(Box::from([0]))); // Set the increment on x to 0. defx1.set_increment(&mut db).to(0); // Both symbols still converge on 0. assert_eq!(infer_use(&db, use_x), Type::Values(Box::from([0]))); assert_eq!(infer_use(&db, use_y), Type::Values(Box::from([0]))); // Set the increment on x from 0 to 1. defx1.set_increment(&mut db).to(1); // Now the loop diverges and we fall back to Top. assert_eq!(infer_use(&db, use_x), Type::Top); assert_eq!(infer_use(&db, use_y), Type::Top); } salsa-0.26.2/tests/debug.rs000064400000000000000000000106241046102023000136170ustar 00000000000000#![cfg(feature = "inventory")] //! Test that `DeriveWithDb` is correctly derived. use expect_test::expect; use salsa::{Database, Setter}; #[salsa::input(debug)] struct MyInput { field: u32, } #[derive(Debug, Eq, PartialEq, Clone)] struct NotSalsa { field: String, } #[salsa::input(debug)] struct ComplexStruct { my_input: MyInput, not_salsa: NotSalsa, } #[test] fn input() { salsa::DatabaseImpl::new().attach(|db| { let input = MyInput::new(db, 22); let not_salsa = NotSalsa { field: "it's salsa time".to_string(), }; let complex_struct = ComplexStruct::new(db, input, not_salsa); // debug includes all fields let actual = format!("{complex_struct:?}"); let expected = expect![[r#"ComplexStruct { [salsa id]: Id(400), my_input: MyInput { [salsa id]: Id(0), field: 22 }, not_salsa: NotSalsa { field: "it's salsa time" } }"#]]; expected.assert_eq(&actual); }) } #[salsa::tracked] fn leak_debug_string(_db: &dyn salsa::Database, input: MyInput) -> String { format!("{input:?}") } /// Test that field reads that occur as part of `Debug` are not tracked. /// Intentionally leaks the debug string. /// Don't try this at home, kids. #[test] fn untracked_dependencies() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 22); let s = leak_debug_string(&db, input); expect![[r#" "MyInput { [salsa id]: Id(0), field: 22 }" "#]] .assert_debug_eq(&s); input.set_field(&mut db).to(23); // check that we reuse the cached result for debug string // even though the dependency changed. let s = leak_debug_string(&db, input); assert!(s.contains(", field: 22 }")); } #[salsa::tracked] fn dep_a(db: &dyn salsa::Database, input: MyInput) -> u32 { input.field(db) } #[salsa::tracked] fn dep_b(db: &dyn salsa::Database, input: MyInput) -> u32 { input.field(db) } #[salsa::tracked] fn debug_branch_query(db: &dyn salsa::Database, selector: MyInput, a: MyInput, b: MyInput) -> u32 { // `Debug` for salsa structs is explicitly untracked; branching on it is unsound. // This test demonstrates it can break the backdate invariant. let s = format!("{selector:?}"); if s.contains("field: 0") { dep_a(db, a) } else { dep_b(db, b) } } /// Backdating warns about branching on the output of a Salsa struct's derived `Debug` output, /// because it doesn't track its reads (can lead to stale results). #[test] #[cfg_attr(debug_assertions, should_panic(expected = "returned the same value"))] fn debug_branch_can_trip_backdate_assertion() { let mut db = salsa::DatabaseImpl::new(); let selector = MyInput::new(&db, 0); let a = MyInput::new(&db, 0); let b = MyInput::new(&db, 0); // R1: depends on `a`, returns 0 assert_eq!(debug_branch_query(&db, selector, a, b), 0); // R2: force `debug_branch_query` to change (0 -> 1) so its memo's changed_at advances. a.set_field(&mut db).to(1); assert_eq!(debug_branch_query(&db, selector, a, b), 1); // R3: change back to 0; memo value is 0 but changed_at is now "recent". a.set_field(&mut db).to(0); assert_eq!(debug_branch_query(&db, selector, a, b), 0); // R4/R5: change `selector` (untracked) so the query switches to `b`, and change `a` // to force re-execution. New execution returns 0 (equal) but depends only on older `b`, // so `new.changed_at < old.changed_at` and backdating asserts. selector.set_field(&mut db).to(1); a.set_field(&mut db).to(1); let _ = debug_branch_query(&db, selector, a, b); } #[salsa::tracked] struct DerivedCustom<'db> { my_input: MyInput, value: u32, } impl std::fmt::Debug for DerivedCustom<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { salsa::with_attached_database(|db| { write!(f, "{:?} / {:?}", self.my_input(db), self.value(db)) }) .unwrap_or_else(|| f.debug_tuple("DerivedCustom").finish()) } } #[salsa::tracked] fn leak_derived_custom(db: &dyn salsa::Database, input: MyInput, value: u32) -> String { let c = DerivedCustom::new(db, input, value); format!("{c:?}") } #[test] fn custom_debug_impl() { let db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 22); let s = leak_derived_custom(&db, input, 23); expect![[r#" "MyInput { [salsa id]: Id(0), field: 22 } / 23" "#]] .assert_debug_eq(&s); } salsa-0.26.2/tests/debug_bounds.rs000064400000000000000000000015451046102023000151730ustar 00000000000000#![cfg(feature = "inventory")] //! Test that debug and non-debug structs compile correctly #[derive(Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash)] struct NotDebug; #[derive(Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Hash, Debug)] struct Debug; #[salsa::input(debug)] struct DebugInput { field: Debug, } #[salsa::input] struct NotDebugInput { field: NotDebug, } #[salsa::interned(debug)] struct DebugInterned { field: Debug, } #[salsa::interned] struct NotDebugInterned { field: NotDebug, } #[salsa::interned(no_lifetime, debug)] struct DebugInternedNoLifetime { field: Debug, } #[salsa::interned(no_lifetime)] struct NotDebugInternedNoLifetime { field: NotDebug, } #[salsa::tracked(debug)] struct DebugTracked<'db> { field: Debug, } #[salsa::tracked] struct NotDebugTracked<'db> { field: NotDebug, } #[test] fn ok() {} salsa-0.26.2/tests/debug_db_contents.rs000064400000000000000000000032731046102023000162030ustar 00000000000000#![cfg(feature = "inventory")] #[salsa::interned(debug)] struct InternedStruct<'db> { name: String, } #[salsa::input(debug)] struct InputStruct { field: u32, } #[salsa::tracked(debug)] struct TrackedStruct<'db> { field: u32, } #[salsa::tracked] fn tracked_fn(db: &dyn salsa::Database, input: InputStruct) -> TrackedStruct<'_> { TrackedStruct::new(db, input.field(db) * 2) } #[test] fn execute() { use salsa::plumbing::ZalsaDatabase; let db = salsa::DatabaseImpl::new(); let interned1 = InternedStruct::new(&db, "Salsa".to_string()); let interned2 = InternedStruct::new(&db, "Salsa2".to_string()); // test interned structs let interned = InternedStruct::ingredient(db.zalsa()) .entries(db.zalsa()) .collect::>(); assert_eq!(interned.len(), 2); assert_eq!(interned[0].as_struct(), interned1); assert_eq!(interned[1].as_struct(), interned2); assert_eq!(interned[0].value().fields().0, "Salsa"); assert_eq!(interned[1].value().fields().0, "Salsa2"); // test input structs let input1 = InputStruct::new(&db, 22); let inputs = InputStruct::ingredient(&db) .entries(db.zalsa()) .collect::>(); assert_eq!(inputs.len(), 1); assert_eq!(inputs[0].as_struct(), input1); assert_eq!(inputs[0].value().fields().0, 22); // test tracked structs let tracked1 = tracked_fn(&db, input1); assert_eq!(tracked1.field(&db), 44); let tracked = TrackedStruct::ingredient(&db) .entries(db.zalsa()) .collect::>(); assert_eq!(tracked.len(), 1); assert_eq!(tracked[0].as_struct(), tracked1); assert_eq!(tracked[0].value().fields().0, tracked1.field(&db)); } salsa-0.26.2/tests/deletion-cascade.rs000064400000000000000000000053251046102023000157170ustar 00000000000000#![cfg(feature = "inventory")] //! Delete cascade: //! //! * when we delete memoized data, also delete outputs from that data mod common; use common::LogDatabase; use expect_test::expect; use salsa::Setter; use test_log::test; #[salsa::input(singleton, debug)] struct MyInput { field: u32, } #[salsa::tracked] fn final_result(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("final_result({input:?})")); let mut sum = 0; for tracked_struct in create_tracked_structs(db, input) { sum += contribution_from_struct(db, tracked_struct); } sum } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn create_tracked_structs(db: &dyn LogDatabase, input: MyInput) -> Vec> { db.push_log(format!("intermediate_result({input:?})")); (0..input.field(db)) .map(|i| MyTracked::new(db, i)) .collect() } #[salsa::tracked] fn contribution_from_struct<'db>(db: &'db dyn LogDatabase, tracked: MyTracked<'db>) -> u32 { let m = MyTracked::new(db, tracked.field(db)); copy_field(db, m) * 2 } #[salsa::tracked] fn copy_field<'db>(db: &'db dyn LogDatabase, tracked: MyTracked<'db>) -> u32 { tracked.field(db) } #[test] fn basic() { let mut db = common::DiscardLoggerDatabase::default(); // Creates 3 tracked structs let input = MyInput::new(&db, 3); assert_eq!(final_result(&db, input), 2 * 2 + 2); db.assert_logs(expect![[r#" [ "final_result(MyInput { [salsa id]: Id(0), field: 3 })", "intermediate_result(MyInput { [salsa id]: Id(0), field: 3 })", ]"#]]); // Creates only 2 tracked structs in this revision, should delete 1 // // Expect to see 6 DidDiscard events. Three from the primary struct: // // * the struct itself // * the struct's field // * the `contribution_from_struct` result // // and then 3 more from the struct created by `contribution_from_struct`: // // * the struct itself // * the struct's field // * the `copy_field` result input.set_field(&mut db).to(2); assert_eq!(final_result(&db, input), 2); db.assert_logs(expect![[r#" [ "intermediate_result(MyInput { [salsa id]: Id(0), field: 2 })", "salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(Id(0)), output_key: MyTracked(Id(402)) })", "salsa_event(DidDiscard { key: MyTracked(Id(402)) })", "salsa_event(DidDiscard { key: contribution_from_struct(Id(402)) })", "salsa_event(DidDiscard { key: MyTracked(Id(405)) })", "salsa_event(DidDiscard { key: copy_field(Id(405)) })", "final_result(MyInput { [salsa id]: Id(0), field: 2 })", ]"#]]); } salsa-0.26.2/tests/deletion-drops.rs000064400000000000000000000042031046102023000154550ustar 00000000000000#![cfg(feature = "inventory")] //! Basic deletion test: //! //! * entities not created in a revision are deleted, as is any memoized data keyed on them. mod common; use salsa::{Database, Setter}; use test_log::test; #[salsa::input] struct MyInput { identity: u32, } #[salsa::tracked] struct MyTracked<'db> { identifier: u32, #[tracked] #[returns(ref)] field: Bomb, } thread_local! { static DROPPED: std::cell::RefCell> = const { std::cell::RefCell::new(vec![]) }; } fn dropped() -> Vec { DROPPED.with(|d| d.borrow().clone()) } #[derive(Clone, Debug, PartialEq, Eq)] struct Bomb { identity: u32, } impl Drop for Bomb { fn drop(&mut self) { DROPPED.with(|d| d.borrow_mut().push(self.identity)); } } #[salsa::tracked] impl MyInput { #[salsa::tracked] fn create_tracked_struct(self, db: &dyn Database) -> MyTracked<'_> { MyTracked::new( db, self.identity(db), Bomb { identity: self.identity(db), }, ) } } #[test] fn deletion_drops() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 22); expect_test::expect![[r#" [] "#]] .assert_debug_eq(&dropped()); let tracked_struct = input.create_tracked_struct(&db); assert_eq!(tracked_struct.field(&db).identity, 22); expect_test::expect![[r#" [] "#]] .assert_debug_eq(&dropped()); input.set_identity(&mut db).to(44); expect_test::expect![[r#" [] "#]] .assert_debug_eq(&dropped()); // Now that we execute with rev = 44, the old id is put on the free list let tracked_struct = input.create_tracked_struct(&db); assert_eq!(tracked_struct.field(&db).identity, 44); expect_test::expect![[r#" [] "#]] .assert_debug_eq(&dropped()); // When we execute again with `input1`, that id is re-used, so the old value is deleted let input1 = MyInput::new(&db, 66); let _tracked_struct1 = input1.create_tracked_struct(&db); expect_test::expect![[r#" [ 22, ] "#]] .assert_debug_eq(&dropped()); } salsa-0.26.2/tests/deletion.rs000064400000000000000000000043361046102023000143370ustar 00000000000000#![cfg(feature = "inventory")] //! Basic deletion test: //! //! * entities not created in a revision are deleted, as is any memoized data keyed on them. mod common; use common::LogDatabase; use expect_test::expect; use salsa::Setter; use test_log::test; #[salsa::input(debug)] struct MyInput { field: u32, } #[salsa::tracked] fn final_result(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("final_result({input:?})")); let mut sum = 0; for tracked_struct in create_tracked_structs(db, input) { sum += contribution_from_struct(db, tracked_struct); } sum } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn create_tracked_structs(db: &dyn LogDatabase, input: MyInput) -> Vec> { db.push_log(format!("intermediate_result({input:?})")); (0..input.field(db)) .map(|i| MyTracked::new(db, i)) .collect() } #[salsa::tracked] fn contribution_from_struct<'db>(db: &'db dyn LogDatabase, tracked: MyTracked<'db>) -> u32 { tracked.field(db) * 2 } #[test] fn basic() { let mut db = common::DiscardLoggerDatabase::default(); // Creates 3 tracked structs let input = MyInput::new(&db, 3); assert_eq!(final_result(&db, input), 2 * 2 + 2); db.assert_logs(expect![[r#" [ "final_result(MyInput { [salsa id]: Id(0), field: 3 })", "intermediate_result(MyInput { [salsa id]: Id(0), field: 3 })", ]"#]]); // Creates only 2 tracked structs in this revision, should delete 1 // // Expect to see 3 DidDiscard events-- // // * the struct itself // * the struct's field // * the `contribution_from_struct` result input.set_field(&mut db).to(2); assert_eq!(final_result(&db, input), 2); db.assert_logs(expect![[r#" [ "intermediate_result(MyInput { [salsa id]: Id(0), field: 2 })", "salsa_event(WillDiscardStaleOutput { execute_key: create_tracked_structs(Id(0)), output_key: MyTracked(Id(402)) })", "salsa_event(DidDiscard { key: MyTracked(Id(402)) })", "salsa_event(DidDiscard { key: contribution_from_struct(Id(402)) })", "final_result(MyInput { [salsa id]: Id(0), field: 2 })", ]"#]]); } salsa-0.26.2/tests/derive_update.rs000064400000000000000000000031371046102023000153520ustar 00000000000000#![cfg(feature = "inventory")] //! Test that the `Update` derive works as expected #[derive(salsa::Update)] struct MyInput { field: &'static str, } #[derive(salsa::Update)] struct MyInput2 { #[update(unsafe(with(custom_update)))] field: &'static str, #[update(unsafe(with(|dest, data| { *dest = data; true })))] field2: &'static str, } unsafe fn custom_update(dest: *mut &'static str, _data: &'static str) -> bool { unsafe { *dest = "ill-behaved for testing purposes" }; true } #[test] fn derived() { let mut m = MyInput { field: "foo" }; assert_eq!(m.field, "foo"); assert!(unsafe { salsa::Update::maybe_update(&mut m, MyInput { field: "bar" }) }); assert_eq!(m.field, "bar"); assert!(!unsafe { salsa::Update::maybe_update(&mut m, MyInput { field: "bar" }) }); assert_eq!(m.field, "bar"); } #[test] fn derived_with() { let mut m = MyInput2 { field: "foo", field2: "foo", }; assert_eq!(m.field, "foo"); assert_eq!(m.field2, "foo"); assert!(unsafe { salsa::Update::maybe_update( &mut m, MyInput2 { field: "bar", field2: "bar", }, ) }); assert_eq!(m.field, "ill-behaved for testing purposes"); assert_eq!(m.field2, "bar"); assert!(unsafe { salsa::Update::maybe_update( &mut m, MyInput2 { field: "ill-behaved for testing purposes", field2: "foo", }, ) }); assert_eq!(m.field, "ill-behaved for testing purposes"); assert_eq!(m.field2, "foo"); } salsa-0.26.2/tests/durability.rs000064400000000000000000000024751046102023000147060ustar 00000000000000#![cfg(feature = "inventory")] //! Tests that code using the builder's durability methods compiles. use salsa::{Database, Durability, Setter}; use test_log::test; #[salsa::input] struct N { value: u32, } #[salsa::tracked] fn add3(db: &dyn Database, a: N, b: N, c: N) -> u32 { add(db, a, b) + c.value(db) } #[salsa::tracked] fn add(db: &dyn Database, a: N, b: N) -> u32 { a.value(db) + b.value(db) } #[test] fn durable_to_less_durable() { let mut db = salsa::DatabaseImpl::new(); let a = N::builder(11).value_durability(Durability::HIGH).new(&db); let b = N::builder(22).value_durability(Durability::HIGH).new(&db); let c = N::builder(33).value_durability(Durability::HIGH).new(&db); // Here, `add3` invokes `add(a, b)`, which yields 33. assert_eq!(add3(&db, a, b, c), 66); a.set_value(&mut db).with_durability(Durability::LOW).to(11); // Here, `add3` invokes `add`, which *still* yields 33, but which // is no longer of high durability. Since value didn't change, we might // preserve `add3` unchanged, not noticing that it is no longer // of high durability. assert_eq!(add3(&db, a, b, c), 66); // In that case, we would not get the correct result here, when // 'a' changes *again*. a.set_value(&mut db).to(22); assert_eq!(add3(&db, a, b, c), 77); } salsa-0.26.2/tests/elided-lifetime-in-tracked-fn.rs000064400000000000000000000032521046102023000201720ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. mod common; use common::LogDatabase; use expect_test::expect; use salsa::Setter; use test_log::test; #[salsa::input(debug)] struct MyInput { field: u32, } #[salsa::tracked] fn final_result(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("final_result({input:?})")); intermediate_result(db, input).field(db) * 2 } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn intermediate_result(db: &dyn LogDatabase, input: MyInput) -> MyTracked<'_> { db.push_log(format!("intermediate_result({input:?})")); MyTracked::new(db, input.field(db) / 2) } #[test] fn execute() { let mut db = common::LoggerDatabase::default(); let input = MyInput::new(&db, 22); assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ "final_result(MyInput { [salsa id]: Id(0), field: 22 })", "intermediate_result(MyInput { [salsa id]: Id(0), field: 22 })", ]"#]]); // Intermediate result is the same, so final result does // not need to be recomputed: input.set_field(&mut db).to(23); assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ "intermediate_result(MyInput { [salsa id]: Id(0), field: 23 })", ]"#]]); input.set_field(&mut db).to(24); assert_eq!(final_result(&db, input), 24); db.assert_logs(expect![[r#" [ "intermediate_result(MyInput { [salsa id]: Id(0), field: 24 })", "final_result(MyInput { [salsa id]: Id(0), field: 24 })", ]"#]]); } salsa-0.26.2/tests/expect_reuse_field_x_of_a_tracked_struct_changes_but_fn_depends_on_field_y.rs000064400000000000000000000046321046102023000317230ustar 00000000000000#![cfg(feature = "inventory")] //! Test that if field X of a tracked struct changes but not field Y, //! functions that depend on X re-execute, but those depending only on Y do not //! compiles and executes successfully. #![allow(dead_code)] mod common; use common::LogDatabase; use expect_test::expect; use salsa::Setter; #[salsa::input(debug)] struct MyInput { field: u32, } #[salsa::tracked] fn final_result_depends_on_x(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("final_result_depends_on_x({input:?})")); intermediate_result(db, input).x(db) * 2 } #[salsa::tracked] fn final_result_depends_on_y(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("final_result_depends_on_y({input:?})")); intermediate_result(db, input).y(db) * 2 } #[salsa::tracked] struct MyTracked<'db> { #[tracked] x: u32, #[tracked] y: u32, } #[salsa::tracked] fn intermediate_result(db: &dyn LogDatabase, input: MyInput) -> MyTracked<'_> { MyTracked::new(db, input.field(db).div_ceil(2), input.field(db) / 2) } #[test] fn execute() { // x = (input.field + 1) / 2 // y = input.field / 2 // final_result_depends_on_x = x * 2 = (input.field + 1) / 2 * 2 // final_result_depends_on_y = y * 2 = input.field / 2 * 2 let mut db = common::LoggerDatabase::default(); // intermediate results: // x = (22 + 1) / 2 = 11 // y = 22 / 2 = 11 let input = MyInput::new(&db, 22); assert_eq!(final_result_depends_on_x(&db, input), 22); db.assert_logs(expect![[r#" [ "final_result_depends_on_x(MyInput { [salsa id]: Id(0), field: 22 })", ]"#]]); assert_eq!(final_result_depends_on_y(&db, input), 22); db.assert_logs(expect![[r#" [ "final_result_depends_on_y(MyInput { [salsa id]: Id(0), field: 22 })", ]"#]]); input.set_field(&mut db).to(23); // x = (23 + 1) / 2 = 12 // Intermediate result x changes, so final result depends on x // needs to be recomputed; assert_eq!(final_result_depends_on_x(&db, input), 24); db.assert_logs(expect![[r#" [ "final_result_depends_on_x(MyInput { [salsa id]: Id(0), field: 23 })", ]"#]]); // y = 23 / 2 = 11 // Intermediate result y is the same, so final result depends on y // does not need to be recomputed; assert_eq!(final_result_depends_on_y(&db, input), 22); db.assert_logs(expect!["[]"]); } salsa-0.26.2/tests/expect_reuse_field_x_of_an_input_changes_but_fn_depends_on_field_y.rs000064400000000000000000000033601046102023000302140ustar 00000000000000#![cfg(feature = "inventory")] //! Test that if field X of an input changes but not field Y, //! functions that depend on X re-execute, but those depending only on Y do not //! compiles and executes successfully. #![allow(dead_code)] mod common; use common::LogDatabase; use expect_test::expect; use salsa::Setter; #[salsa::input(debug)] struct MyInput { x: u32, y: u32, } #[salsa::tracked] fn result_depends_on_x(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("result_depends_on_x({input:?})")); input.x(db) + 1 } #[salsa::tracked] fn result_depends_on_y(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("result_depends_on_y({input:?})")); input.y(db) - 1 } #[test] fn execute() { // result_depends_on_x = x + 1 // result_depends_on_y = y - 1 let mut db = common::LoggerDatabase::default(); let input = MyInput::new(&db, 22, 33); assert_eq!(result_depends_on_x(&db, input), 23); db.assert_logs(expect![[r#" [ "result_depends_on_x(MyInput { [salsa id]: Id(0), x: 22, y: 33 })", ]"#]]); assert_eq!(result_depends_on_y(&db, input), 32); db.assert_logs(expect![[r#" [ "result_depends_on_y(MyInput { [salsa id]: Id(0), x: 22, y: 33 })", ]"#]]); input.set_x(&mut db).to(23); // input x changes, so result depends on x needs to be recomputed; assert_eq!(result_depends_on_x(&db, input), 24); db.assert_logs(expect![[r#" [ "result_depends_on_x(MyInput { [salsa id]: Id(0), x: 23, y: 33 })", ]"#]]); // input y is the same, so result depends on y // does not need to be recomputed; assert_eq!(result_depends_on_y(&db, input), 32); db.assert_logs(expect!["[]"]); } salsa-0.26.2/tests/hash_collision.rs000064400000000000000000000011731046102023000155260ustar 00000000000000#![cfg(feature = "inventory")] use std::hash::Hash; #[test] fn hello() { use salsa::{Database, DatabaseImpl, Setter}; #[salsa::input] struct Bool { value: bool, } #[salsa::tracked] struct True<'db> {} #[salsa::tracked] struct False<'db> {} #[salsa::tracked] fn hello(db: &dyn Database, bool: Bool) { if bool.value(db) { True::new(db); } else { False::new(db); } } let mut db = DatabaseImpl::new(); let input = Bool::new(&db, false); hello(&db, input); input.set_value(&mut db).to(true); hello(&db, input); } salsa-0.26.2/tests/hello_world.rs000064400000000000000000000047661046102023000150550ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. mod common; use common::LogDatabase; use expect_test::expect; use salsa::Setter; use test_log::test; #[salsa::input(debug)] struct MyInput { field: u32, } #[salsa::tracked] fn final_result(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("final_result({input:?})")); intermediate_result(db, input).field(db) * 2 } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn intermediate_result(db: &dyn LogDatabase, input: MyInput) -> MyTracked<'_> { db.push_log(format!("intermediate_result({input:?})")); MyTracked::new(db, input.field(db) / 2) } #[test] fn execute() { let mut db = common::LoggerDatabase::default(); let input = MyInput::new(&db, 22); assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ "final_result(MyInput { [salsa id]: Id(0), field: 22 })", "intermediate_result(MyInput { [salsa id]: Id(0), field: 22 })", ]"#]]); // Intermediate result is the same, so final result does // not need to be recomputed: input.set_field(&mut db).to(23); assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ "intermediate_result(MyInput { [salsa id]: Id(0), field: 23 })", ]"#]]); input.set_field(&mut db).to(24); assert_eq!(final_result(&db, input), 24); db.assert_logs(expect![[r#" [ "intermediate_result(MyInput { [salsa id]: Id(0), field: 24 })", "final_result(MyInput { [salsa id]: Id(0), field: 24 })", ]"#]]); } /// Create and mutate a distinct input. No re-execution required. #[test] fn red_herring() { let mut db = common::LoggerDatabase::default(); let input = MyInput::new(&db, 22); assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ "final_result(MyInput { [salsa id]: Id(0), field: 22 })", "intermediate_result(MyInput { [salsa id]: Id(0), field: 22 })", ]"#]]); // Create a distinct input and mutate it. // This will trigger a new revision in the database // but shouldn't actually invalidate our existing ones. let input2 = MyInput::new(&db, 44); input2.set_field(&mut db).to(66); // Re-run the query on the original input. Nothing re-executes! assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" []"#]]); } salsa-0.26.2/tests/input_default.rs000064400000000000000000000016641046102023000154000ustar 00000000000000#![cfg(feature = "inventory")] //! Tests that fields attributed with `#[default]` are initialized with `Default::default()`. use salsa::Durability; use test_log::test; #[salsa::input] struct MyInput { required: bool, #[default] optional: usize, } #[test] fn new_constructor() { let db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, true); assert!(input.required(&db)); assert_eq!(input.optional(&db), 0); } #[test] fn builder_specify_optional() { let db = salsa::DatabaseImpl::new(); let input = MyInput::builder(true).optional(20).new(&db); assert!(input.required(&db)); assert_eq!(input.optional(&db), 20); } #[test] fn builder_default_optional_value() { let db = salsa::DatabaseImpl::new(); let input = MyInput::builder(true) .required_durability(Durability::HIGH) .new(&db); assert!(input.required(&db)); assert_eq!(input.optional(&db), 0); } salsa-0.26.2/tests/input_field_durability.rs000064400000000000000000000015171046102023000172640ustar 00000000000000#![cfg(feature = "inventory")] //! Tests that code using the builder's durability methods compiles. use salsa::Durability; use test_log::test; #[salsa::input] struct MyInput { required_field: bool, #[default] optional_field: usize, } #[test] fn required_field_durability() { let db = salsa::DatabaseImpl::new(); let input = MyInput::builder(true) .required_field_durability(Durability::HIGH) .new(&db); assert!(input.required_field(&db)); assert_eq!(input.optional_field(&db), 0); } #[test] fn optional_field_durability() { let db = salsa::DatabaseImpl::new(); let input = MyInput::builder(true) .optional_field(20) .optional_field_durability(Durability::HIGH) .new(&db); assert!(input.required_field(&db)); assert_eq!(input.optional_field(&db), 20); } salsa-0.26.2/tests/input_setter_preserves_durability.rs000064400000000000000000000015251046102023000216040ustar 00000000000000#![cfg(feature = "inventory")] use salsa::plumbing::ZalsaDatabase; use salsa::{Durability, Setter}; use test_log::test; #[salsa::input] struct MyInput { required_field: bool, #[default] optional_field: usize, } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::builder(true) .required_field_durability(Durability::HIGH) .new(&db); // Change the field value. It should preserve high durability. input.set_required_field(&mut db).to(false); let last_high_revision = db.zalsa().last_changed_revision(Durability::HIGH); // Changing the value again should **again** dump the high durability revision. input.set_required_field(&mut db).to(false); assert_ne!( db.zalsa().last_changed_revision(Durability::HIGH), last_high_revision ); } salsa-0.26.2/tests/intern_access_in_different_revision.rs000064400000000000000000000012211046102023000217740ustar 00000000000000#![cfg(feature = "inventory")] use salsa::{Durability, Setter}; #[salsa::interned(no_lifetime)] struct Interned { field: u32, } #[salsa::input] struct Input { field: i32, } #[test] fn the_test() { let mut db = salsa::DatabaseImpl::default(); let input = Input::builder(-123456) .field_durability(Durability::HIGH) .new(&db); // Create an intern in an early revision. let interned = Interned::new(&db, 0xDEADBEEF); // Trigger a new revision. input .set_field(&mut db) .with_durability(Durability::HIGH) .to(123456); // Read the interned value let _ = interned.field(&db); } salsa-0.26.2/tests/interned-revisions.rs000064400000000000000000000366321046102023000163670ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. mod common; use common::LogDatabase; use expect_test::expect; use salsa::{Database, Durability, Setter}; use test_log::test; #[salsa::input] struct Input { field1: usize, } #[salsa::interned(revisions = 3)] #[derive(Debug)] struct Interned<'db> { field1: BadHash, } // Use a consistent hash value to ensure that interned value sharding // does not interefere with garbage collection. #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] struct BadHash(usize); impl std::hash::Hash for BadHash { fn hash(&self, state: &mut H) { state.write_i16(0); } } #[salsa::interned] #[derive(Debug)] struct NestedInterned<'db> { interned: Interned<'db>, } #[test] fn test_intern_new() { #[salsa::tracked] fn function(db: &dyn Database, input: Input) -> Interned<'_> { Interned::new(db, BadHash(input.field1(db))) } let mut db = common::EventLoggerDatabase::default(); let input = Input::new(&db, 0); let result_in_rev_1 = function(&db, input); assert_eq!(result_in_rev_1.field1(&db).0, 0); // Modify the input to force a new value to be created. input.set_field1(&mut db).to(1); let result_in_rev_2 = function(&db, input); assert_eq!(result_in_rev_2.field1(&db).0, 1); db.assert_logs(expect![[r#" [ "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidInternValue { key: Interned(Id(400)), revision: R1 }", "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidInternValue { key: Interned(Id(401)), revision: R2 }", ]"#]]); } #[test] fn test_reintern() { #[salsa::tracked] fn function(db: &dyn Database, input: Input) -> Interned<'_> { let _ = input.field1(db); Interned::new(db, BadHash(0)) } let mut db = common::EventLoggerDatabase::default(); let input = Input::new(&db, 0); let result_in_rev_1 = function(&db, input); db.assert_logs(expect![[r#" [ "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidInternValue { key: Interned(Id(400)), revision: R1 }", ]"#]]); assert_eq!(result_in_rev_1.field1(&db).0, 0); // Modify the input to force the value to be re-interned. input.set_field1(&mut db).to(1); let result_in_rev_2 = function(&db, input); db.assert_logs(expect![[r#" [ "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidValidateInternedValue { key: Interned(Id(400)), revision: R2 }", ]"#]]); assert_eq!(result_in_rev_2.field1(&db).0, 0); } #[test] fn test_durability() { #[salsa::tracked] fn function(db: &dyn Database, _input: Input) -> Interned<'_> { Interned::new(db, BadHash(0)) } let mut db = common::EventLoggerDatabase::default(); let input = Input::new(&db, 0); let result_in_rev_1 = function(&db, input); assert_eq!(result_in_rev_1.field1(&db).0, 0); // Modify the input to bump the revision without re-interning the value, as there // is no read dependency. input.set_field1(&mut db).to(1); let result_in_rev_2 = function(&db, input); assert_eq!(result_in_rev_2.field1(&db).0, 0); db.assert_logs(expect![[r#" [ "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidInternValue { key: Interned(Id(400)), revision: R1 }", "DidSetCancellationFlag", "WillCheckCancellation", "DidValidateMemoizedValue { database_key: function(Id(0)) }", ]"#]]); } #[salsa::interned(revisions = usize::MAX)] #[derive(Debug)] struct Immortal<'db> { field1: BadHash, } #[test] fn test_immortal() { #[salsa::tracked] fn function(db: &dyn Database, input: Input) -> Immortal<'_> { Immortal::new(db, BadHash(input.field1(db))) } let mut db = common::EventLoggerDatabase::default(); let input = Input::new(&db, 0); let result = function(&db, input); assert_eq!(result.field1(&db).0, 0); // Modify the input to bump the revision and intern a new value. // // No values should ever be reused with `revisions = usize::MAX`. for i in 1..if cfg!(miri) { 50 } else { 1000 } { input.set_field1(&mut db).to(i); let result = function(&db, input); assert_eq!(result.field1(&db).0, i); assert_eq!(salsa::plumbing::AsId::as_id(&result).generation(), 0); } } #[test] fn test_reuse() { #[salsa::tracked] fn function(db: &dyn Database, input: Input) -> Interned<'_> { Interned::new(db, BadHash(input.field1(db))) } let mut db = common::EventLoggerDatabase::default(); let input = Input::new(&db, 0); let result = function(&db, input); assert_eq!(result.field1(&db).0, 0); // Modify the input to bump the revision and intern a new value. // // The slot will not be reused for the first few revisions, but after // that we should not allocate any more slots. for i in 1..10 { input.set_field1(&mut db).to(i); let result = function(&db, input); assert_eq!(result.field1(&db).0, i); } // Values that have been reused should be re-interned. for i in 1..10 { let result = function(&db, Input::new(&db, i)); assert_eq!(result.field1(&db).0, i); } db.assert_logs(expect![[r#" [ "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidInternValue { key: Interned(Id(400)), revision: R1 }", "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidInternValue { key: Interned(Id(401)), revision: R2 }", "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidInternValue { key: Interned(Id(402)), revision: R3 }", "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidReuseInternedValue { key: Interned(Id(400g1)), revision: R4 }", "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidReuseInternedValue { key: Interned(Id(401g1)), revision: R5 }", "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidReuseInternedValue { key: Interned(Id(402g1)), revision: R6 }", "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidReuseInternedValue { key: Interned(Id(400g2)), revision: R7 }", "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidReuseInternedValue { key: Interned(Id(401g2)), revision: R8 }", "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidReuseInternedValue { key: Interned(Id(402g2)), revision: R9 }", "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidReuseInternedValue { key: Interned(Id(400g3)), revision: R10 }", "WillCheckCancellation", "WillExecute { database_key: function(Id(1)) }", "DidInternValue { key: Interned(Id(403)), revision: R10 }", "WillCheckCancellation", "WillExecute { database_key: function(Id(2)) }", "DidInternValue { key: Interned(Id(404)), revision: R10 }", "WillCheckCancellation", "WillExecute { database_key: function(Id(3)) }", "DidInternValue { key: Interned(Id(405)), revision: R10 }", "WillCheckCancellation", "WillExecute { database_key: function(Id(4)) }", "DidInternValue { key: Interned(Id(406)), revision: R10 }", "WillCheckCancellation", "WillExecute { database_key: function(Id(5)) }", "DidInternValue { key: Interned(Id(407)), revision: R10 }", "WillCheckCancellation", "WillExecute { database_key: function(Id(6)) }", "DidInternValue { key: Interned(Id(408)), revision: R10 }", "WillCheckCancellation", "WillExecute { database_key: function(Id(7)) }", "DidValidateInternedValue { key: Interned(Id(401g2)), revision: R10 }", "WillCheckCancellation", "WillExecute { database_key: function(Id(8)) }", "DidValidateInternedValue { key: Interned(Id(402g2)), revision: R10 }", "WillCheckCancellation", "WillExecute { database_key: function(Id(9)) }", ]"#]]); } #[test] fn test_reuse_indirect() { #[salsa::tracked] fn intern(db: &dyn Database, input: Input, value: usize) -> Interned<'_> { intern_inner(db, input, value) } #[salsa::tracked] fn intern_inner(db: &dyn Database, input: Input, value: usize) -> Interned<'_> { let _i = input.field1(db); // Only low durability interned values are garbage collected. Interned::new(db, BadHash(value)) } let mut db = common::EventLoggerDatabase::default(); let input = Input::builder(0).durability(Durability::LOW).new(&db); // Intern `i0`. let i0 = intern(&db, input, 0); let i0_id = salsa::plumbing::AsId::as_id(&i0); assert_eq!(i0.field1(&db).0, 0); // Get the garbage collector to consider `i0` stale. for x in 1.. { db.synthetic_write(Durability::LOW); let ix = intern(&db, input, x); let ix_id = salsa::plumbing::AsId::as_id(&ix); // We reused the slot of `i0`. if ix_id.index() == i0_id.index() { assert_eq!(ix.field1(&db).0, x); // Re-intern and read `i0` from a new slot. // // Note that the only writes have been synthetic, so none of the query dependencies // have changed directly. The interned value dependency should be enough to force // the inner query to update. let i0 = intern(&db, input, 0); assert_eq!(i0.field1(&db).0, 0); break; } } } #[test] fn test_reuse_interned_input() { // A query that creates an interned value. #[salsa::tracked] fn create_interned(db: &dyn Database, input: Input) -> Interned<'_> { Interned::new(db, BadHash(input.field1(db))) } #[salsa::tracked] fn use_interned<'db>(db: &'db dyn Database, interned: Interned<'db>) -> usize { interned.field1(db).0 } let mut db = common::EventLoggerDatabase::default(); let input = Input::new(&db, 0); // Create and use I0 in R0. let interned = create_interned(&db, input); let result = use_interned(&db, interned); assert_eq!(result, 0); // Create and use I1 in a number of revisions, marking I0 as stale. input.set_field1(&mut db).to(1); for _ in 0..10 { let interned = create_interned(&db, input); let result = use_interned(&db, interned); assert_eq!(result, 1); // Trigger a new revision. input.set_field1(&mut db).to(1); } // Create I2, reusing the stale slot of I0. input.set_field1(&mut db).to(2); let interned = create_interned(&db, input); // Use I2. The function should not be memoized with the value of I0, despite I2 and I0 // sharing the same slot. let result = use_interned(&db, interned); assert_eq!(result, 2); } #[test] fn test_reuse_multiple_interned_input() { // A query that creates an interned value. #[salsa::tracked] fn create_interned(db: &dyn Database, input: Input) -> Interned<'_> { Interned::new(db, BadHash(input.field1(db))) } // A query that creates an interned value. #[salsa::tracked] fn create_nested_interned<'db>( db: &'db dyn Database, interned: Interned<'db>, ) -> NestedInterned<'db> { NestedInterned::new(db, interned) } #[salsa::tracked] fn use_interned<'db>(db: &'db dyn Database, interned: Interned<'db>) -> usize { interned.field1(db).0 } // A query that reads an interned value. #[salsa::tracked] fn use_nested_interned<'db>( db: &'db dyn Database, nested_interned: NestedInterned<'db>, ) -> usize { nested_interned.interned(db).field1(db).0 } let mut db = common::EventLoggerDatabase::default(); let input = Input::new(&db, 0); // Create and use NI0, which wraps I0, in R0. let interned = create_interned(&db, input); let i0_id = salsa::plumbing::AsId::as_id(&interned); let nested_interned = create_nested_interned(&db, interned); let result = use_nested_interned(&db, nested_interned); assert_eq!(result, 0); // Create and use I1 in a number of revisions, marking I0 as stale. input.set_field1(&mut db).to(1); for _ in 0..10 { let interned = create_interned(&db, input); let result = use_interned(&db, interned); assert_eq!(result, 1); // Trigger a new revision. input.set_field1(&mut db).to(1); } // Create I2, reusing the stale slot of I0. input.set_field1(&mut db).to(2); let interned = create_interned(&db, input); let i2_id = salsa::plumbing::AsId::as_id(&interned); assert_ne!(i0_id, i2_id); // Create NI1 wrapping I2 instead of I0. let nested_interned = create_nested_interned(&db, interned); // Use NI1. The function should not be memoized with the value of NI0, // despite I2 and I0 sharing the same ID. let result = use_nested_interned(&db, nested_interned); assert_eq!(result, 2); } #[test] fn test_durability_increase() { #[salsa::tracked] fn intern(db: &dyn Database, input: Input, value: usize) -> Interned<'_> { let _f = input.field1(db); Interned::new(db, BadHash(value)) } let mut db = common::EventLoggerDatabase::default(); let high_durability = Input::builder(0).durability(Durability::HIGH).new(&db); let low_durability = Input::builder(1).durability(Durability::LOW).new(&db); // Intern `i0`. let _i0 = intern(&db, low_durability, 0); // Re-intern `i0`, this time using a high-durability. let _i0 = intern(&db, high_durability, 0); // Get the garbage collector to consider `i0` stale. for _ in 0..100 { let _dummy = intern(&db, low_durability, 1000).field1(&db); db.synthetic_write(Durability::LOW); } // Intern `i1`. // // The slot of `i0` should not be reused as it is high-durability, and there // were no high-durability writes. let _i1 = intern(&db, low_durability, 1); // Re-intern and read `i0`. // // If the slot was reused, the memo would be shallow-verified and we would // read `i1` incorrectly. let value = intern(&db, high_durability, 0); assert_eq!(value.field1(&db).0, 0); db.synthetic_write(Durability::LOW); // We should have the same issue even after a low-durability write. let value = intern(&db, high_durability, 0); assert_eq!(value.field1(&db).0, 0); } salsa-0.26.2/tests/interned-structs.rs000064400000000000000000000263371046102023000160560ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. use std::borrow::Cow; use std::path::{Path, PathBuf}; use expect_test::expect; use salsa::plumbing::{AsId, FromId}; use test_log::test; #[salsa::interned(debug)] struct InternedBoxed<'db> { data: Box, } #[salsa::interned(debug)] struct InternedString<'db> { data: String, } #[salsa::interned(debug)] struct InternedPair<'db> { data: (InternedString<'db>, InternedString<'db>), } #[salsa::interned(debug)] struct InternedTwoFields<'db> { data1: String, data2: String, } #[salsa::interned(debug)] struct InternedVec<'db> { data1: Vec, } #[salsa::interned(debug)] struct InternedBoxedSlice<'db> { data: Box<[String]>, } #[salsa::interned(debug)] struct InternedPathBuf<'db> { data1: PathBuf, } #[salsa::interned(no_lifetime, debug)] struct InternedStringNoLifetime { data: String, } #[derive(Debug, Eq, PartialEq, Hash, Clone)] struct Foo; #[salsa::interned(debug)] struct InternedFoo<'db> { data: Foo, } #[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)] struct SalsaIdWrapper(salsa::Id); impl AsId for SalsaIdWrapper { fn as_id(&self) -> salsa::Id { self.0 } } impl FromId for SalsaIdWrapper { fn from_id(id: salsa::Id) -> Self { SalsaIdWrapper(id) } } #[salsa::interned(id = SalsaIdWrapper, debug)] struct InternedStringWithCustomId<'db> { data: String, } #[salsa::interned(id = SalsaIdWrapper, no_lifetime, debug)] struct InternedStringWithCustomIdAndNoLifetime<'db> { data: String, } #[derive(salsa::Update, Clone, Eq, PartialEq, Hash, Debug)] struct Generic(T); #[salsa::interned(debug)] struct InternedOverGeneric { value: Generic, } #[salsa::tracked] fn intern_stuff(db: &dyn salsa::Database) -> String { let s1 = InternedString::new(db, "Hello, ".to_string()); let s2 = InternedString::new(db, "World, "); let s3 = InternedPair::new(db, (s1, s2)); format!("{s3:?}") } #[test] fn execute() { let db = salsa::DatabaseImpl::new(); expect![[r#" "InternedPair { data: (InternedString { data: \"Hello, \" }, InternedString { data: \"World, \" }) }" "#]].assert_debug_eq(&intern_stuff(&db)); } #[test] fn interning_returns_equal_keys_for_equal_data() { let db = salsa::DatabaseImpl::new(); let s1 = InternedString::new(&db, "Hello, ".to_string()); let s2 = InternedString::new(&db, "World, ".to_string()); let s1_2 = InternedString::new(&db, "Hello, "); let s2_2 = InternedString::new(&db, "World, "); assert_eq!(s1, s1_2); assert_eq!(s2, s2_2); } #[test] fn interning_returns_equal_keys_for_equal_data_multi_field() { let db = salsa::DatabaseImpl::new(); let s1 = InternedTwoFields::new(&db, "Hello, ".to_string(), "World"); let s2 = InternedTwoFields::new(&db, "World, ", "Hello".to_string()); let s1_2 = InternedTwoFields::new(&db, "Hello, ", "World"); let s2_2 = InternedTwoFields::new(&db, "World, ", "Hello"); let new = InternedTwoFields::new(&db, "Hello, World", ""); assert_eq!(s1, s1_2); assert_eq!(s2, s2_2); assert_ne!(s1, s2_2); assert_ne!(s1, new); } #[test] fn interning_boxed() { let db = salsa::DatabaseImpl::new(); assert_eq!( InternedBoxed::new(&db, "Hello"), InternedBoxed::new(&db, Box::from("Hello")) ); } #[test] fn interned_structs_have_public_ingredients() { use salsa::plumbing::{AsId, ZalsaDatabase}; let db = salsa::DatabaseImpl::new(); let s = InternedString::new(&db, String::from("Hello, world!")); let underlying_id = s.0; let data = InternedString::ingredient(db.zalsa()).data(db.zalsa(), underlying_id.as_id()); assert_eq!(data, &(String::from("Hello, world!"),)); } #[test] fn interning_vec() { let db = salsa::DatabaseImpl::new(); let s1 = InternedVec::new(&db, ["Hello, ".to_string(), "World".to_string()].as_slice()); let s2 = InternedVec::new(&db, ["Hello, ", "World"].as_slice()); let s3 = InternedVec::new(&db, vec!["Hello, ".to_string(), "World".to_string()]); let s4 = InternedVec::new(&db, ["Hello, ", "World"].as_slice()); let s5 = InternedVec::new(&db, ["Hello, ", "World", "Test"].as_slice()); let s6 = InternedVec::new(&db, ["Hello, ", "World", ""].as_slice()); let s7 = InternedVec::new(&db, ["Hello, "].as_slice()); assert_eq!(s1, s2); assert_eq!(s1, s3); assert_eq!(s1, s4); assert_ne!(s1, s5); assert_ne!(s1, s6); assert_ne!(s5, s6); assert_ne!(s6, s7); } #[test] fn interning_path_buf() { let db = salsa::DatabaseImpl::new(); let s1 = InternedPathBuf::new(&db, PathBuf::from("test_path".to_string())); let s2 = InternedPathBuf::new(&db, Path::new("test_path")); let s3 = InternedPathBuf::new(&db, Path::new("test_path/")); let s4 = InternedPathBuf::new(&db, Path::new("test_path/a")); assert_eq!(s1, s2); assert_eq!(s1, s3); assert_ne!(s1, s4); } #[test] fn interning_without_lifetimes() { let db = salsa::DatabaseImpl::new(); let s1 = InternedStringNoLifetime::new(&db, "Hello, ".to_string()); let s2 = InternedStringNoLifetime::new(&db, "World, ".to_string()); let s1_2 = InternedStringNoLifetime::new(&db, "Hello, "); let s2_2 = InternedStringNoLifetime::new(&db, "World, "); assert_eq!(s1, s1_2); assert_eq!(s2, s2_2); } #[test] fn interning_with_custom_ids() { let db = salsa::DatabaseImpl::new(); let s1 = InternedStringWithCustomId::new(&db, "Hello, ".to_string()); let s2 = InternedStringWithCustomId::new(&db, "World, ".to_string()); let s1_2 = InternedStringWithCustomId::new(&db, "Hello, "); let s2_2 = InternedStringWithCustomId::new(&db, "World, "); assert_eq!(s1, s1_2); assert_eq!(s2, s2_2); } #[test] fn interning_with_custom_ids_and_no_lifetime() { let db = salsa::DatabaseImpl::new(); let s1 = InternedStringWithCustomIdAndNoLifetime::new(&db, "Hello, ".to_string()); let s2 = InternedStringWithCustomIdAndNoLifetime::new(&db, "World, ".to_string()); let s1_2 = InternedStringWithCustomIdAndNoLifetime::new(&db, "Hello, "); let s2_2 = InternedStringWithCustomIdAndNoLifetime::new(&db, "World, "); assert_eq!(s1, s1_2); assert_eq!(s2, s2_2); } #[test] fn interning_reference() { let db = salsa::DatabaseImpl::new(); let s1 = InternedFoo::new(&db, Foo); let s2 = InternedFoo::new(&db, &Foo); assert_eq!(s1, s2); } #[test] fn interned_generic() { let db = salsa::DatabaseImpl::new(); let s1 = InternedOverGeneric::new(&db, Generic("test".to_string())); let s2 = InternedOverGeneric::new(&db, Generic("test".to_string())); assert_eq!(s1, s2); } #[test] fn interning_boxed_slice_with_cow() { let db = salsa::DatabaseImpl::new(); // Create an interned boxed slice using a boxed slice directly. let boxed: Box<[String]> = vec!["Hello".to_string(), "World".to_string()].into(); let s1 = InternedBoxedSlice::new(&db, boxed); // Looking up with a Cow::Borrowed should find the same interned value. let slice = ["Hello".to_string(), "World".to_string()]; let borrowed: Cow<'_, [String]> = Cow::Borrowed(&slice); let s2 = InternedBoxedSlice::new(&db, borrowed); assert_eq!(s1, s2); // Looking up with a Cow::Owned should also work and reuse the owned value. let owned: Cow<'_, [String]> = Cow::Owned(vec!["Hello".to_string(), "World".to_string()]); let s3 = InternedBoxedSlice::new(&db, owned); assert_eq!(s1, s3); // Different values should result in different interned structs. let different_slice = ["Different".to_string()]; let different: Cow<'_, [String]> = Cow::Borrowed(&different_slice); let s4 = InternedBoxedSlice::new(&db, different); assert_ne!(s1, s4); } #[test] fn interning_string_with_cow() { let db = salsa::DatabaseImpl::new(); // Create an interned string using a String directly. let s1 = InternedString::new(&db, "Hello".to_string()); // Looking up with a Cow::Borrowed should find the same interned value. let borrowed: Cow<'_, str> = Cow::Borrowed("Hello"); let s2 = InternedString::new(&db, borrowed); assert_eq!(s1, s2); // Looking up with a Cow::Owned should also work. let owned: Cow<'_, str> = Cow::Owned("Hello".to_string()); let s3 = InternedString::new(&db, owned); assert_eq!(s1, s3); // Different values should result in different interned structs. let different: Cow<'_, str> = Cow::Borrowed("Different"); let s4 = InternedString::new(&db, different); assert_ne!(s1, s4); } #[test] fn interning_pathbuf_with_cow() { let db = salsa::DatabaseImpl::new(); // Create an interned path using a PathBuf directly. let s1 = InternedPathBuf::new(&db, PathBuf::from("test_path")); // Looking up with a Cow::Borrowed should find the same interned value. let borrowed: Cow<'_, Path> = Cow::Borrowed(Path::new("test_path")); let s2 = InternedPathBuf::new(&db, borrowed); assert_eq!(s1, s2); // Looking up with a Cow::Owned should also work. let owned: Cow<'_, Path> = Cow::Owned(PathBuf::from("test_path")); let s3 = InternedPathBuf::new(&db, owned); assert_eq!(s1, s3); // Different values should result in different interned structs. let different: Cow<'_, Path> = Cow::Borrowed(Path::new("different_path")); let s4 = InternedPathBuf::new(&db, different); assert_ne!(s1, s4); } #[test] fn interning_vec_with_cow() { let db = salsa::DatabaseImpl::new(); // Create an interned vec using a Vec directly. let s1 = InternedVec::new(&db, vec!["Hello".to_string(), "World".to_string()]); // Looking up with a Cow::Borrowed should find the same interned value. let slice = ["Hello".to_string(), "World".to_string()]; let borrowed: Cow<'_, [String]> = Cow::Borrowed(&slice); let s2 = InternedVec::new(&db, borrowed); assert_eq!(s1, s2); // Looking up with a Cow::Owned should also work. let owned: Cow<'_, [String]> = Cow::Owned(vec!["Hello".to_string(), "World".to_string()]); let s3 = InternedVec::new(&db, owned); assert_eq!(s1, s3); // Different values should result in different interned structs. let different: Cow<'_, [String]> = Cow::Owned(vec!["Different".to_string()]); let s4 = InternedVec::new(&db, different); assert_ne!(s1, s4); } #[cfg(feature = "compact_str")] #[salsa::interned(debug)] struct InternedCompactString<'db> { data: compact_str::CompactString, } #[cfg(feature = "compact_str")] #[test] fn interning_compact_string_with_cow() { let db = salsa::DatabaseImpl::new(); // Create an interned compact string using a CompactString directly. let s1 = InternedCompactString::new(&db, compact_str::CompactString::new("Hello")); // Looking up with a Cow::Borrowed should find the same interned value. let borrowed: Cow<'_, str> = Cow::Borrowed("Hello"); let s2 = InternedCompactString::new(&db, borrowed); assert_eq!(s1, s2); // Looking up with a Cow::Owned should also work. let owned: Cow<'_, str> = Cow::Owned("Hello".to_string()); let s3 = InternedCompactString::new(&db, owned); assert_eq!(s1, s3); // Different values should result in different interned structs. let different: Cow<'_, str> = Cow::Borrowed("Different"); let s4 = InternedCompactString::new(&db, different); assert_ne!(s1, s4); } salsa-0.26.2/tests/interned-structs_self_ref.rs000064400000000000000000000210301046102023000177040ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. use std::any::TypeId; use std::convert::identity; use salsa::plumbing::Zalsa; use test_log::test; #[test] fn interning_returns_equal_keys_for_equal_data() { let db = salsa::DatabaseImpl::new(); let s1 = InternedString::new(&db, "Hello, ".to_string(), identity); let s2 = InternedString::new(&db, "World, ".to_string(), |_| s1); let s1_2 = InternedString::new(&db, "Hello, ", identity); let s2_2 = InternedString::new(&db, "World, ", |_| s2); assert_eq!(s1, s1_2); assert_eq!(s2, s2_2); } // Recursive expansion of interned macro // #[salsa::interned] // struct InternedString<'db> { // data: String, // other: InternedString<'db>, // } // ====================================== #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] struct InternedString<'db>( salsa::Id, std::marker::PhantomData<&'db salsa::plumbing::interned::Value>>, ); #[allow(warnings)] const _: () = { use salsa::plumbing as zalsa_; use zalsa_::interned as zalsa_struct_; type Configuration_ = InternedString<'static>; impl<'db> zalsa_::HasJar for InternedString<'db> { type Jar = zalsa_struct_::JarImpl; const KIND: zalsa_::JarKind = zalsa_::JarKind::Struct; } zalsa_::register_jar! { zalsa_::ErasedJar::erase::>() } #[derive(Clone)] struct StructData<'db>(String, InternedString<'db>); impl<'db> Eq for StructData<'db> {} impl<'db> PartialEq for StructData<'db> { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } impl<'db> std::hash::Hash for StructData<'db> { fn hash(&self, state: &mut H) { self.0.hash(state); } } #[doc = r" Key to use during hash lookups. Each field is some type that implements `Lookup`"] #[doc = r" for the owned type. This permits interning with an `&str` when a `String` is required and so forth."] #[derive(Hash)] struct StructKey<'db, T0>(T0, std::marker::PhantomData<&'db ()>); impl<'db, T0> zalsa_::HashEqLike> for StructData<'db> where String: zalsa_::HashEqLike, { fn hash(&self, h: &mut H) { zalsa_::HashEqLike::::hash(&self.0, &mut *h); } fn eq(&self, data: &StructKey<'db, T0>) -> bool { (zalsa_::HashEqLike::::eq(&self.0, &data.0) && true) } } impl zalsa_struct_::Configuration for Configuration_ { const LOCATION: zalsa_::Location = zalsa_::Location { file: file!(), line: line!(), }; const DEBUG_NAME: &'static str = "InternedString"; type Fields<'a> = StructData<'a>; type Struct<'a> = InternedString<'a>; const PERSIST: bool = false; fn serialize(value: &Self::Fields<'_>, serializer: S) -> Result where S: zalsa_::serde::Serializer, { panic!("attempted to serialize value not marked with `persist` attribute") } fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: zalsa_::serde::Deserializer<'de>, { panic!("attempted to deserialize value not marked with `persist` attribute") } } impl Configuration_ { pub fn ingredient(zalsa: &zalsa_::Zalsa) -> &zalsa_struct_::IngredientImpl { static CACHE: zalsa_::IngredientCache> = zalsa_::IngredientCache::new(); // SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the only // ingredient created by our jar is the struct ingredient. unsafe { CACHE.get_or_create(zalsa, || { zalsa.lookup_jar_by_type::>() }) } } } impl zalsa_::AsId for InternedString<'_> { fn as_id(&self) -> salsa::Id { self.0 } } impl zalsa_::FromId for InternedString<'_> { fn from_id(id: salsa::Id) -> Self { Self(id, std::marker::PhantomData) } } unsafe impl Send for InternedString<'_> {} unsafe impl Sync for InternedString<'_> {} impl std::fmt::Debug for InternedString<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Self::default_debug_fmt(*self, f) } } impl zalsa_::SalsaStructInDb for InternedString<'_> { type MemoIngredientMap = zalsa_::MemoIngredientSingletonIndex; const LEAF_TYPE_IDS: &'static [salsa::plumbing::ConstTypeId] = &[salsa::plumbing::ConstTypeId::of::()]; fn lookup_ingredient_index(aux: &Zalsa) -> salsa::plumbing::IngredientIndices { aux.lookup_jar_by_type::>() .into() } fn entries(zalsa: &zalsa_::Zalsa) -> impl Iterator + '_ { let ingredient_index = zalsa.lookup_jar_by_type::>(); ::ingredient(zalsa) .entries(zalsa) .map(|entry| entry.key()) } #[inline] fn cast(id: zalsa_::Id, type_id: TypeId) -> Option { if type_id == TypeId::of::() { Some(::from_id(id)) } else { None } } #[inline] unsafe fn memo_table( zalsa: &zalsa_::Zalsa, id: zalsa_::Id, current_revision: zalsa_::Revision, ) -> zalsa_::MemoTableWithTypes<'_> { // SAFETY: Guaranteed by caller. unsafe { zalsa .table() .memos::>(id, current_revision) } } } unsafe impl zalsa_::Update for InternedString<'_> { unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { if unsafe { *old_pointer } != new_value { unsafe { *old_pointer = new_value }; true } else { false } } } impl<'db> InternedString<'db> { pub fn new + std::hash::Hash>( db: &'db Db_, data: T0, other: impl FnOnce(InternedString<'db>) -> InternedString<'db>, ) -> Self where Db_: ?Sized + salsa::Database, String: zalsa_::HashEqLike, { Configuration_::ingredient(db.zalsa()).intern( db.zalsa(), db.zalsa_local(), StructKey::<'db>(data, std::marker::PhantomData::default()), |id, data| { StructData( zalsa_::Lookup::into_owned(data.0), other(zalsa_::FromId::from_id(id)), ) }, ) } fn data(self, db: &'db Db_) -> String where Db_: ?Sized + zalsa_::Database, { let fields = Configuration_::ingredient(db.zalsa()).fields(db.zalsa(), self); std::clone::Clone::clone((&fields.0)) } fn other(self, db: &'db Db_) -> InternedString<'db> where Db_: ?Sized + zalsa_::Database, { let fields = Configuration_::ingredient(db.zalsa()).fields(db.zalsa(), self); std::clone::Clone::clone((&fields.1)) } #[doc = r" Default debug formatting for this struct (may be useful if you define your own `Debug` impl)"] pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { zalsa_::with_attached_database(|db| { let fields = Configuration_::ingredient(db.zalsa()).fields(db.zalsa(), this); let mut f = f.debug_struct("InternedString"); let f = f.field("data", &fields.0); let f = f.field("other", &fields.1); f.finish() }) .unwrap_or_else(|| { f.debug_tuple("InternedString") .field(&zalsa_::AsId::as_id(&this)) .finish() }) } } }; salsa-0.26.2/tests/lru.rs000064400000000000000000000076151046102023000133410ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn with lru options //! compiles and executes successfully. use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; mod common; use common::LogDatabase; use salsa::Database as _; use test_log::test; #[derive(Debug, PartialEq, Eq)] struct HotPotato(u32); thread_local! { static N_POTATOES: AtomicUsize = const { AtomicUsize::new(0) } } impl HotPotato { fn new(id: u32) -> HotPotato { N_POTATOES.with(|n| n.fetch_add(1, Ordering::SeqCst)); HotPotato(id) } } impl Drop for HotPotato { fn drop(&mut self) { N_POTATOES.with(|n| n.fetch_sub(1, Ordering::SeqCst)); } } #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked(lru = 8)] fn get_hot_potato(db: &dyn LogDatabase, input: MyInput) -> Arc { db.push_log(format!("get_hot_potato({:?})", input.field(db))); Arc::new(HotPotato::new(input.field(db))) } #[salsa::tracked] fn get_hot_potato2(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("get_hot_potato2({:?})", input.field(db))); get_hot_potato(db, input).0 } fn load_n_potatoes() -> usize { N_POTATOES.with(|n| n.load(Ordering::SeqCst)) } #[test] fn lru_works() { let mut db = common::LoggerDatabase::default(); assert_eq!(load_n_potatoes(), 0); for i in 0..32u32 { let input = MyInput::new(&db, i); let p = get_hot_potato(&db, input); assert_eq!(p.0, i); } assert_eq!(load_n_potatoes(), 32); // trigger the GC db.synthetic_write(salsa::Durability::HIGH); assert_eq!(load_n_potatoes(), 8); } #[test] fn lru_can_be_changed_at_runtime() { let mut db = common::LoggerDatabase::default(); assert_eq!(load_n_potatoes(), 0); let inputs: Vec<(u32, MyInput)> = (0..32).map(|i| (i, MyInput::new(&db, i))).collect(); for &(i, input) in inputs.iter() { let p = get_hot_potato(&db, input); assert_eq!(p.0, i); } assert_eq!(load_n_potatoes(), 32); // trigger the GC db.synthetic_write(salsa::Durability::HIGH); assert_eq!(load_n_potatoes(), 8); get_hot_potato::set_lru_capacity(&mut db, 16); assert_eq!(load_n_potatoes(), 8); for &(i, input) in inputs.iter() { let p = get_hot_potato(&db, input); assert_eq!(p.0, i); } assert_eq!(load_n_potatoes(), 32); // trigger the GC db.synthetic_write(salsa::Durability::HIGH); assert_eq!(load_n_potatoes(), 16); // Special case: setting capacity to zero disables LRU get_hot_potato::set_lru_capacity(&mut db, 0); assert_eq!(load_n_potatoes(), 16); for &(i, input) in inputs.iter() { let p = get_hot_potato(&db, input); assert_eq!(p.0, i); } assert_eq!(load_n_potatoes(), 32); // trigger the GC db.synthetic_write(salsa::Durability::HIGH); assert_eq!(load_n_potatoes(), 32); drop(db); assert_eq!(load_n_potatoes(), 0); } #[test] fn lru_keeps_dependency_info() { let mut db = common::LoggerDatabase::default(); let capacity = 8; // Invoke `get_hot_potato2` 33 times. This will (in turn) invoke // `get_hot_potato`, which will trigger LRU after 8 executions. let inputs: Vec = (0..(capacity + 1)) .map(|i| MyInput::new(&db, i as u32)) .collect(); for (i, input) in inputs.iter().enumerate() { let x = get_hot_potato2(&db, *input); assert_eq!(x as usize, i); } db.synthetic_write(salsa::Durability::HIGH); // We want to test that calls to `get_hot_potato2` are still considered // clean. Check that no new executions occur as we go here. db.assert_logs_len((capacity + 1) * 2); // calling `get_hot_potato2(0)` has to check that `get_hot_potato(0)` is still valid; // even though we've evicted it (LRU), we find that it is still good let p = get_hot_potato2(&db, *inputs.first().unwrap()); assert_eq!(p, 0); db.assert_logs_len(0); } salsa-0.26.2/tests/manual_registration.rs000064400000000000000000000050761046102023000166050ustar 00000000000000#![cfg(not(feature = "inventory"))] mod ingredients { #[salsa::input] pub(super) struct MyInput { field: u32, } #[salsa::tracked] pub(super) struct MyTracked<'db> { pub(super) field: u32, } #[salsa::interned] pub(super) struct MyInterned<'db> { pub(super) field: u32, } #[salsa::tracked] pub(super) fn intern<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyInterned<'db> { MyInterned::new(db, input.field(db)) } #[salsa::tracked] pub(super) fn track<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyTracked<'db> { MyTracked::new(db, input.field(db)) } } #[salsa::db] #[derive(Clone, Default)] pub struct DatabaseImpl { storage: salsa::Storage, } #[salsa::db] impl salsa::Database for DatabaseImpl {} #[test] fn single_database() { let db = DatabaseImpl { storage: salsa::Storage::builder() .ingredient::() .ingredient::() .ingredient::() .ingredient::>() .ingredient::>() .build(), }; let input = ingredients::MyInput::new(&db, 1); let tracked = ingredients::track(&db, input); let interned = ingredients::intern(&db, input); assert_eq!(tracked.field(&db), 1); assert_eq!(interned.field(&db), 1); } #[test] fn multiple_databases() { let db1 = DatabaseImpl { storage: salsa::Storage::builder() .ingredient::() .ingredient::() .ingredient::>() .build(), }; let input = ingredients::MyInput::new(&db1, 1); let interned = ingredients::intern(&db1, input); assert_eq!(interned.field(&db1), 1); // Create a second database with different ingredient indices. let db2 = DatabaseImpl { storage: salsa::Storage::builder() .ingredient::() .ingredient::() .ingredient::() .ingredient::>() .ingredient::>() .build(), }; let input = ingredients::MyInput::new(&db2, 2); let interned = ingredients::intern(&db2, input); assert_eq!(interned.field(&db2), 2); let input = ingredients::MyInput::new(&db2, 3); let tracked = ingredients::track(&db2, input); assert_eq!(tracked.field(&db2), 3); } salsa-0.26.2/tests/memory-usage.rs000064400000000000000000000125161046102023000151450ustar 00000000000000#![cfg(feature = "inventory")] #[salsa::input(heap_size = string_tuple_size_of)] struct MyInput { field: String, } #[salsa::tracked(heap_size = string_tuple_size_of)] struct MyTracked<'db> { field: String, } #[salsa::interned(heap_size = string_tuple_size_of)] struct MyInterned<'db> { field: String, } #[salsa::tracked] fn input_to_interned(db: &dyn salsa::Database, input: MyInput) -> MyInterned<'_> { MyInterned::new(db, input.field(db)) } #[salsa::tracked] fn input_to_tracked(db: &dyn salsa::Database, input: MyInput) -> MyTracked<'_> { MyTracked::new(db, input.field(db)) } #[salsa::tracked] fn input_to_string(_db: &dyn salsa::Database) -> String { "a".repeat(1000) } #[salsa::tracked(heap_size = string_size_of)] fn input_to_string_get_size(_db: &dyn salsa::Database) -> String { "a".repeat(1000) } fn string_size_of(x: &String) -> usize { x.capacity() } fn string_tuple_size_of((x,): &(String,)) -> usize { x.capacity() } #[salsa::tracked] fn input_to_tracked_tuple( db: &dyn salsa::Database, input: MyInput, ) -> (MyTracked<'_>, MyTracked<'_>) { ( MyTracked::new(db, input.field(db)), MyTracked::new(db, input.field(db)), ) } #[rustversion::all(stable, since(1.91))] #[test] fn test() { use expect_test::expect; let db = salsa::DatabaseImpl::new(); let input1 = MyInput::new(&db, "a".repeat(50)); let input2 = MyInput::new(&db, "a".repeat(150)); let input3 = MyInput::new(&db, "a".repeat(250)); let _tracked1 = input_to_tracked(&db, input1); let _tracked2 = input_to_tracked(&db, input2); let _tracked_tuple = input_to_tracked_tuple(&db, input1); let _interned1 = input_to_interned(&db, input1); let _interned2 = input_to_interned(&db, input2); let _interned3 = input_to_interned(&db, input3); let _string1 = input_to_string(&db); let _string2 = input_to_string_get_size(&db); let memory_usage = ::memory_usage(&db); let expected = expect![[r#" [ IngredientInfo { debug_name: "MyInput", count: 3, size_of_metadata: 96, size_of_fields: 72, heap_size_of_fields: Some( 450, ), }, IngredientInfo { debug_name: "MyInterned", count: 3, size_of_metadata: 168, size_of_fields: 72, heap_size_of_fields: Some( 450, ), }, IngredientInfo { debug_name: "MyTracked", count: 4, size_of_metadata: 128, size_of_fields: 96, heap_size_of_fields: Some( 300, ), }, IngredientInfo { debug_name: "input_to_string::interned_arguments", count: 1, size_of_metadata: 56, size_of_fields: 0, heap_size_of_fields: None, }, IngredientInfo { debug_name: "input_to_string_get_size::interned_arguments", count: 1, size_of_metadata: 56, size_of_fields: 0, heap_size_of_fields: None, }, ]"#]]; expected.assert_eq(&format!("{:#?}", memory_usage.structs)); let mut queries_info = memory_usage.queries.into_iter().collect::>(); queries_info.sort(); let expected = expect![[r#" [ ( "input_to_interned", IngredientInfo { debug_name: "memory_usage::MyInterned<'_>", count: 3, size_of_metadata: 192, size_of_fields: 24, heap_size_of_fields: None, }, ), ( "input_to_string", IngredientInfo { debug_name: "alloc::string::String", count: 1, size_of_metadata: 40, size_of_fields: 24, heap_size_of_fields: None, }, ), ( "input_to_string_get_size", IngredientInfo { debug_name: "alloc::string::String", count: 1, size_of_metadata: 40, size_of_fields: 24, heap_size_of_fields: Some( 1000, ), }, ), ( "input_to_tracked", IngredientInfo { debug_name: "memory_usage::MyTracked<'_>", count: 2, size_of_metadata: 168, size_of_fields: 16, heap_size_of_fields: None, }, ), ( "input_to_tracked_tuple", IngredientInfo { debug_name: "(memory_usage::MyTracked<'_>, memory_usage::MyTracked<'_>)", count: 1, size_of_metadata: 108, size_of_fields: 16, heap_size_of_fields: None, }, ), ]"#]]; expected.assert_eq(&format!("{queries_info:#?}")); } salsa-0.26.2/tests/mutate_in_place.rs000064400000000000000000000014451046102023000156630ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a setting a field on a `#[salsa::input]` //! overwrites and returns the old value. use salsa::Setter; use test_log::test; #[salsa::input] struct MyInput { field: String, } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, "Hello".to_string()); // Overwrite field with an empty String // and store the old value in my_string let mut my_string = input.set_field(&mut db).to(String::new()); my_string.push_str(" World!"); // Set the field back to out initial String, // expecting to get the empty one back assert_eq!(input.set_field(&mut db).to(my_string), ""); // Check if the stored String is the one we expected assert_eq!(input.field(&db), "Hello World!"); } salsa-0.26.2/tests/override_new_get_set.rs000064400000000000000000000027271046102023000167400ustar 00000000000000#![cfg(feature = "inventory")] //! Test that the `constructor` macro overrides //! the `new` method's name and `get` and `set` //! change the name of the getter and setter of the fields. #![allow(warnings)] use std::fmt::Display; use salsa::Setter; #[salsa::db] trait Db: salsa::Database {} #[salsa::input(constructor = from_string)] struct MyInput { #[get(text)] #[set(set_text)] field: String, } impl MyInput { pub fn new(db: &mut dyn Db, s: impl Display) -> MyInput { MyInput::from_string(db, s.to_string()) } pub fn field(self, db: &dyn Db) -> String { self.text(db) } pub fn set_field(self, db: &mut dyn Db, id: String) { self.set_text(db).to(id); } } #[salsa::interned(constructor = from_string)] struct MyInterned<'db> { #[get(text)] #[returns(ref)] field: String, } impl<'db> MyInterned<'db> { pub fn new(db: &'db dyn Db, s: impl Display) -> MyInterned<'db> { MyInterned::from_string(db, s.to_string()) } pub fn field(self, db: &'db dyn Db) -> &str { &self.text(db) } } #[salsa::tracked(constructor = from_string)] struct MyTracked<'db> { #[get(text)] field: String, } impl<'db> MyTracked<'db> { pub fn new(db: &'db dyn Db, s: impl Display) -> MyTracked<'db> { MyTracked::from_string(db, s.to_string()) } pub fn field(self, db: &'db dyn Db) -> String { self.text(db) } } #[test] fn execute() { salsa::DatabaseImpl::new(); } salsa-0.26.2/tests/panic-when-creating-tracked-struct-outside-of-tracked-fn.rs000064400000000000000000000006241046102023000254000ustar 00000000000000#![cfg(feature = "inventory")] //! Test that creating a tracked struct outside of a //! tracked function panics with an assert message. #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[test] #[should_panic( expected = "cannot create a tracked struct disambiguator outside of a tracked function" )] fn execute() { let db = salsa::DatabaseImpl::new(); MyTracked::new(&db, 0); } salsa-0.26.2/tests/parallel/cancellation_token_cycle_nested.rs000064400000000000000000000110461046102023000227010ustar 00000000000000// Shuttle doesn't like panics inside of its runtime. #![cfg(not(feature = "shuttle"))] //! Test for cancellation with deeply nested cycles across multiple threads. //! //! These tests verify that local cancellation is disabled during cycle iteration, //! allowing multi-threaded cycles to complete successfully before cancellation //! can take effect. use salsa::Database; use crate::setup::{Knobs, KnobsDatabase}; use crate::sync::thread; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); const MIN: CycleValue = CycleValue(0); const MAX: CycleValue = CycleValue(3); #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn KnobsDatabase) -> CycleValue { query_b(db) } #[salsa::tracked(cycle_initial=initial)] fn query_b(db: &dyn KnobsDatabase) -> CycleValue { let c_value = query_c(db); CycleValue(c_value.0 + 1).min(MAX) } #[salsa::tracked(cycle_initial=initial)] fn query_c(db: &dyn KnobsDatabase) -> CycleValue { let d_value = query_d(db); let e_value = query_e(db); let b_value = query_b(db); let a_value = query_a(db); CycleValue(d_value.0.max(e_value.0).max(b_value.0).max(a_value.0)) } #[salsa::tracked(cycle_initial=initial)] fn query_d(db: &dyn KnobsDatabase) -> CycleValue { query_c(db) } #[salsa::tracked(cycle_initial=initial)] fn query_e(db: &dyn KnobsDatabase) -> CycleValue { query_c(db) } #[salsa::tracked] fn query_f(db: &dyn KnobsDatabase) -> CycleValue { let c = query_c(db); // this should trigger cancellation again query_h(db); c } #[salsa::tracked] fn query_h(db: &dyn KnobsDatabase) { _ = db; } fn initial(db: &dyn KnobsDatabase, _id: salsa::Id) -> CycleValue { db.signal(1); db.wait_for(6); MIN } /// Test that a multi-threaded cycle completes successfully even when /// cancellation is requested during the cycle. /// /// This test is similar to cycle_nested_deep but adds cancellation during /// the cycle to verify that cancellation is properly deferred. #[test] fn multi_threaded_cycle_completes_despite_cancellation() { let db = Knobs::default(); let db_t1 = db.clone(); let db_t2 = db.clone(); let db_t3 = db.clone(); let db_t4 = db.clone(); let db_t5 = db.clone(); let db_signaler = db; let token_t1 = db_t1.cancellation_token(); let token_t2 = db_t2.cancellation_token(); let token_t3 = db_t3.cancellation_token(); let token_t5 = db_t5.cancellation_token(); // Thread 1: Runs the main cycle, will have cancellation requested during it let t1 = thread::spawn(move || query_a(&db_t1)); // Wait for t1 to start the cycle db_signaler.wait_for(1); // Spawn t2 and wait for it to block on the cycle db_signaler.signal_on_will_block(2); let t2 = thread::spawn(move || query_b(&db_t2)); db_signaler.wait_for(2); // Spawn t3 and wait for it to block on the cycle db_signaler.signal_on_will_block(3); let t3 = thread::spawn(move || query_d(&db_t3)); db_signaler.wait_for(3); // Spawn t4 - doesn't get cancelled db_signaler.signal_on_will_block(4); let t4 = thread::spawn(move || query_e(&db_t4)); db_signaler.wait_for(4); // Spawn t5 - doesn't get cancelled db_signaler.signal_on_will_block(5); let t5 = thread::spawn(move || query_f(&db_t5)); db_signaler.wait_for(5); // Request cancellation while t2 and t3 are blocked on the cycle // This should be deferred until after the cycle completes token_t1.cancel(); token_t2.cancel(); token_t3.cancel(); token_t5.cancel(); // Let t1 continue - the cycle should still complete because // cancellation is disabled during fixpoint iteration db_signaler.signal(6); // All threads should complete successfully let r_t1 = t1.join().unwrap(); let r_t2 = t2.join().unwrap(); let r_t3 = t3.join().unwrap(); let r_t4 = t4.join().unwrap(); let r_t5 = t5.join().unwrap_err(); // All should get MAX because cycles defer cancellation assert_eq!(r_t1, MAX, "t1 should get MAX"); assert_eq!(r_t2, MAX, "t2 should get MAX"); assert_eq!(r_t3, MAX, "t3 should get MAX"); assert_eq!(r_t4, MAX, "t4 should get MAX"); assert!( matches!( *r_t5.downcast::().unwrap(), salsa::Cancelled::Local ), "t5 should be cancelled as its blocked on the cycle, not participating in it and calling an uncomputed query after" ); } salsa-0.26.2/tests/parallel/cancellation_token_multi_blocked.rs000064400000000000000000000050331046102023000230540ustar 00000000000000// Shuttle doesn't like panics inside of its runtime. #![cfg(not(feature = "shuttle"))] //! Test for cancellation when multiple queries are blocked on the cancelled thread. //! //! This test verifies that: //! 1. When a thread is cancelled, blocked threads recompute rather than propagate cancellation //! 2. The final result is correctly computed by the remaining threads use salsa::{Cancelled, Database}; use crate::setup::{Knobs, KnobsDatabase}; #[salsa::tracked] fn query_a(db: &dyn KnobsDatabase) -> u32 { query_b(db) } #[salsa::tracked] fn query_b(db: &dyn KnobsDatabase) -> u32 { // Signal that t1 has started computing query_b db.signal(1); // Wait for t2 and t3 to block on us db.wait_for(3); // Wait for cancellation to happen db.wait_for(4); query_c(db) } #[salsa::tracked] fn query_c(_db: &dyn KnobsDatabase) -> u32 { 42 } /// Test that when a thread is cancelled, other blocked threads successfully /// recompute the query and get the correct result. #[test] fn multiple_threads_blocked_on_cancelled() { let db = Knobs::default(); let db2 = db.clone(); let db3 = db.clone(); let db_signaler = db.clone(); let token = db.cancellation_token(); // Thread 1: Starts computing query_a -> query_b, will be cancelled let t1 = std::thread::spawn(move || query_a(&db)); // Wait for t1 to start query_b db_signaler.wait_for(1); // Thread 2: Will block on query_a (which is blocked on query_b) db2.signal_on_will_block(2); let t2 = std::thread::spawn(move || query_a(&db2)); // Wait for t2 to block db_signaler.wait_for(2); // Thread 3: Also blocks on query_a db3.signal_on_will_block(3); let t3 = std::thread::spawn(move || query_a(&db3)); // Wait for t3 to block db_signaler.wait_for(3); // Now cancel t1 token.cancel(); // Let t1 continue and get cancelled db_signaler.signal(4); // Collect results let r1 = t1.join(); let r2 = t2.join(); let r3 = t3.join(); // t1 should have been cancelled let r1_cancelled = r1.unwrap_err().downcast::().map(|c| *c); assert!( matches!(r1_cancelled, Ok(Cancelled::Local)), "t1 should be locally cancelled, got: {:?}", r1_cancelled ); // t2 and t3 should both succeed with the correct value assert_eq!(r2.unwrap(), 42, "t2 should compute the correct result"); assert_eq!(r3.unwrap(), 42, "t3 should compute the correct result"); } salsa-0.26.2/tests/parallel/cancellation_token_recomputes.rs000064400000000000000000000021471046102023000224300ustar 00000000000000// Shuttle doesn't like panics inside of its runtime. #![cfg(not(feature = "shuttle"))] //! Test for cancellation when another query is blocked on the cancelled thread. use salsa::{Cancelled, Database}; use crate::setup::{Knobs, KnobsDatabase}; #[salsa::tracked] fn query_a(db: &dyn KnobsDatabase) -> u32 { query_b(db) } #[salsa::tracked] fn query_b(db: &dyn KnobsDatabase) -> u32 { db.signal(1); db.wait_for(3); query_c(db) } #[salsa::tracked] fn query_c(_db: &dyn KnobsDatabase) -> u32 { 1 } #[test] fn execute() { let db = Knobs::default(); let db2 = db.clone(); let db_signaler = db.clone(); let token = db.cancellation_token(); let t1 = std::thread::spawn(move || query_a(&db)); db_signaler.wait_for(1); db2.signal_on_will_block(2); let t2 = std::thread::spawn(move || query_a(&db2)); db_signaler.wait_for(2); token.cancel(); db_signaler.signal(3); let (r1, r2) = (t1.join(), t2.join()); let r1 = *r1.unwrap_err().downcast::().unwrap(); assert!(matches!(r1, Cancelled::Local), "{r1:?}"); assert_eq!(r2.unwrap(), 1); } salsa-0.26.2/tests/parallel/cycle_a_t1_b_t2.rs000064400000000000000000000035361046102023000172420ustar 00000000000000//! Test a specific cycle scenario: //! //! ```text //! Thread T1 Thread T2 //! --------- --------- //! | | //! v | //! query_a() | //! ^ | v //! | +------------> query_b() //! | | //! +--------------------+ //! ``` use crate::sync::thread; use crate::{Knobs, KnobsDatabase}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); const MIN: CycleValue = CycleValue(0); const MAX: CycleValue = CycleValue(3); // Signal 1: T1 has entered `query_a` // Signal 2: T2 has entered `query_b` #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn KnobsDatabase) -> CycleValue { db.signal(1); // Wait for Thread T2 to enter `query_b` before we continue. db.wait_for(2); query_b(db) } #[salsa::tracked(cycle_initial=initial)] fn query_b(db: &dyn KnobsDatabase) -> CycleValue { // Wait for Thread T1 to enter `query_a` before we continue. db.wait_for(1); db.signal(2); let a_value = query_a(db); CycleValue(a_value.0 + 1).min(MAX) } fn initial(_db: &dyn KnobsDatabase, _id: salsa::Id) -> CycleValue { MIN } #[test_log::test] fn the_test() { crate::sync::check(|| { tracing::debug!("Starting new run"); let db_t1 = Knobs::default(); let db_t2 = db_t1.clone(); let t1 = thread::spawn(move || { let _span = tracing::debug_span!("t1", thread_id = ?thread::current().id()).entered(); query_a(&db_t1) }); let t2 = thread::spawn(move || { let _span = tracing::debug_span!("t2", thread_id = ?thread::current().id()).entered(); query_b(&db_t2) }); let (r_t1, r_t2) = (t1.join().unwrap(), t2.join().unwrap()); assert_eq!((r_t1, r_t2), (MAX, MAX)); }); } salsa-0.26.2/tests/parallel/cycle_a_t1_b_t2_fallback.rs000064400000000000000000000035661046102023000210640ustar 00000000000000//! Test a specific cycle scenario: //! //! ```text //! Thread T1 Thread T2 //! --------- --------- //! | | //! v | //! query_a() | //! ^ | v //! | +------------> query_b() //! | | //! +--------------------+ //! ``` use crate::KnobsDatabase; const FALLBACK_A: u32 = 0b01; const FALLBACK_B: u32 = 0b10; const OFFSET_A: u32 = 0b0100; const OFFSET_B: u32 = 0b1000; // Signal 1: T1 has entered `query_a` // Signal 2: T2 has entered `query_b` #[salsa::tracked(cycle_result=cycle_result_a)] fn query_a(db: &dyn KnobsDatabase) -> u32 { db.signal(1); // Wait for Thread T2 to enter `query_b` before we continue. db.wait_for(2); query_b(db) | OFFSET_A } #[salsa::tracked(cycle_result=cycle_result_b)] fn query_b(db: &dyn KnobsDatabase) -> u32 { // Wait for Thread T1 to enter `query_a` before we continue. db.wait_for(1); db.signal(2); query_a(db) | OFFSET_B } fn cycle_result_a(_db: &dyn KnobsDatabase, _id: salsa::Id) -> u32 { FALLBACK_A } fn cycle_result_b(_db: &dyn KnobsDatabase, _id: salsa::Id) -> u32 { FALLBACK_B } #[test_log::test] fn the_test() { use crate::Knobs; use crate::sync::thread; crate::sync::check(|| { tracing::debug!("Starting new run"); let db_t1 = Knobs::default(); let db_t2 = db_t1.clone(); let t1 = thread::spawn(move || { let _span = tracing::debug_span!("t1", thread_id = ?thread::current().id()).entered(); query_a(&db_t1) }); let t2 = thread::spawn(move || { let _span = tracing::debug_span!("t2", thread_id = ?thread::current().id()).entered(); query_b(&db_t2) }); let (r_t1, r_t2) = (t1.join(), t2.join()); assert_eq!((r_t1.unwrap(), r_t2.unwrap()), (FALLBACK_A, FALLBACK_B)); }); } salsa-0.26.2/tests/parallel/cycle_ab_peeping_c.rs000064400000000000000000000041011046102023000200700ustar 00000000000000//! Test a specific cycle scenario: //! //! Thread T1 calls A which calls B which calls A. //! //! Thread T2 calls C which calls B. //! //! The trick is that the call from Thread T2 comes before B has reached a fixed point. //! We want to be sure that C sees the final value (and blocks until it is complete). use crate::sync::thread; use crate::{Knobs, KnobsDatabase}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); const MIN: CycleValue = CycleValue(0); const MID: CycleValue = CycleValue(5); const MAX: CycleValue = CycleValue(10); #[salsa::tracked(cycle_initial=cycle_initial)] fn query_a(db: &dyn KnobsDatabase) -> CycleValue { let b_value = query_b(db); // When we reach the mid point, signal stage 1 (unblocking T2) // and then wait for T2 to signal stage 2. if b_value == MID { db.signal(1); db.wait_for(2); } b_value } fn cycle_initial(_db: &dyn KnobsDatabase, _id: salsa::Id) -> CycleValue { MIN } #[salsa::tracked(cycle_initial=cycle_initial)] fn query_b(db: &dyn KnobsDatabase) -> CycleValue { let a_value = query_a(db); CycleValue(a_value.0 + 1).min(MAX) } #[salsa::tracked] fn query_c(db: &dyn KnobsDatabase) -> CycleValue { // Wait until T1 has reached MID then execute `query_b`. // This should block and (due to the configuration on our database) signal stage 2. db.wait_for(1); query_b(db) } #[test_log::test] fn the_test() { crate::sync::check(|| { let db_t1 = Knobs::default(); let db_t2 = db_t1.clone(); db_t2.signal_on_will_block(2); let t1 = thread::spawn(move || { let _span = tracing::debug_span!("t1", thread_id = ?thread::current().id()).entered(); query_a(&db_t1) }); let t2 = thread::spawn(move || { let _span = tracing::debug_span!("t2", thread_id = ?thread::current().id()).entered(); query_c(&db_t2) }); let (r_t1, r_t2) = (t1.join().unwrap(), t2.join().unwrap()); assert_eq!((r_t1, r_t2), (MAX, MAX)); }); } salsa-0.26.2/tests/parallel/cycle_iteration_mismatch.rs000064400000000000000000000074131046102023000213710ustar 00000000000000//! Test for iteration count mismatch bug where cycle heads have different iteration counts //! //! This test aims to reproduce the scenario where: //! 1. A memo has multiple cycle heads with different iteration counts //! 2. When validating, iteration counts mismatch causes re-execution //! 3. After re-execution, the memo still has the same mismatched iteration counts use crate::sync::thread; use crate::{Knobs, KnobsDatabase}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); const MIN: CycleValue = CycleValue(0); const MAX: CycleValue = CycleValue(5); // Query A: First cycle head - will iterate multiple times #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn KnobsDatabase) -> CycleValue { let b = query_b(db); CycleValue(b.0 + 1).min(MAX) } // Query B: Depends on C and D, creating complex dependencies #[salsa::tracked(cycle_initial=initial)] fn query_b(db: &dyn KnobsDatabase) -> CycleValue { let c = query_c(db); let d = query_d(db); CycleValue(c.0.max(d.0) + 1).min(MAX) } // Query C: Creates a cycle back to A #[salsa::tracked(cycle_initial=initial)] fn query_c(db: &dyn KnobsDatabase) -> CycleValue { let a = query_a(db); // Also depends on E to create more complex cycle structure let e = query_e(db); CycleValue(a.0.max(e.0)) } // Query D: Part of a separate cycle with E #[salsa::tracked(cycle_initial=initial)] fn query_d(db: &dyn KnobsDatabase) -> CycleValue { let e = query_e(db); CycleValue(e.0 + 1).min(MAX) } // Query E: Depends back on D and F #[salsa::tracked(cycle_initial=initial)] fn query_e(db: &dyn KnobsDatabase) -> CycleValue { let d = query_d(db); let f = query_f(db); CycleValue(d.0.max(f.0) + 1).min(MAX) } // Query F: Creates another cycle that might have different iteration count #[salsa::tracked(cycle_initial=initial)] fn query_f(db: &dyn KnobsDatabase) -> CycleValue { // Create a cycle that depends on earlier queries let b = query_b(db); let e = query_e(db); CycleValue(b.0.max(e.0)) } fn initial(_db: &dyn KnobsDatabase, _id: salsa::Id) -> CycleValue { MIN } #[test_log::test] fn test_iteration_count_mismatch() { crate::sync::check(|| { tracing::debug!("Starting new run"); let db_t1 = Knobs::default(); let db_t2 = db_t1.clone(); let db_t3 = db_t1.clone(); let db_t4 = db_t1.clone(); // Thread 1: Starts with query_a - main cycle head let t1 = thread::spawn(move || { let _span = tracing::debug_span!("t1", thread_id = ?thread::current().id()).entered(); query_a(&db_t1) }); // Thread 2: Starts with query_d - separate cycle that will have different iteration let t2 = thread::spawn(move || { let _span = tracing::debug_span!("t2", thread_id = ?thread::current().id()).entered(); query_d(&db_t2) }); // Thread 3: Starts with query_f after others have started let t3 = thread::spawn(move || { let _span = tracing::debug_span!("t3", thread_id = ?thread::current().id()).entered(); query_f(&db_t3) }); // Thread 4: Queries b which depends on multiple cycles let t4 = thread::spawn(move || { let _span = tracing::debug_span!("t4", thread_id = ?thread::current().id()).entered(); query_b(&db_t4) }); let r_t1 = t1.join().unwrap(); let r_t2 = t2.join().unwrap(); let r_t3 = t3.join().unwrap(); let r_t4 = t4.join().unwrap(); // All queries should converge to the same value assert_eq!(r_t1, r_t2); assert_eq!(r_t2, r_t3); assert_eq!(r_t3, r_t4); // They should have computed a non-initial value assert!(r_t1.0 > MIN.0); }); } salsa-0.26.2/tests/parallel/cycle_nested_deep.rs000064400000000000000000000053411046102023000177630ustar 00000000000000//! Test a deeply nested-cycle scenario across multiple threads. //! //! The trick is that different threads call into the same cycle from different entry queries. //! //! * Thread 1: `a` -> b -> c (which calls back into d, e, b, a) //! * Thread 2: `b` //! * Thread 3: `d` -> `c` //! * Thread 4: `e` -> `c` use crate::sync::thread; use crate::{Knobs, KnobsDatabase}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); const MIN: CycleValue = CycleValue(0); const MAX: CycleValue = CycleValue(3); #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn KnobsDatabase) -> CycleValue { query_b(db) } #[salsa::tracked(cycle_initial=initial)] fn query_b(db: &dyn KnobsDatabase) -> CycleValue { let c_value = query_c(db); CycleValue(c_value.0 + 1).min(MAX) } #[salsa::tracked(cycle_initial=initial)] fn query_c(db: &dyn KnobsDatabase) -> CycleValue { let d_value = query_d(db); let e_value = query_e(db); let b_value = query_b(db); let a_value = query_a(db); CycleValue(d_value.0.max(e_value.0).max(b_value.0).max(a_value.0)) } #[salsa::tracked(cycle_initial=initial)] fn query_d(db: &dyn KnobsDatabase) -> CycleValue { query_c(db) } #[salsa::tracked(cycle_initial=initial)] fn query_e(db: &dyn KnobsDatabase) -> CycleValue { query_c(db) } fn initial(_db: &dyn KnobsDatabase, _id: salsa::Id) -> CycleValue { MIN } #[test_log::test] fn the_test() { crate::sync::check(|| { tracing::debug!("Starting new run"); let db_t1 = Knobs::default(); let db_t2 = db_t1.clone(); let db_t3 = db_t1.clone(); let db_t4 = db_t1.clone(); let t1 = thread::spawn(move || { let _span = tracing::debug_span!("t1", thread_id = ?thread::current().id()).entered(); let result = query_a(&db_t1); db_t1.signal(1); result }); let t2 = thread::spawn(move || { let _span = tracing::debug_span!("t4", thread_id = ?thread::current().id()).entered(); db_t4.wait_for(1); query_b(&db_t4) }); let t3 = thread::spawn(move || { let _span = tracing::debug_span!("t2", thread_id = ?thread::current().id()).entered(); db_t2.wait_for(1); query_d(&db_t2) }); let t4 = thread::spawn(move || { let _span = tracing::debug_span!("t3", thread_id = ?thread::current().id()).entered(); db_t3.wait_for(1); query_e(&db_t3) }); let r_t1 = t1.join().unwrap(); let r_t2 = t2.join().unwrap(); let r_t3 = t3.join().unwrap(); let r_t4 = t4.join().unwrap(); assert_eq!((r_t1, r_t2, r_t3, r_t4), (MAX, MAX, MAX, MAX)); }); } salsa-0.26.2/tests/parallel/cycle_nested_deep_conditional.rs000064400000000000000000000057351046102023000223550ustar 00000000000000//! Test a deeply nested-cycle scenario where cycles have changing query dependencies. //! //! The trick is that different threads call into the same cycle from different entry queries and //! the cycle heads change over different iterations //! //! * Thread 1: `a` -> b -> c //! * Thread 2: `b` //! * Thread 3: `d` -> `c` //! * Thread 4: `e` -> `c` //! //! `c` calls: //! * `d` and `a` in the first few iterations //! * `d`, `b` and `e` in the last iterations use crate::sync::thread; use crate::{Knobs, KnobsDatabase}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); const MIN: CycleValue = CycleValue(0); const MAX: CycleValue = CycleValue(3); #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn KnobsDatabase) -> CycleValue { query_b(db) } #[salsa::tracked(cycle_initial=initial)] fn query_b(db: &dyn KnobsDatabase) -> CycleValue { let c_value = query_c(db); CycleValue(c_value.0 + 1).min(MAX) } #[salsa::tracked(cycle_initial=initial)] fn query_c(db: &dyn KnobsDatabase) -> CycleValue { let d_value = query_d(db); if d_value > CycleValue(0) { let e_value = query_e(db); let b_value = query_b(db); CycleValue(d_value.0.max(e_value.0).max(b_value.0)) } else { let a_value = query_a(db); CycleValue(d_value.0.max(a_value.0)) } } #[salsa::tracked(cycle_initial=initial)] fn query_d(db: &dyn KnobsDatabase) -> CycleValue { query_c(db) } #[salsa::tracked(cycle_initial=initial)] fn query_e(db: &dyn KnobsDatabase) -> CycleValue { query_c(db) } fn initial(_db: &dyn KnobsDatabase, _id: salsa::Id) -> CycleValue { MIN } #[test_log::test] fn the_test() { crate::sync::check(|| { tracing::debug!("Starting new run"); let db_t1 = Knobs::default(); let db_t2 = db_t1.clone(); let db_t3 = db_t1.clone(); let db_t4 = db_t1.clone(); let t1 = thread::spawn(move || { let _span = tracing::debug_span!("t1", thread_id = ?thread::current().id()).entered(); let result = query_a(&db_t1); db_t1.signal(1); result }); let t2 = thread::spawn(move || { let _span = tracing::debug_span!("t2", thread_id = ?thread::current().id()).entered(); db_t4.wait_for(1); query_b(&db_t4) }); let t3 = thread::spawn(move || { let _span = tracing::debug_span!("t3", thread_id = ?thread::current().id()).entered(); db_t2.wait_for(1); query_d(&db_t2) }); let t4 = thread::spawn(move || { let _span = tracing::debug_span!("t4", thread_id = ?thread::current().id()).entered(); db_t3.wait_for(1); query_e(&db_t3) }); let r_t1 = t1.join().unwrap(); let r_t2 = t2.join().unwrap(); let r_t3 = t3.join().unwrap(); let r_t4 = t4.join().unwrap(); assert_eq!((r_t1, r_t2, r_t3, r_t4), (MAX, MAX, MAX, MAX)); }); } salsa-0.26.2/tests/parallel/cycle_nested_deep_conditional_changed.rs000064400000000000000000000101511046102023000240120ustar 00000000000000//! Test a deeply nested-cycle scenario where cycles have changing query dependencies. //! //! The trick is that different threads call into the same cycle from different entry queries and //! the cycle heads change over different iterations //! //! * Thread 1: `a` -> `b` -> `c` //! * Thread 2: `b` //! * Thread 3: `d` -> `c` //! * Thread 4: `e` -> `c` //! //! `c` calls: //! * `d` and `a` in the first few iterations //! * `d`, `b` and `e` in the last iterations //! //! Specifically, the maybe_changed_after flow. use crate::sync::thread; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); const MIN: CycleValue = CycleValue(0); const MAX: CycleValue = CycleValue(3); #[salsa::input] struct Input { value: u32, } #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn salsa::Database, input: Input) -> CycleValue { query_b(db, input) } #[salsa::tracked(cycle_initial=initial)] fn query_b(db: &dyn salsa::Database, input: Input) -> CycleValue { let c_value = query_c(db, input); CycleValue(c_value.0 + input.value(db).max(1)).min(MAX) } #[salsa::tracked(cycle_initial=initial)] fn query_c(db: &dyn salsa::Database, input: Input) -> CycleValue { let d_value = query_d(db, input); if d_value > CycleValue(0) { let e_value = query_e(db, input); let b_value = query_b(db, input); CycleValue(d_value.0.max(e_value.0).max(b_value.0)) } else { let a_value = query_a(db, input); CycleValue(d_value.0.max(a_value.0)) } } #[salsa::tracked(cycle_initial=initial)] fn query_d(db: &dyn salsa::Database, input: Input) -> CycleValue { query_c(db, input) } #[salsa::tracked(cycle_initial=initial)] fn query_e(db: &dyn salsa::Database, input: Input) -> CycleValue { query_c(db, input) } fn initial(_db: &dyn salsa::Database, _id: salsa::Id, _input: Input) -> CycleValue { MIN } #[test_log::test] fn the_test() { use crate::sync; use salsa::Setter as _; sync::check(|| { tracing::debug!("Starting new run"); // This is a bit silly but it works around https://github.com/awslabs/shuttle/issues/192 static INITIALIZE: sync::Mutex> = sync::Mutex::new(None); fn get_db(f: impl FnOnce(&salsa::DatabaseImpl, Input)) -> (salsa::DatabaseImpl, Input) { let mut shared = INITIALIZE.lock().unwrap(); if let Some((db, input)) = shared.as_ref() { return (db.clone(), *input); } let mut db = salsa::DatabaseImpl::default(); let input = Input::new(&db, 0); f(&db, input); input.set_value(&mut db).to(1); *shared = Some((db.clone(), input)); (db, input) } let t1 = thread::spawn(move || { let _span = tracing::info_span!("t1", thread_id = ?thread::current().id()).entered(); let (db, input) = get_db(|db, input| { query_a(db, input); }); query_a(&db, input) }); let t2 = thread::spawn(move || { let _span = tracing::info_span!("t2", thread_id = ?thread::current().id()).entered(); let (db, input) = get_db(|db, input| { query_b(db, input); }); query_b(&db, input) }); let t3 = thread::spawn(move || { let _span = tracing::info_span!("t3", thread_id = ?thread::current().id()).entered(); let (db, input) = get_db(|db, input| { query_d(db, input); }); query_d(&db, input) }); let t4 = thread::spawn(move || { let _span = tracing::info_span!("t4", thread_id = ?thread::current().id()).entered(); let (db, input) = get_db(|db, input| { query_e(db, input); }); query_e(&db, input) }); let r_t1 = t1.join().unwrap(); let r_t2 = t2.join().unwrap(); let r_t3 = t3.join().unwrap(); let r_t4 = t4.join().unwrap(); assert_eq!((r_t1, r_t2, r_t3, r_t4), (MAX, MAX, MAX, MAX)); }); } salsa-0.26.2/tests/parallel/cycle_nested_deep_panic.rs000064400000000000000000000073711046102023000211420ustar 00000000000000// Shuttle doesn't like panics inside of its runtime. #![cfg(not(feature = "shuttle"))] //! Tests that salsa doesn't get stuck after a panic in a nested cycle function. use crate::sync::thread; use crate::{Knobs, KnobsDatabase}; use std::fmt; use std::panic::catch_unwind; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); const MIN: CycleValue = CycleValue(0); const MAX: CycleValue = CycleValue(3); #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn KnobsDatabase) -> CycleValue { query_b(db) } #[salsa::tracked(cycle_initial=initial)] fn query_b(db: &dyn KnobsDatabase) -> CycleValue { let c_value = query_c(db); CycleValue(c_value.0 + 1).min(MAX) } #[salsa::tracked] fn query_c(db: &dyn KnobsDatabase) -> CycleValue { let d_value = query_d(db); if d_value > CycleValue(0) { let e_value = query_e(db); let b_value = query_b(db); CycleValue(d_value.0.max(e_value.0).max(b_value.0)) } else { let a_value = query_a(db); CycleValue(d_value.0.max(a_value.0)) } } #[salsa::tracked(cycle_initial=initial)] fn query_d(db: &dyn KnobsDatabase) -> CycleValue { query_b(db) } #[salsa::tracked(cycle_initial=initial)] fn query_e(db: &dyn KnobsDatabase) -> CycleValue { query_c(db) } fn initial(_db: &dyn KnobsDatabase, _id: salsa::Id) -> CycleValue { MIN } fn run() { tracing::debug!("Starting new run"); let db_t1 = Knobs::default(); let db_t2 = db_t1.clone(); let db_t3 = db_t1.clone(); let db_t4 = db_t1.clone(); let t1 = thread::spawn(move || { let _span = tracing::debug_span!("t1", thread_id = ?thread::current().id()).entered(); catch_unwind(|| { db_t1.wait_for(1); query_a(&db_t1) }) }); let t2 = thread::spawn(move || { let _span = tracing::debug_span!("t2", thread_id = ?thread::current().id()).entered(); catch_unwind(|| { db_t2.wait_for(1); query_b(&db_t2) }) }); let t3 = thread::spawn(move || { let _span = tracing::debug_span!("t3", thread_id = ?thread::current().id()).entered(); catch_unwind(|| { db_t3.signal(2); query_d(&db_t3) }) }); let r_t1 = t1.join().unwrap(); let r_t2 = t2.join().unwrap(); let r_t3 = t3.join().unwrap(); assert_is_set_cycle_error(r_t1); assert_is_set_cycle_error(r_t2); assert_is_set_cycle_error(r_t3); // Pulling the cycle again at a later point should still result in a panic. assert_is_set_cycle_error(catch_unwind(|| query_d(&db_t4))); } #[test_log::test] fn the_test() { let count = if cfg!(miri) { 1 } else { 200 }; for _ in 0..count { run() } } #[track_caller] fn assert_is_set_cycle_error(result: Result>) where T: fmt::Debug, { let err = result.expect_err("expected an error"); if let Some(message) = err.downcast_ref::<&str>() { assert!( message.contains("set cycle_fn/cycle_initial to fixpoint iterate"), "Expected error message to contain 'set cycle_fn/cycle_initial to fixpoint iterate', but got: {}", message ); } else if let Some(message) = err.downcast_ref::() { assert!( message.contains("set cycle_fn/cycle_initial to fixpoint iterate"), "Expected error message to contain 'set cycle_fn/cycle_initial to fixpoint iterate', but got: {}", message ); } else if err.downcast_ref::().is_some() { // This is okay, because Salsa throws a Cancelled::PropagatedPanic when a panic occurs in a query // that it blocks on. } else { std::panic::resume_unwind(err); } } salsa-0.26.2/tests/parallel/cycle_nested_three_threads.rs000064400000000000000000000050011046102023000216600ustar 00000000000000//! Test a nested-cycle scenario across three threads: //! //! ```text //! Thread T1 Thread T2 Thread T3 //! --------- --------- --------- //! | | | //! v | | //! query_a() | | //! ^ | v | //! | +------------> query_b() | //! | ^ | v //! | | +------------> query_c() //! | | | //! +------------------+--------------------+ //! //! ``` use crate::sync::thread; use crate::{Knobs, KnobsDatabase}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); const MIN: CycleValue = CycleValue(0); const MAX: CycleValue = CycleValue(3); // Signal 1: T1 has entered `query_a` // Signal 2: T2 has entered `query_b` // Signal 3: T3 has entered `query_c` #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn KnobsDatabase) -> CycleValue { db.signal(1); db.wait_for(3); query_b(db) } #[salsa::tracked(cycle_initial=initial)] fn query_b(db: &dyn KnobsDatabase) -> CycleValue { db.wait_for(1); db.signal(2); db.wait_for(3); let c_value = query_c(db); CycleValue(c_value.0 + 1).min(MAX) } #[salsa::tracked(cycle_initial=initial)] fn query_c(db: &dyn KnobsDatabase) -> CycleValue { db.wait_for(2); db.signal(3); let a_value = query_a(db); let b_value = query_b(db); CycleValue(a_value.0.max(b_value.0)) } fn initial(_db: &dyn KnobsDatabase, _id: salsa::Id) -> CycleValue { MIN } #[test_log::test] fn the_test() { crate::sync::check(|| { let db_t1 = Knobs::default(); let db_t2 = db_t1.clone(); let db_t3 = db_t1.clone(); let t1 = thread::spawn(move || { let _span = tracing::info_span!("t1", thread_id = ?thread::current().id()).entered(); query_a(&db_t1) }); let t2 = thread::spawn(move || { let _span = tracing::info_span!("t2", thread_id = ?thread::current().id()).entered(); query_b(&db_t2) }); let t3 = thread::spawn(move || { let _span = tracing::info_span!("t3", thread_id = ?thread::current().id()).entered(); query_c(&db_t3) }); let r_t1 = t1.join().unwrap(); let r_t2 = t2.join().unwrap(); let r_t3 = t3.join().unwrap(); assert_eq!((r_t1, r_t2, r_t3), (MAX, MAX, MAX)); }); } salsa-0.26.2/tests/parallel/cycle_nested_three_threads_changed.rs000064400000000000000000000063451046102023000233450ustar 00000000000000//! Test a nested-cycle scenario across three threads: //! //! ```text //! Thread T1 Thread T2 Thread T3 //! --------- --------- --------- //! | | | //! v | | //! query_a() | | //! ^ | v | //! | +------------> query_b() | //! | ^ | v //! | | +------------> query_c() //! | | | //! +------------------+--------------------+ //! ``` //! //! Specifically, the maybe_changed_after flow. use crate::sync; use crate::sync::thread; use salsa::{DatabaseImpl, Setter as _}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); const MIN: CycleValue = CycleValue(0); const MAX: CycleValue = CycleValue(3); #[salsa::input] struct Input { value: u32, } // Signal 1: T1 has entered `query_a` // Signal 2: T2 has entered `query_b` // Signal 3: T3 has entered `query_c` #[salsa::tracked(cycle_initial=initial)] fn query_a(db: &dyn salsa::Database, input: Input) -> CycleValue { query_b(db, input) } #[salsa::tracked(cycle_initial=initial)] fn query_b(db: &dyn salsa::Database, input: Input) -> CycleValue { let c_value = query_c(db, input); CycleValue(c_value.0 + input.value(db)).min(MAX) } #[salsa::tracked(cycle_initial=initial)] fn query_c(db: &dyn salsa::Database, input: Input) -> CycleValue { let a_value = query_a(db, input); let b_value = query_b(db, input); CycleValue(a_value.0.max(b_value.0)) } fn initial(_db: &dyn salsa::Database, _id: salsa::Id, _input: Input) -> CycleValue { MIN } #[test_log::test] fn the_test() { crate::sync::check(move || { // This is a bit silly but it works around https://github.com/awslabs/shuttle/issues/192 static INITIALIZE: sync::Mutex> = sync::Mutex::new(None); fn get_db(f: impl FnOnce(&salsa::DatabaseImpl, Input)) -> (salsa::DatabaseImpl, Input) { let mut shared = INITIALIZE.lock().unwrap(); if let Some((db, input)) = shared.as_ref() { return (db.clone(), *input); } let mut db = DatabaseImpl::default(); let input = Input::new(&db, 1); f(&db, input); input.set_value(&mut db).to(2); *shared = Some((db.clone(), input)); (db, input) } let t1 = thread::spawn(|| { let (db, input) = get_db(|db, input| { query_a(db, input); }); query_a(&db, input) }); let t2 = thread::spawn(|| { let (db, input) = get_db(|db, input| { query_b(db, input); }); query_b(&db, input) }); let t3 = thread::spawn(|| { let (db, input) = get_db(|db, input| { query_c(db, input); }); query_c(&db, input) }); let r_t1 = t1.join().unwrap(); let r_t2 = t2.join().unwrap(); let r_t3 = t3.join().unwrap(); assert_eq!((r_t1, r_t2, r_t3), (MAX, MAX, MAX)); }); } salsa-0.26.2/tests/parallel/cycle_panic.rs000064400000000000000000000021571046102023000166000ustar 00000000000000// Shuttle doesn't like panics inside of its runtime. #![cfg(not(feature = "shuttle"))] //! Test for panic in cycle recovery function, in cross-thread cycle. use crate::setup::{Knobs, KnobsDatabase}; #[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=initial)] fn query_a(db: &dyn KnobsDatabase) -> u32 { db.signal(1); db.wait_for(2); query_b(db) } #[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=initial)] fn query_b(db: &dyn KnobsDatabase) -> u32 { db.wait_for(1); db.signal(2); query_a(db) + 1 } fn cycle_fn( _db: &dyn KnobsDatabase, _cycle: &salsa::Cycle, _last_provisional_value: &u32, _value: u32, ) -> u32 { panic!("cancel!") } fn initial(_db: &dyn KnobsDatabase, _id: salsa::Id) -> u32 { 0 } #[test] fn execute() { let db = Knobs::default(); let db_t1 = db.clone(); let t1 = std::thread::spawn(move || query_a(&db_t1)); let db_t2 = db.clone(); let t2 = std::thread::spawn(move || query_b(&db_t2)); // The main thing here is that we don't deadlock. let (r1, r2) = (t1.join(), t2.join()); assert!(r1.is_err()); assert!(r2.is_err()); } salsa-0.26.2/tests/parallel/cycle_provisional_depending_on_itself.rs000064400000000000000000000065601046102023000241340ustar 00000000000000//! Test a specific cycle scenario: //! //! 1. Thread T1 calls `a` which calls `b` //! 2. Thread T2 calls `c` which calls `b` (blocks on T1 for `b`). The ordering here is important! //! 3. Thread T1: `b` calls `c` and `a`, both trigger a cycle and Salsa returns a fixpoint initial values (with `c` and `a` as cycle heads). //! 4. Thread T1: `b` is released (its not in its own cycle heads), `Memo::provisional_retry` blocks blocks on `T2` because `c` is in its cycle heads //! 5. Thread T2: Iterates `c`, blocks on T1 when reading `a`. //! 6. Thread T1: Completes the first itaration of `a`, inserting a provisional that depends on `c` and itself (`a`). //! Starts a new iteration where it executes `b`. Calling `query_a` hits a cycle: //! //! 1. `fetch_cold` returns the current provisional for `a` that depends both on `a` (owned by itself) and `c` (has no cycle heads). //! 2. `Memo::provisional_retry`: Awaits `c` (which has no cycle heads anymore). //! - Before: it skipped over the dependency key `a` that it is holding itself. It sees that `c` is final, so it retries (which gets us back to 6.1) //! - Now: Return the provisional memo and allow the outer cycle to resolve. //! //! The desired behavior here is that: //! 1. `t1`: completes the first iteration of b //! 2. `t2`: completes the cycle `c`, up to where it only depends on `a`, now blocks on `a` //! 3. `t1`: Iterates on `a`, finalizes the memo use crate::sync::thread; use crate::setup::{Knobs, KnobsDatabase}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)] struct CycleValue(u32); const MIN: CycleValue = CycleValue(0); const MAX: CycleValue = CycleValue(1); #[salsa::tracked(cycle_initial=cycle_initial)] fn query_a(db: &dyn KnobsDatabase) -> CycleValue { query_b(db) } #[salsa::tracked(cycle_initial=cycle_initial)] fn query_b(db: &dyn KnobsDatabase) -> CycleValue { // Wait for thread 2 to have entered `query_c`. tracing::debug!("Wait for signal 1 from thread 2"); db.wait_for(1); // Unblock query_c on thread 2 db.signal(2); tracing::debug!("Signal 2 for thread 2"); let c_value = query_c(db); tracing::debug!("query_b: c = {:?}", c_value); let a_value = query_a(db); tracing::debug!("query_b: a = {:?}", a_value); CycleValue(a_value.0 + 1).min(MAX) } #[salsa::tracked(cycle_initial=cycle_initial)] fn query_c(db: &dyn KnobsDatabase) -> CycleValue { tracing::debug!("query_c: signaling thread1 to call c"); db.signal(1); tracing::debug!("query_c: waiting for signal"); // Wait for thread 1 to acquire the lock on query_b db.wait_for(1); let b = query_b(db); tracing::debug!("query_c: b = {:?}", b); b } fn cycle_initial(_db: &dyn KnobsDatabase, _id: salsa::Id) -> CycleValue { MIN } #[test_log::test] fn the_test() { crate::sync::check(|| { let db_t1 = Knobs::default(); let db_t2 = db_t1.clone(); let t1 = thread::spawn(move || { let _span = tracing::debug_span!("t1", thread_id = ?thread::current().id()).entered(); query_a(&db_t1) }); let t2 = thread::spawn(move || { let _span = tracing::debug_span!("t2", thread_id = ?thread::current().id()).entered(); query_c(&db_t2) }); let (r_t1, r_t2) = (t1.join().unwrap(), t2.join().unwrap()); assert_eq!((r_t1, r_t2), (MAX, MAX)); }); } salsa-0.26.2/tests/parallel/main.rs000064400000000000000000000017021046102023000152460ustar 00000000000000#![cfg(feature = "inventory")] mod setup; mod signal; mod cancellation_token_cycle_nested; mod cancellation_token_multi_blocked; mod cancellation_token_recomputes; mod cycle_a_t1_b_t2; mod cycle_a_t1_b_t2_fallback; mod cycle_ab_peeping_c; mod cycle_iteration_mismatch; mod cycle_nested_deep; mod cycle_nested_deep_conditional; mod cycle_nested_deep_conditional_changed; mod cycle_nested_deep_panic; mod cycle_nested_three_threads; mod cycle_nested_three_threads_changed; mod cycle_panic; mod cycle_provisional_depending_on_itself; #[cfg(not(feature = "shuttle"))] pub(crate) mod sync { pub use std::sync::*; pub use std::thread; pub fn check(f: impl Fn() + Send + Sync + 'static) { f(); } } #[cfg(feature = "shuttle")] pub(crate) mod sync { pub use shuttle::sync::*; pub use shuttle::thread; pub fn check(f: impl Fn() + Send + Sync + 'static) { shuttle::check_pct(f, 2500, 50); } } pub(crate) use setup::*; salsa-0.26.2/tests/parallel/setup.rs000064400000000000000000000064051046102023000154670ustar 00000000000000#![allow(dead_code)] use salsa::{Database, Storage}; use super::signal::Signal; use super::sync::Arc; use super::sync::atomic::{AtomicUsize, Ordering}; /// Various "knobs" and utilities used by tests to force /// a certain behavior. #[salsa::db] pub(crate) trait KnobsDatabase: Database { /// Signal that we are entering stage `stage`. fn signal(&self, stage: usize); /// Wait until we reach stage `stage` (no-op if we have already reached that stage). fn wait_for(&self, stage: usize); } /// A database containing various "knobs" that can be used to customize how the queries /// behave on one specific thread. Note that this state is /// intentionally thread-local (apart from `signal`). #[salsa::db] pub(crate) struct Knobs { storage: salsa::Storage, /// A kind of flexible barrier used to coordinate execution across /// threads to ensure we reach various weird states. pub(crate) signal: Arc, /// When this database is about to block, send this signal. signal_on_will_block: Arc, /// When this database has set the cancellation flag, send this signal. signal_on_did_cancel: Arc, } impl Knobs { pub fn signal_on_did_cancel(&self, stage: usize) { self.signal_on_did_cancel.store(stage, Ordering::Release); } pub fn signal_on_will_block(&self, stage: usize) { self.signal_on_will_block.store(stage, Ordering::Release); } } impl Clone for Knobs { #[track_caller] fn clone(&self) -> Self { // To avoid mistakes, check that when we clone, we haven't customized this behavior yet assert_eq!(self.signal_on_will_block.load(Ordering::Acquire), 0); assert_eq!(self.signal_on_did_cancel.load(Ordering::Acquire), 0); Self { storage: self.storage.clone(), signal: self.signal.clone(), signal_on_will_block: self.signal_on_will_block.clone(), signal_on_did_cancel: self.signal_on_did_cancel.clone(), } } } impl Default for Knobs { fn default() -> Self { let signal = >::default(); let signal_on_will_block = Arc::new(AtomicUsize::new(0)); let signal_on_did_cancel = Arc::new(AtomicUsize::new(0)); Self { storage: Storage::new(Some(Box::new({ let signal = signal.clone(); let signal_on_will_block = signal_on_will_block.clone(); let signal_on_did_cancel = signal_on_did_cancel.clone(); move |event| match event.kind { salsa::EventKind::WillBlockOn { .. } => { signal.signal(signal_on_will_block.load(Ordering::Acquire)); } salsa::EventKind::DidSetCancellationFlag => { signal.signal(signal_on_did_cancel.load(Ordering::Acquire)); } _ => {} } }))), signal, signal_on_will_block, signal_on_did_cancel, } } } #[salsa::db] impl salsa::Database for Knobs {} #[salsa::db] impl KnobsDatabase for Knobs { fn signal(&self, stage: usize) { self.signal.signal(stage); } fn wait_for(&self, stage: usize) { self.signal.wait_for(stage); } } salsa-0.26.2/tests/parallel/signal.rs000064400000000000000000000026231046102023000156020ustar 00000000000000#![allow(unused)] use super::sync::{Condvar, Mutex}; #[derive(Default)] pub(crate) struct Signal { value: Mutex, cond_var: Condvar, } impl Signal { pub(crate) fn signal(&self, stage: usize) { // When running with shuttle we want to explore as many possible // executions, so we avoid signals entirely. #[cfg(not(feature = "shuttle"))] { // This check avoids acquiring the lock for things that will // clearly be a no-op. Not *necessary* but helps to ensure we // are more likely to encounter weird race conditions; // otherwise calls to `sum` will tend to be unnecessarily // synchronous. if stage > 0 { let mut v = self.value.lock().unwrap(); if stage > *v { *v = stage; self.cond_var.notify_all(); } } } } /// Waits until the given condition is true; the fn is invoked /// with the current stage. pub(crate) fn wait_for(&self, stage: usize) { #[cfg(not(feature = "shuttle"))] { // As above, avoid lock if clearly a no-op. if stage > 0 { let mut v = self.value.lock().unwrap(); while *v < stage { v = self.cond_var.wait(v).unwrap(); } } } } } salsa-0.26.2/tests/persistence.rs000064400000000000000000000363271046102023000150650ustar 00000000000000#![cfg(all(feature = "persistence", feature = "inventory"))] mod common; use common::LogDatabase; use salsa::{Database, Durability, Setter}; use expect_test::expect; #[salsa::input(persist)] struct MyInput { field: usize, } #[salsa::input(persist, singleton)] struct MySingleton { field: usize, } #[salsa::interned(persist)] struct MyInterned<'db> { field: String, } #[salsa::tracked(persist)] struct MyTracked<'db> { field: String, } #[salsa::tracked(persist)] fn unit_to_interned(db: &dyn salsa::Database) -> MyInterned<'_> { MyInterned::new(db, "a".repeat(50)) } #[salsa::tracked(persist)] fn input_to_tracked(db: &dyn salsa::Database, input: MyInput) -> MyTracked<'_> { MyTracked::new(db, "a".repeat(input.field(db))) } #[salsa::tracked(persist)] fn input_pair_to_string(db: &dyn salsa::Database, input1: MyInput, input2: MyInput) -> String { "a".repeat(input1.field(db) + input2.field(db)) } #[test] fn everything() { let mut db = common::LoggerDatabase::default(); let _input1 = MyInput::new(&db, 1); let _input2 = MyInput::new(&db, 2); let serialized = serde_json::to_string_pretty(&::as_serialize(&mut db)).unwrap(); let expected = expect![[r#" { "runtime": { "revisions": [ 1, 1, 1 ] }, "ingredients": { "0": { "1": { "durabilities": [ 0 ], "revisions": [ 1 ], "fields": [ 1 ] }, "2": { "durabilities": [ 0 ], "revisions": [ 1 ], "fields": [ 2 ] } } } }"#]]; expected.assert_eq(&serialized); let input1 = MyInput::new(&db, 1); let input2 = MyInput::new(&db, 2); let _singleton = MySingleton::new(&db, 1); let _out = unit_to_interned(&db); let _out = input_to_tracked(&db, input1); let _out = input_pair_to_string(&db, input1, input2); let serialized = serde_json::to_string_pretty(&::as_serialize(&mut db)).unwrap(); let expected = expect![[r#" { "runtime": { "revisions": [ 1, 1, 1 ] }, "ingredients": { "0": { "1": { "durabilities": [ 0 ], "revisions": [ 1 ], "fields": [ 1 ] }, "2": { "durabilities": [ 0 ], "revisions": [ 1 ], "fields": [ 2 ] }, "3": { "durabilities": [ 0 ], "revisions": [ 1 ], "fields": [ 1 ] }, "4": { "durabilities": [ 0 ], "revisions": [ 1 ], "fields": [ 2 ] } }, "2": { "1025": { "durabilities": [ 0 ], "revisions": [ 1 ], "fields": [ 1 ] } }, "4": { "3073": { "durability": 2, "last_interned_at": 1, "fields": [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ] } }, "5": { "4097": { "durability": 0, "updated_at": 1, "revisions": [], "fields": [ "a" ] } }, "7": { "5121": { "durability": 2, "last_interned_at": 18446744073709551615, "fields": [ 3, 4 ] } }, "19": { "2049": { "durability": 2, "last_interned_at": 18446744073709551615, "fields": null } }, "6": { "7:5121": { "value": "aaa", "verified_at": 1, "revisions": { "changed_at": 1, "durability": 0, "origin": { "Derived": [ [ 3, 1 ], [ 4, 1 ] ] }, "verified_final": true, "extra": null } } }, "8": { "0:3": { "value": 4097, "verified_at": 1, "revisions": { "changed_at": 1, "durability": 0, "origin": { "Derived": [ [ 3, 1 ] ] }, "verified_final": true, "extra": { "tracked_struct_ids": [ [ { "ingredient_index": 5, "hash": 6073466998405137972, "disambiguator": 0 }, 4097 ] ], "cycle_heads": [], "iteration": 0 } } } }, "18": { "19:2049": { "value": 3073, "verified_at": 1, "revisions": { "changed_at": 1, "durability": 2, "origin": { "Derived": [ [ 3073, 4 ] ] }, "verified_final": true, "extra": null } } } } }"#]]; expected.assert_eq(&serialized); let mut db = common::EventLoggerDatabase::default(); ::deserialize( &mut db, &mut serde_json::Deserializer::from_str(&serialized), ) .unwrap(); assert_eq!(MySingleton::get(&db).field(&db), 1); let _out = unit_to_interned(&db); let _out = input_to_tracked(&db, input1); let _out = input_pair_to_string(&db, input1, input2); // The structs are not recreated, and the queries are not re-executed. db.assert_logs(expect![[r#" [ "DidSetCancellationFlag", "WillCheckCancellation", "WillCheckCancellation", "WillCheckCancellation", ]"#]]); } #[test] fn partial_query() { use salsa::plumbing::ZalsaDatabase; #[salsa::tracked(persist)] fn query(db: &dyn salsa::Database, input: MyInput) -> usize { inner_query(db, input) + 1 } // Note that the inner query is not persisted, but we should still preserve the dependency on `input.field`. #[salsa::tracked] fn inner_query(db: &dyn salsa::Database, input: MyInput) -> usize { input.field(db) } let mut db = common::EventLoggerDatabase::default(); let input = MyInput::new(&db, 0); let result = query(&db, input); assert_eq!(result, 1); let serialized = serde_json::to_string_pretty(&::as_serialize(&mut db)).unwrap(); let expected = expect![[r#" { "runtime": { "revisions": [ 1, 1, 1 ] }, "ingredients": { "0": { "1": { "durabilities": [ 0 ], "revisions": [ 1 ], "fields": [ 0 ] } }, "13": { "0:1": { "value": 1, "verified_at": 1, "revisions": { "changed_at": 1, "durability": 0, "origin": { "Derived": [ [ 1, 1 ] ] }, "verified_final": true, "extra": null } } } } }"#]]; expected.assert_eq(&serialized); let mut db = common::EventLoggerDatabase::default(); ::deserialize( &mut db, &mut serde_json::Deserializer::from_str(&serialized), ) .unwrap(); let input = MyInput::ingredient(&db) .entries(db.zalsa()) .next() .unwrap() .as_struct(); let result = query(&db, input); assert_eq!(result, 1); // The query was not re-executed. db.assert_logs(expect![[r#" [ "DidSetCancellationFlag", "WillCheckCancellation", ]"#]]); input.set_field(&mut db).to(1); let result = query(&db, input); assert_eq!(result, 2); // The query was re-executed afer the input was updated. db.assert_logs(expect![[r#" [ "DidSetCancellationFlag", "WillCheckCancellation", "WillExecute { database_key: query(Id(0)) }", "WillCheckCancellation", "WillExecute { database_key: inner_query(Id(0)) }", ]"#]]); } #[test] fn partial_query_interned() { use salsa::plumbing::{AsId, ZalsaDatabase}; #[salsa::tracked(persist)] fn intern(db: &dyn salsa::Database, input: MyInput, value: usize) -> MyInterned<'_> { do_intern(db, input, value) } // Note that the inner query is not persisted, but we should still preserve the dependency on `MyInterned`. #[salsa::tracked] fn do_intern(db: &dyn salsa::Database, input: MyInput, value: usize) -> MyInterned<'_> { let _i = input.field(db); // Only low durability interned values are garbage collected. MyInterned::new(db, value.to_string()) } let mut db = common::EventLoggerDatabase::default(); let input = MyInput::builder(0).durability(Durability::LOW).new(&db); // Intern `i0`. let i0 = intern(&db, input, 0); assert_eq!(i0.field(&db), "0"); let serialized = serde_json::to_string_pretty(&::as_serialize(&mut db)).unwrap(); let expected = expect![[r#" { "runtime": { "revisions": [ 1, 1, 1 ] }, "ingredients": { "0": { "1": { "durabilities": [ 0 ], "revisions": [ 1 ], "fields": [ 0 ] } }, "4": { "3073": { "durability": 0, "last_interned_at": 1, "fields": [ "0" ] } }, "17": { "1025": { "durability": 2, "last_interned_at": 18446744073709551615, "fields": [ 1, 0 ] } }, "16": { "17:1025": { "value": 3073, "verified_at": 1, "revisions": { "changed_at": 1, "durability": 0, "origin": { "Derived": [ [ 1, 1 ], [ 3073, 4 ] ] }, "verified_final": true, "extra": null } } } } }"#]]; expected.assert_eq(&serialized); let mut db = common::EventLoggerDatabase::default(); ::deserialize( &mut db, &mut serde_json::Deserializer::from_str(&serialized), ) .unwrap(); let input = MyInput::ingredient(&db) .entries(db.zalsa()) .next() .unwrap() .as_struct(); // Re-intern `i0`. let i0 = intern(&db, input, 0); let i0_id = i0.as_id(); assert_eq!(i0.field(&db), "0"); // The query was not re-executed. db.assert_logs(expect![[r#" [ "DidSetCancellationFlag", "WillCheckCancellation", ]"#]]); // Get the garbage collector to consider `i0` stale. for x in 1.. { db.synthetic_write(Durability::LOW); let ix = intern(&db, input, x); let ix_id = ix.as_id(); // We reused the slot of `i0`. if ix_id.index() == i0_id.index() { break; } } // Re-intern `i0` after is has been garbage collected. let i0 = intern(&db, input, 0); // The query was re-executed due to garbage collection, even though no inputs have changed // and the inner query was not persisted. assert_eq!(i0.field(&db), "0"); assert_ne!(i0_id.index(), i0.as_id().index()); } #[test] #[should_panic(expected = "must be persistable")] fn invalid_specified_dependency() { #[salsa::tracked] fn specify(db: &dyn salsa::Database) { let tracked = MyTracked::new(db, "a".to_string()); specified_query::specify(db, tracked, 2222); } #[salsa::tracked(specify, persist)] fn specified_query<'db>(_db: &'db dyn salsa::Database, _tracked: MyTracked<'db>) -> u32 { 0 } let mut db = common::LoggerDatabase::default(); specify(&db); let _serialized = serde_json::to_string_pretty(&::as_serialize(&mut db)).unwrap(); } #[test] fn serialize_nothing() { let mut db = common::LoggerDatabase::default(); let serialized = serde_json::to_string_pretty(&::as_serialize(&mut db)).unwrap(); // Empty ingredients should not be serialized. let expected = expect![[r#" { "runtime": { "revisions": [ 1, 1, 1 ] }, "ingredients": {} }"#]]; expected.assert_eq(&serialized); } salsa-0.26.2/tests/preverify-struct-with-leaked-data-2.rs000064400000000000000000000062741046102023000213360ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. use std::cell::Cell; use common::LogDatabase; use expect_test::expect; mod common; use salsa::{Database, Setter}; use test_log::test; thread_local! { static COUNTER: Cell = const { Cell::new(0) }; } #[salsa::input] struct MyInput { field1: u32, field2: u32, } #[salsa::tracked] struct MyTracked<'db> { #[tracked] counter: usize, } #[salsa::tracked] fn function(db: &dyn Database, input: MyInput) -> (usize, usize) { // Read input 1 let _field1 = input.field1(db); // **BAD:** Leak in the value of the counter non-deterministically let counter = COUNTER.with(|c| c.get()); // Create the tracked struct, which (from salsa's POV), only depends on field1; // but which actually depends on the leaked value. let tracked = MyTracked::new(db, counter); // Read the tracked field let result = counter_field(db, input, tracked); // Read input 2. This will cause us to re-execute on revision 2. let _field2 = input.field2(db); (result, tracked.counter(db)) } #[salsa::tracked] fn counter_field<'db>(db: &'db dyn Database, input: MyInput, tracked: MyTracked<'db>) -> usize { // Read input 2. This will cause us to re-execute on revision 2. let _field2 = input.field2(db); tracked.counter(db) } #[test] fn test_leaked_inputs_ignored() { let mut db = common::EventLoggerDatabase::default(); let input = MyInput::new(&db, 10, 20); let result_in_rev_1 = function(&db, input); db.assert_logs(expect![[r#" [ "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "DidInternValue { key: counter_field::interned_arguments(Id(800)), revision: R1 }", "WillCheckCancellation", "WillExecute { database_key: counter_field(Id(800)) }", ]"#]]); assert_eq!(result_in_rev_1, (0, 0)); // Modify field2 so that `function` is seen to have changed -- // but only *after* the tracked struct is created. input.set_field2(&mut db).to(30); // Also modify the thread-local counter COUNTER.with(|c| c.set(100)); let result_in_rev_2 = function(&db, input); db.assert_logs(expect![[r#" [ "DidSetCancellationFlag", "WillCheckCancellation", "DidValidateInternedValue { key: counter_field::interned_arguments(Id(800)), revision: R2 }", "WillCheckCancellation", "WillExecute { database_key: counter_field(Id(800)) }", "WillExecute { database_key: function(Id(0)) }", "WillCheckCancellation", ]"#]]); // Salsa will re-execute `counter_field` before re-executing // `function` since, from what it can see, no inputs have changed // before `counter_field` is called. This will read the field of // the tracked struct which means it will be *fixed* at `0`. // When we re-execute `counter_field` later, we ignore the new // value of 100 since the struct has already been read during // this revision. // // Contrast with preverify-struct-with-leaked-data.rs. assert_eq!(result_in_rev_2, (0, 0)); } salsa-0.26.2/tests/preverify-struct-with-leaked-data.rs000064400000000000000000000053221046102023000211700ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. use std::cell::Cell; use common::LogDatabase; use expect_test::expect; mod common; use salsa::{Database, Setter}; use test_log::test; thread_local! { static COUNTER: Cell = const { Cell::new(0) }; } #[salsa::input] struct MyInput { field1: u32, field2: u32, } #[salsa::tracked] struct MyTracked<'db> { #[tracked] counter: usize, } #[salsa::tracked] fn function(db: &dyn Database, input: MyInput) -> (usize, usize) { // Read input 1 let _field1 = input.field1(db); // **BAD:** Leak in the value of the counter non-deterministically let counter = COUNTER.with(|c| c.get()); // Create the tracked struct, which (from salsa's POV), only depends on field1; // but which actually depends on the leaked value. let tracked = MyTracked::new(db, counter); // Read the tracked field let result = counter_field(db, tracked); // Read input 2. This will cause us to re-execute on revision 2. let _field2 = input.field2(db); (result, tracked.counter(db)) } #[salsa::tracked] fn counter_field<'db>(db: &'db dyn Database, tracked: MyTracked<'db>) -> usize { tracked.counter(db) } #[test] fn test_leaked_inputs_ignored() { let mut db = common::EventLoggerDatabase::default(); let input = MyInput::new(&db, 10, 20); let result_in_rev_1 = function(&db, input); db.assert_logs(expect![[r#" [ "WillCheckCancellation", "WillExecute { database_key: function(Id(0)) }", "WillCheckCancellation", "WillExecute { database_key: counter_field(Id(400)) }", ]"#]]); assert_eq!(result_in_rev_1, (0, 0)); // Modify field2 so that `function` is seen to have changed -- // but only *after* the tracked struct is created. input.set_field2(&mut db).to(30); // Also modify the thread-local counter COUNTER.with(|c| c.set(100)); let result_in_rev_2 = function(&db, input); db.assert_logs(expect![[r#" [ "DidSetCancellationFlag", "WillCheckCancellation", "WillCheckCancellation", "DidValidateMemoizedValue { database_key: counter_field(Id(400)) }", "WillExecute { database_key: function(Id(0)) }", "WillCheckCancellation", ]"#]]); // Because salsa does not see any way for the tracked // struct to have changed, it will re-use the cached return value // from `counter_field` (`0`). This in turn "locks" the cached // struct so that the new value of 100 is ignored. // // Contrast with preverify-struct-with-leaked-data-2.rs. assert_eq!(result_in_rev_2, (0, 0)); } salsa-0.26.2/tests/return_mode.rs000064400000000000000000000103501046102023000150500ustar 00000000000000#![cfg(feature = "inventory")] use salsa::Database; #[salsa::input] struct DefaultInput { text: String, } #[salsa::tracked] fn default_fn(db: &dyn Database, input: DefaultInput) -> String { let input: String = input.text(db); input } #[test] fn default_test() { salsa::DatabaseImpl::new().attach(|db| { let input = DefaultInput::new(db, "Test".into()); let x: String = default_fn(db, input); expect_test::expect![[r#" "Test" "#]] .assert_debug_eq(&x); }) } #[salsa::input] struct CopyInput { #[returns(copy)] text: &'static str, } #[salsa::tracked(returns(copy))] fn copy_fn(db: &dyn Database, input: CopyInput) -> &'static str { let input: &'static str = input.text(db); input } #[test] fn copy_test() { salsa::DatabaseImpl::new().attach(|db| { let input = CopyInput::new(db, "Test"); let x: &str = copy_fn(db, input); expect_test::expect![[r#" "Test" "#]] .assert_debug_eq(&x); }) } #[salsa::input] struct CloneInput { #[returns(clone)] text: String, } #[salsa::tracked(returns(clone))] fn clone_fn(db: &dyn Database, input: CloneInput) -> String { let input: String = input.text(db); input } #[test] fn clone_test() { salsa::DatabaseImpl::new().attach(|db| { let input = CloneInput::new(db, "Test".into()); let x: String = clone_fn(db, input); expect_test::expect![[r#" "Test" "#]] .assert_debug_eq(&x); }) } #[salsa::input] struct RefInput { #[returns(ref)] text: String, } #[salsa::tracked(returns(ref))] fn ref_fn(db: &dyn Database, input: RefInput) -> String { let input: &String = input.text(db); input.to_owned() } #[test] fn ref_test() { salsa::DatabaseImpl::new().attach(|db| { let input = RefInput::new(db, "Test".into()); let x: &String = ref_fn(db, input); expect_test::expect![[r#" "Test" "#]] .assert_debug_eq(&x); }) } #[salsa::input] struct DerefInput { #[returns(deref)] text: String, } #[salsa::tracked(returns(deref))] fn deref_fn(db: &dyn Database, input: DerefInput) -> String { let input: &str = input.text(db); input.to_owned() } #[test] fn deref_test() { salsa::DatabaseImpl::new().attach(|db| { let input = DerefInput::new(db, "Test".into()); let x: &str = deref_fn(db, input); expect_test::expect![[r#" "Test" "#]] .assert_debug_eq(&x); }) } #[salsa::input] struct AsRefInput { #[returns(as_ref)] text: Option, } #[salsa::tracked(returns(as_ref))] fn as_ref_fn(db: &dyn Database, input: AsRefInput) -> Option { let input: Option<&String> = input.text(db); input.cloned() } #[test] fn as_ref_test() { salsa::DatabaseImpl::new().attach(|db| { let input = AsRefInput::new(db, Some("Test".into())); let x: Option<&String> = as_ref_fn(db, input); expect_test::expect![[r#" Some( "Test", ) "#]] .assert_debug_eq(&x); }) } #[salsa::input] struct AsDerefInput { #[returns(as_deref)] text: Option, } #[salsa::tracked(returns(as_deref))] fn as_deref_fn(db: &dyn Database, input: AsDerefInput) -> Option { let input: Option<&str> = input.text(db); input.map(|s| s.to_owned()) } #[salsa::tracked] impl AsDerefInput { #[salsa::tracked(returns(as_deref))] fn as_deref_method(self, db: &dyn Database) -> Option { let input: Option<&str> = self.text(db); input.map(|s| s.to_owned()) } } #[test] fn as_deref_test() { salsa::DatabaseImpl::new().attach(|db| { let input = AsDerefInput::new(db, Some("Test".into())); let x: Option<&str> = as_deref_fn(db, input); expect_test::expect![[r#" Some( "Test", ) "#]] .assert_debug_eq(&x); }); salsa::DatabaseImpl::new().attach(|db| { let input = AsDerefInput::new(db, Some("Test".into())); let x: Option<&str> = input.as_deref_method(db); expect_test::expect![[r#" Some( "Test", ) "#]] .assert_debug_eq(&x); }); } salsa-0.26.2/tests/singleton.rs000064400000000000000000000021601046102023000145270ustar 00000000000000#![cfg(feature = "inventory")] //! Basic Singleton struct test: //! //! Singleton structs are created only once. Subsequent `get`s and `new`s after creation return the same `Id`. use expect_test::expect; use salsa::Database as _; use test_log::test; #[salsa::input(singleton, debug)] struct MyInput { field: u32, id_field: u16, } #[test] fn basic() { let db = salsa::DatabaseImpl::new(); let input1 = MyInput::new(&db, 3, 4); let input2 = MyInput::get(&db); assert_eq!(input1, input2); let input3 = MyInput::try_get(&db); assert_eq!(Some(input1), input3); } #[test] #[should_panic] fn twice() { let db = salsa::DatabaseImpl::new(); let input1 = MyInput::new(&db, 3, 4); let input2 = MyInput::get(&db); assert_eq!(input1, input2); // should panic here _ = MyInput::new(&db, 3, 5); } #[test] fn debug() { salsa::DatabaseImpl::new().attach(|db| { let input = MyInput::new(db, 3, 4); let actual = format!("{input:?}"); let expected = expect!["MyInput { [salsa id]: Id(0), field: 3, id_field: 4 }"]; expected.assert_eq(&actual); }); } salsa-0.26.2/tests/specify-only-works-if-the-key-is-created-in-the-current-query.rs000064400000000000000000000022131046102023000262730ustar 00000000000000#![cfg(feature = "inventory")] //! Test that `specify` only works if the key is a tracked struct created in the current query. //! compilation succeeds but execution panics #![allow(warnings)] #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn tracked_struct_created_in_another_query<'db>( db: &'db dyn salsa::Database, input: MyInput, ) -> MyTracked<'db> { MyTracked::new(db, input.field(db) * 2) } #[salsa::tracked] fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyTracked<'db> { let t = tracked_struct_created_in_another_query(db, input); if input.field(db) != 0 { tracked_fn_extra::specify(db, t, 2222); } t } #[salsa::tracked(specify)] fn tracked_fn_extra<'db>(_db: &'db dyn salsa::Database, _input: MyTracked<'db>) -> u32 { 0 } #[test] #[should_panic( expected = "can only use `specify` on salsa structs created during the current tracked fn" )] fn execute_when_specified() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 22); let tracked = tracked_fn(&db, input); } salsa-0.26.2/tests/supertype_overlap.rs000064400000000000000000000052731046102023000163250ustar 00000000000000//! Tests that overlapping variant types in supertype enums are detected. #![cfg(feature = "inventory")] use salsa::plumbing::ZalsaDatabase; #[salsa::interned(no_lifetime, debug)] struct Name { text: String, } #[salsa::interned(no_lifetime, debug)] struct Age { value: u32, } #[salsa::input(debug)] struct Input { data: u32, } // ---- Test: direct overlap (same type in two variants) ---- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Supertype)] enum DirectOverlap { First(Name), Second(Name), // same type as First } #[test] #[should_panic(expected = "overlapping variants")] fn direct_overlap_detected() { let db = salsa::DatabaseImpl::new(); let _name = Name::new(&db, "hello".to_string()); let _ = ::lookup_ingredient_index(db.zalsa()); } // ---- Test: transitive overlap (nested supertype contains same type) ---- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Supertype)] enum Inner { Name(Name), Age(Age), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Supertype)] enum TransitiveOverlap { Inner(Inner), // transitively contains Name and Age Name(Name), // overlaps with Inner's Name variant } #[test] #[should_panic(expected = "overlapping variants")] fn transitive_overlap_detected() { let db = salsa::DatabaseImpl::new(); let _name = Name::new(&db, "hello".to_string()); let _ = ::lookup_ingredient_index( db.zalsa(), ); } // ---- Test: no overlap (disjoint types) ---- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Supertype)] enum NoOverlap { Name(Name), Age(Age), } #[test] fn no_overlap_is_fine() { let db = salsa::DatabaseImpl::new(); let _name = Name::new(&db, "hello".to_string()); // This should NOT panic let _ = ::lookup_ingredient_index(db.zalsa()); } // ---- Test: no overlap with nesting (disjoint nested supertypes) ---- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Supertype)] enum DisjointInner { Name(Name), Age(Age), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Supertype)] enum DisjointOuter { Inner(DisjointInner), Input(Input), // Input is not in DisjointInner, so no overlap } #[test] fn disjoint_nesting_is_fine() { let db = salsa::DatabaseImpl::new(); let _name = Name::new(&db, "hello".to_string()); // This should NOT panic let _ = ::lookup_ingredient_index(db.zalsa()); } salsa-0.26.2/tests/synthetic_write.rs000064400000000000000000000020221046102023000157460ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a constant `tracked` fn (has no inputs) //! compiles and executes successfully. #![allow(warnings)] mod common; use common::{LogDatabase, Logger}; use expect_test::expect; use salsa::{Database, DatabaseImpl, Durability, Event, EventKind}; #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] fn tracked_fn(db: &dyn Database, input: MyInput) -> u32 { input.field(db) * 2 } #[test] fn execute() { let mut db = common::ExecuteValidateLoggerDatabase::default(); let input = MyInput::new(&db, 22); assert_eq!(tracked_fn(&db, input), 44); db.assert_logs(expect![[r#" [ "salsa_event(WillExecute { database_key: tracked_fn(Id(0)) })", ]"#]]); // Bumps the revision db.synthetic_write(Durability::LOW); // Query should re-run assert_eq!(tracked_fn(&db, input), 44); db.assert_logs(expect![[r#" [ "salsa_event(DidValidateMemoizedValue { database_key: tracked_fn(Id(0)) })", ]"#]]); } salsa-0.26.2/tests/tracked-struct-id-field-bad-eq.rs000064400000000000000000000017041046102023000202710ustar 00000000000000#![cfg(feature = "inventory")] //! Test an id field whose `PartialEq` impl is always true. use salsa::{Database, Setter}; use test_log::test; #[salsa::input] struct MyInput { field: bool, } #[allow(clippy::derived_hash_with_manual_eq)] #[derive(Eq, Hash, Debug, Clone)] struct BadEq { field: bool, } impl PartialEq for BadEq { fn eq(&self, _other: &Self) -> bool { true } } impl From for BadEq { fn from(value: bool) -> Self { Self { field: value } } } #[salsa::tracked] struct MyTracked<'db> { field: BadEq, } #[salsa::tracked] fn the_fn(db: &dyn Database, input: MyInput) { let tracked0 = MyTracked::new(db, BadEq::from(input.field(db))); assert_eq!(tracked0.field(db).field, input.field(db)); } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, true); the_fn(&db, input); input.set_field(&mut db).to(false); the_fn(&db, input); } salsa-0.26.2/tests/tracked-struct-id-field-bad-hash.rs000064400000000000000000000042741046102023000206140ustar 00000000000000#![cfg(feature = "inventory")] //! Test for a tracked struct where an untracked field has a //! very poorly chosen hash impl (always returns 0). //! //! This demonstrates that tracked struct ids will always change if //! untracked fields on a struct change values, because although struct //! ids are based on the *hash* of the untracked fields, ids are generational //! based on the field values. use salsa::{Database as Db, Setter}; use test_log::test; #[salsa::input] struct MyInput { field: u64, } #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] struct BadHash { field: u64, } impl From for BadHash { fn from(value: u64) -> Self { Self { field: value } } } impl std::hash::Hash for BadHash { fn hash(&self, state: &mut H) { state.write_i16(0); } } #[salsa::tracked] struct MyTracked<'db> { field: BadHash, } #[salsa::tracked] fn the_fn(db: &dyn Db, input: MyInput) { let tracked0 = MyTracked::new(db, BadHash::from(input.field(db))); assert_eq!(tracked0.field(db).field, input.field(db)); } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 1); the_fn(&db, input); input.set_field(&mut db).to(0); the_fn(&db, input); } #[salsa::tracked] fn create_tracked(db: &dyn Db, input: MyInput) -> MyTracked<'_> { MyTracked::new(db, BadHash::from(input.field(db))) } #[salsa::tracked] fn with_tracked<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> u64 { tracked.field(db).field } #[test] fn dependent_query() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 1); let tracked = create_tracked(&db, input); assert_eq!(with_tracked(&db, tracked), 1); input.set_field(&mut db).to(0); // We now re-run the query that creates the tracked struct. // // Salsa will re-use the `MyTracked` struct from the previous revision, // but practically it has been re-created due to generational ids. let tracked = create_tracked(&db, input); assert_eq!(with_tracked(&db, tracked), 0); input.set_field(&mut db).to(2); let tracked = create_tracked(&db, input); assert_eq!(with_tracked(&db, tracked), 2); } salsa-0.26.2/tests/tracked-struct-unchanged-in-new-rev.rs000064400000000000000000000013651046102023000214110ustar 00000000000000#![cfg(feature = "inventory")] use salsa::{Database as Db, Setter}; use test_log::test; #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn tracked_fn(db: &dyn Db, input: MyInput) -> MyTracked<'_> { MyTracked::new(db, input.field(db) / 2) } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let input1 = MyInput::new(&db, 22); let input2 = MyInput::new(&db, 44); let _tracked1 = tracked_fn(&db, input1); let _tracked2 = tracked_fn(&db, input2); // modify the input and change the revision input1.set_field(&mut db).to(24); let tracked2 = tracked_fn(&db, input2); // this should not panic tracked2.field(&db); } salsa-0.26.2/tests/tracked-struct-value-field-bad-eq.rs000064400000000000000000000044451046102023000210160ustar 00000000000000#![cfg(feature = "inventory")] //! Test a field whose `PartialEq` impl is always true. //! This can result in us getting different results than //! if we were to execute from scratch. use expect_test::expect; use salsa::{Database, Setter}; mod common; use common::LogDatabase; use test_log::test; #[salsa::input] struct MyInput { field: bool, } #[allow(clippy::derived_hash_with_manual_eq)] #[derive(Eq, Hash, Debug, Clone)] struct BadEq { field: bool, } impl PartialEq for BadEq { fn eq(&self, _other: &Self) -> bool { true } } impl From for BadEq { fn from(value: bool) -> Self { Self { field: value } } } #[salsa::tracked] struct MyTracked<'db> { #[tracked] field: BadEq, } #[salsa::tracked] fn the_fn(db: &dyn Database, input: MyInput) -> bool { let tracked = make_tracked_struct(db, input); read_tracked_struct(db, tracked) } #[salsa::tracked] fn make_tracked_struct(db: &dyn Database, input: MyInput) -> MyTracked<'_> { MyTracked::new(db, BadEq::from(input.field(db))) } #[salsa::tracked] fn read_tracked_struct<'db>(db: &'db dyn Database, tracked: MyTracked<'db>) -> bool { tracked.field(db).field } #[test] fn execute() { let mut db = common::ExecuteValidateLoggerDatabase::default(); let input = MyInput::new(&db, true); let result = the_fn(&db, input); assert!(result); db.assert_logs(expect![[r#" [ "salsa_event(WillExecute { database_key: the_fn(Id(0)) })", "salsa_event(WillExecute { database_key: make_tracked_struct(Id(0)) })", "salsa_event(WillExecute { database_key: read_tracked_struct(Id(400)) })", ]"#]]); // Update the input to `false` and re-execute. input.set_field(&mut db).to(false); let result = the_fn(&db, input); // If the `Eq` impl were working properly, we would // now return `false`. But because the `Eq` is considered // equal we re-use memoized results and so we get true. assert!(result); db.assert_logs(expect![[r#" [ "salsa_event(WillExecute { database_key: make_tracked_struct(Id(0)) })", "salsa_event(DidValidateMemoizedValue { database_key: read_tracked_struct(Id(400)) })", "salsa_event(DidValidateMemoizedValue { database_key: the_fn(Id(0)) })", ]"#]]); } salsa-0.26.2/tests/tracked-struct-value-field-not-eq.rs000064400000000000000000000016661046102023000210720ustar 00000000000000#![cfg(feature = "inventory")] //! Test a field whose `PartialEq` impl is always true. //! This can our "last changed" data to be wrong //! but we *should* always reflect the final values. use salsa::{Database, Setter}; use test_log::test; #[salsa::input] struct MyInput { field: bool, } #[derive(Hash, Debug, Clone)] struct NotEq { field: bool, } impl From for NotEq { fn from(value: bool) -> Self { Self { field: value } } } #[salsa::tracked] struct MyTracked<'db> { #[tracked] #[no_eq] field: NotEq, } #[salsa::tracked] fn the_fn(db: &dyn Database, input: MyInput) { let tracked0 = MyTracked::new(db, NotEq::from(input.field(db))); assert_eq!(tracked0.field(db).field, input.field(db)); } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, true); the_fn(&db, input); input.set_field(&mut db).to(false); the_fn(&db, input); } salsa-0.26.2/tests/tracked_assoc_fn.rs000064400000000000000000000042551046102023000160240ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] use common::LogDatabase as _; use expect_test::expect; mod common; trait TrackedTrait<'db> { type Output; fn tracked_trait_fn(db: &'db dyn salsa::Database, input: MyInput) -> Self::Output; } #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] struct MyOutput<'db> { field: u32, } #[salsa::tracked] impl MyInput { #[salsa::tracked] fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> Self { Self::new(db, 2 * input.field(db)) } #[salsa::tracked(returns(ref))] fn tracked_fn_ref(db: &dyn salsa::Database, input: MyInput) -> Self { Self::new(db, 3 * input.field(db)) } } #[salsa::tracked] impl<'db> TrackedTrait<'db> for MyOutput<'db> { type Output = Self; #[salsa::tracked] fn tracked_trait_fn(db: &'db dyn salsa::Database, input: MyInput) -> Self::Output { Self::new(db, 4 * input.field(db)) } } // The self-type of a tracked impl doesn't have to be tracked itself: struct UntrackedHelper; #[salsa::tracked] impl<'db> TrackedTrait<'db> for UntrackedHelper { type Output = MyOutput<'db>; #[salsa::tracked] fn tracked_trait_fn(db: &'db dyn salsa::Database, input: MyInput) -> Self::Output { MyOutput::tracked_trait_fn(db, input) } } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 22); let output = MyOutput::tracked_trait_fn(&db, input); let helper_output = UntrackedHelper::tracked_trait_fn(&db, input); // assert_eq!(object.tracked_fn(&db), 44); // assert_eq!(*object.tracked_fn_ref(&db), 66); assert_eq!(output.field(&db), 88); assert_eq!(helper_output.field(&db), 88); } #[test] fn debug_name() { let mut db = common::ExecuteValidateLoggerDatabase::default(); let input = MyInput::new(&db, 22); let output = MyOutput::tracked_trait_fn(&db, input); assert_eq!(output.field(&db), 88); db.assert_logs(expect![[r#" [ "salsa_event(WillExecute { database_key: MyOutput < 'db >::tracked_trait_fn_(Id(0)) })", ]"#]]); } salsa-0.26.2/tests/tracked_fn_constant.rs000064400000000000000000000011121046102023000165320ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a constant `tracked` fn (has no inputs) //! compiles and executes successfully. #![allow(warnings)] use crate::common::LogDatabase; mod common; #[salsa::tracked] fn tracked_fn(db: &dyn salsa::Database) -> u32 { 44 } #[salsa::tracked] fn tracked_custom_db(db: &dyn LogDatabase) -> u32 { 44 } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); assert_eq!(tracked_fn(&db), 44); } #[test] fn execute_custom() { let mut db = common::LoggerDatabase::default(); assert_eq!(tracked_custom_db(&db), 44); } salsa-0.26.2/tests/tracked_fn_high_durability_dependency.rs000064400000000000000000000014321046102023000222530ustar 00000000000000#![cfg(feature = "inventory")] #![allow(warnings)] use salsa::plumbing::HasStorage; use salsa::{Database, Durability, Setter}; mod common; #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> u32 { input.field(db) * 2 } #[test] fn execute() { let mut db = salsa::DatabaseImpl::default(); let input_high = MyInput::new(&mut db, 0); input_high .set_field(&mut db) .with_durability(Durability::HIGH) .to(2200); assert_eq!(tracked_fn(&db, input_high), 4400); // Changing the value should re-execute the query input_high .set_field(&mut db) .with_durability(Durability::HIGH) .to(2201); assert_eq!(tracked_fn(&db, input_high), 4402); } salsa-0.26.2/tests/tracked_fn_interned_lifetime.rs000064400000000000000000000004721046102023000203770ustar 00000000000000#![cfg(feature = "inventory")] #[salsa::interned] struct Interned<'db> { field: i32, } #[salsa::tracked] fn foo<'a>(_db: &'a dyn salsa::Database, _: Interned<'_>, _: Interned<'a>) {} #[test] fn the_test() { let db = salsa::DatabaseImpl::new(); let i = Interned::new(&db, 123); foo(&db, i, i); } salsa-0.26.2/tests/tracked_fn_multiple_args.rs000064400000000000000000000011461046102023000175570ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on multiple salsa struct args //! compiles and executes successfully. #[salsa::input] struct MyInput { field: u32, } #[salsa::interned] struct MyInterned<'db> { field: u32, } #[salsa::tracked] fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInput, interned: MyInterned<'db>) -> u32 { input.field(db) + interned.field(db) } #[test] fn execute() { let db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 22); let interned = MyInterned::new(&db, 33); assert_eq!(tracked_fn(&db, input, interned), 55); } salsa-0.26.2/tests/tracked_fn_no_eq.rs000064400000000000000000000020111046102023000160010ustar 00000000000000#![cfg(feature = "inventory")] mod common; use common::LogDatabase; use expect_test::expect; use salsa::Setter as _; #[salsa::input] struct Input { number: i16, } #[salsa::tracked(no_eq)] fn abs_float(db: &dyn LogDatabase, input: Input) -> f32 { let number = input.number(db); db.push_log(format!("abs_float({number})")); number.abs() as f32 } #[salsa::tracked] fn derived(db: &dyn LogDatabase, input: Input) -> u32 { let x = abs_float(db, input); db.push_log("derived".to_string()); x as u32 } #[test] fn invoke() { let mut db = common::LoggerDatabase::default(); let input = Input::new(&db, 5); let x = derived(&db, input); assert_eq!(x, 5); input.set_number(&mut db).to(-5); // Derived should re-execute even the result of `abs_float` is the same. let x = derived(&db, input); assert_eq!(x, 5); db.assert_logs(expect![[r#" [ "abs_float(5)", "derived", "abs_float(-5)", "derived", ]"#]]); } salsa-0.26.2/tests/tracked_fn_on_input.rs000064400000000000000000000007111046102023000165400ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> u32 { input.field(db) * 2 } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 22); assert_eq!(tracked_fn(&db, input), 44); } salsa-0.26.2/tests/tracked_fn_on_input_with_high_durability.rs000064400000000000000000000034771046102023000230360ustar 00000000000000#![cfg(feature = "inventory")] #![allow(warnings)] use common::{EventLoggerDatabase, HasLogger, LogDatabase, Logger}; use expect_test::expect; use salsa::plumbing::HasStorage; use salsa::{Database, Durability, Event, EventKind, Setter}; mod common; #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> u32 { input.field(db) * 2 } #[test] fn execute() { let mut db = EventLoggerDatabase::default(); let input_low = MyInput::new(&db, 22); let input_high = MyInput::builder(2200).durability(Durability::HIGH).new(&db); assert_eq!(tracked_fn(&db, input_low), 44); assert_eq!(tracked_fn(&db, input_high), 4400); db.assert_logs(expect![[r#" [ "WillCheckCancellation", "WillExecute { database_key: tracked_fn(Id(0)) }", "WillCheckCancellation", "WillExecute { database_key: tracked_fn(Id(1)) }", ]"#]]); db.synthetic_write(Durability::LOW); assert_eq!(tracked_fn(&db, input_low), 44); assert_eq!(tracked_fn(&db, input_high), 4400); // FIXME: There's currently no good way to verify whether an input was validated using shallow or deep comparison. // All we can do for now is verify that the values were validated. // Note: It maybe confusing why it validates `input_high` when the write has `Durability::LOW`. // This is because all values must be validated whenever a write occurs. It doesn't mean that it // executed the query. db.assert_logs(expect![[r#" [ "DidSetCancellationFlag", "WillCheckCancellation", "DidValidateMemoizedValue { database_key: tracked_fn(Id(0)) }", "WillCheckCancellation", "DidValidateMemoizedValue { database_key: tracked_fn(Id(1)) }", ]"#]]); } salsa-0.26.2/tests/tracked_fn_on_interned.rs000064400000000000000000000007341046102023000172160ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::interned` //! compiles and executes successfully. #[salsa::interned] struct Name<'db> { name: String, } #[salsa::tracked] fn tracked_fn<'db>(db: &'db dyn salsa::Database, name: Name<'db>) -> String { name.name(db).clone() } #[test] fn execute() { let db = salsa::DatabaseImpl::new(); let name = Name::new(&db, "Salsa".to_string()); assert_eq!(tracked_fn(&db, name), "Salsa"); } salsa-0.26.2/tests/tracked_fn_on_interned_enum.rs000064400000000000000000000050351046102023000202410ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::interned` //! compiles and executes successfully. #[salsa::interned(no_lifetime, debug)] struct Name { name: String, } #[salsa::interned(debug)] struct NameAndAge<'db> { name_and_age: String, } #[salsa::interned(no_lifetime, debug)] struct Age { age: u32, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Supertype)] enum Enum<'db> { Name(Name), NameAndAge(NameAndAge<'db>), Age(Age), } #[salsa::input(debug)] struct Input { value: String, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Supertype)] enum EnumOfEnum<'db> { Enum(Enum<'db>), Input(Input), } #[salsa::tracked] fn tracked_fn<'db>(db: &'db dyn salsa::Database, enum_: Enum<'db>) -> String { match enum_ { Enum::Name(name) => name.name(db), Enum::NameAndAge(name_and_age) => name_and_age.name_and_age(db), Enum::Age(age) => age.age(db).to_string(), } } #[salsa::tracked] fn tracked_fn2<'db>(db: &'db dyn salsa::Database, enum_: EnumOfEnum<'db>) -> String { match enum_ { EnumOfEnum::Enum(enum_) => tracked_fn(db, enum_), EnumOfEnum::Input(input) => input.value(db), } } #[test] fn execute() { let db = salsa::DatabaseImpl::new(); let name = Name::new(&db, "Salsa".to_string()); let name_and_age = NameAndAge::new(&db, "Salsa 3".to_string()); let age = Age::new(&db, 123); assert_eq!(tracked_fn(&db, Enum::Name(name)), "Salsa"); assert_eq!(tracked_fn(&db, Enum::NameAndAge(name_and_age)), "Salsa 3"); assert_eq!(tracked_fn(&db, Enum::Age(age)), "123"); assert_eq!(tracked_fn(&db, Enum::Name(name)), "Salsa"); assert_eq!(tracked_fn(&db, Enum::NameAndAge(name_and_age)), "Salsa 3"); assert_eq!(tracked_fn(&db, Enum::Age(age)), "123"); assert_eq!( tracked_fn2(&db, EnumOfEnum::Enum(Enum::Name(name))), "Salsa" ); assert_eq!( tracked_fn2(&db, EnumOfEnum::Enum(Enum::NameAndAge(name_and_age))), "Salsa 3" ); assert_eq!(tracked_fn2(&db, EnumOfEnum::Enum(Enum::Age(age))), "123"); assert_eq!( tracked_fn2(&db, EnumOfEnum::Enum(Enum::Name(name))), "Salsa" ); assert_eq!( tracked_fn2(&db, EnumOfEnum::Enum(Enum::NameAndAge(name_and_age))), "Salsa 3" ); assert_eq!(tracked_fn2(&db, EnumOfEnum::Enum(Enum::Age(age))), "123"); assert_eq!( tracked_fn2( &db, EnumOfEnum::Input(Input::new(&db, "Hello world!".to_string())) ), "Hello world!" ); } salsa-0.26.2/tests/tracked_fn_on_tracked.rs000064400000000000000000000010271046102023000170170ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> MyTracked<'_> { MyTracked::new(db, input.field(db) * 2) } #[test] fn execute() { let db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 22); assert_eq!(tracked_fn(&db, input).field(&db), 44); } salsa-0.26.2/tests/tracked_fn_on_tracked_specify.rs000064400000000000000000000021751046102023000205460ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInput) -> MyTracked<'db> { let t = MyTracked::new(db, input.field(db) * 2); if input.field(db) != 0 { tracked_fn_extra::specify(db, t, 2222); } t } #[salsa::tracked(specify)] fn tracked_fn_extra<'db>(_db: &'db dyn salsa::Database, _input: MyTracked<'db>) -> u32 { 0 } #[test] fn execute_when_specified() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 22); let tracked = tracked_fn(&db, input); assert_eq!(tracked.field(&db), 44); assert_eq!(tracked_fn_extra(&db, tracked), 2222); } #[test] fn execute_when_not_specified() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 0); let tracked = tracked_fn(&db, input); assert_eq!(tracked.field(&db), 0); assert_eq!(tracked_fn_extra(&db, tracked), 0); } salsa-0.26.2/tests/tracked_fn_orphan_escape_hatch.rs000064400000000000000000000011401046102023000206600ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] use std::marker::PhantomData; #[salsa::input] struct MyInput { field: u32, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct NotUpdate<'a>(PhantomData &'a ()>); #[salsa::tracked(unsafe(non_update_types))] fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> NotUpdate<'_> { NotUpdate(PhantomData) } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 22); tracked_fn(&db, input); } salsa-0.26.2/tests/tracked_fn_read_own_entity.rs000064400000000000000000000051351046102023000201040ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. use expect_test::expect; mod common; use common::LogDatabase; use salsa::Setter; use test_log::test; #[salsa::input(debug)] struct MyInput { field: u32, } #[salsa::tracked] fn final_result(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("final_result({input:?})")); intermediate_result(db, input).field(db) * 2 } #[salsa::tracked] struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn intermediate_result(db: &dyn LogDatabase, input: MyInput) -> MyTracked<'_> { db.push_log(format!("intermediate_result({input:?})")); let tracked = MyTracked::new(db, input.field(db) / 2); let _ = tracked.field(db); // read the field of an entity we created tracked } #[test] fn one_entity() { let mut db = common::LoggerDatabase::default(); let input = MyInput::new(&db, 22); assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ "final_result(MyInput { [salsa id]: Id(0), field: 22 })", "intermediate_result(MyInput { [salsa id]: Id(0), field: 22 })", ]"#]]); // Intermediate result is the same, so final result does // not need to be recomputed: input.set_field(&mut db).to(23); assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ "intermediate_result(MyInput { [salsa id]: Id(0), field: 23 })", ]"#]]); input.set_field(&mut db).to(24); assert_eq!(final_result(&db, input), 24); db.assert_logs(expect![[r#" [ "intermediate_result(MyInput { [salsa id]: Id(0), field: 24 })", "final_result(MyInput { [salsa id]: Id(0), field: 24 })", ]"#]]); } /// Create and mutate a distinct input. No re-execution required. #[test] fn red_herring() { let mut db = common::LoggerDatabase::default(); let input = MyInput::new(&db, 22); assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" [ "final_result(MyInput { [salsa id]: Id(0), field: 22 })", "intermediate_result(MyInput { [salsa id]: Id(0), field: 22 })", ]"#]]); // Create a distinct input and mutate it. // This will trigger a new revision in the database // but shouldn't actually invalidate our existing ones. let input2 = MyInput::new(&db, 44); input2.set_field(&mut db).to(66); // Re-run the query on the original input. Nothing re-executes! assert_eq!(final_result(&db, input), 22); db.assert_logs(expect![[r#" []"#]]); } salsa-0.26.2/tests/tracked_fn_read_own_specify.rs000064400000000000000000000024061046102023000202300ustar 00000000000000#![cfg(feature = "inventory")] use expect_test::expect; mod common; use common::LogDatabase; use salsa::Database; #[salsa::input(debug)] struct MyInput { field: u32, } #[salsa::tracked(debug)] struct MyTracked<'db> { field: u32, } #[salsa::tracked] fn tracked_fn(db: &dyn LogDatabase, input: MyInput) -> u32 { db.push_log(format!("tracked_fn({input:?})")); let t = MyTracked::new(db, input.field(db) * 2); tracked_fn_extra::specify(db, t, 2222); tracked_fn_extra(db, t) } #[salsa::tracked(specify)] fn tracked_fn_extra<'db>(db: &'db dyn LogDatabase, input: MyTracked<'db>) -> u32 { db.push_log(format!("tracked_fn_extra({input:?})")); 0 } #[test] fn execute() { let mut db = common::LoggerDatabase::default(); let input = MyInput::new(&db, 22); assert_eq!(tracked_fn(&db, input), 2222); db.assert_logs(expect![[r#" [ "tracked_fn(MyInput { [salsa id]: Id(0), field: 22 })", ]"#]]); // A "synthetic write" causes the system to act *as though* some // input of durability `durability` has changed. db.synthetic_write(salsa::Durability::LOW); // Re-run the query on the original input. Nothing re-executes! assert_eq!(tracked_fn(&db, input), 2222); db.assert_logs(expect!["[]"]); } salsa-0.26.2/tests/tracked_fn_return_ref.rs000064400000000000000000000011501046102023000170560ustar 00000000000000#![cfg(feature = "inventory")] use salsa::Database; #[salsa::input] struct Input { number: usize, } #[salsa::tracked(returns(ref))] fn test(db: &dyn salsa::Database, input: Input) -> Vec { (0..input.number(db)).map(|i| format!("test {i}")).collect() } #[test] fn invoke() { salsa::DatabaseImpl::new().attach(|db| { let input = Input::new(db, 3); let x: &Vec = test(db, input); expect_test::expect![[r#" [ "test 0", "test 1", "test 2", ] "#]] .assert_debug_eq(x); }) } salsa-0.26.2/tests/tracked_method.rs000064400000000000000000000026031046102023000155040ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] use common::LogDatabase as _; use expect_test::expect; mod common; trait TrackedTrait { fn tracked_trait_fn(self, db: &dyn salsa::Database) -> u32; } #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] impl MyInput { #[salsa::tracked] fn tracked_fn(self, db: &dyn salsa::Database) -> u32 { self.field(db) * 2 } #[salsa::tracked(returns(ref))] fn tracked_fn_ref(self, db: &dyn salsa::Database) -> u32 { self.field(db) * 3 } } #[salsa::tracked] impl TrackedTrait for MyInput { #[salsa::tracked] fn tracked_trait_fn(self, db: &dyn salsa::Database) -> u32 { self.field(db) * 4 } } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let object = MyInput::new(&mut db, 22); // assert_eq!(object.tracked_fn(&db), 44); // assert_eq!(*object.tracked_fn_ref(&db), 66); assert_eq!(object.tracked_trait_fn(&db), 88); } #[test] fn debug_name() { let mut db = common::ExecuteValidateLoggerDatabase::default(); let object = MyInput::new(&mut db, 22); assert_eq!(object.tracked_trait_fn(&db), 88); db.assert_logs(expect![[r#" [ "salsa_event(WillExecute { database_key: MyInput::tracked_trait_fn_(Id(0)) })", ]"#]]); } salsa-0.26.2/tests/tracked_method_inherent_return_deref.rs000064400000000000000000000012251046102023000221430ustar 00000000000000#![cfg(feature = "inventory")] use salsa::Database; #[salsa::input] struct Input { number: usize, } #[salsa::tracked] impl Input { #[salsa::tracked(returns(deref))] fn test(self, db: &dyn salsa::Database) -> Vec { (0..self.number(db)).map(|i| format!("test {i}")).collect() } } #[test] fn invoke() { salsa::DatabaseImpl::new().attach(|db| { let input = Input::new(db, 3); let x: &[String] = input.test(db); assert_eq!( x, &[ "test 0".to_string(), "test 1".to_string(), "test 2".to_string() ] ); }) } salsa-0.26.2/tests/tracked_method_inherent_return_ref.rs000064400000000000000000000012171046102023000216330ustar 00000000000000#![cfg(feature = "inventory")] use salsa::Database; #[salsa::input] struct Input { number: usize, } #[salsa::tracked] impl Input { #[salsa::tracked(returns(ref))] fn test(self, db: &dyn salsa::Database) -> Vec { (0..self.number(db)).map(|i| format!("test {i}")).collect() } } #[test] fn invoke() { salsa::DatabaseImpl::new().attach(|db| { let input = Input::new(db, 3); let x: &Vec = input.test(db); expect_test::expect![[r#" [ "test 0", "test 1", "test 2", ] "#]] .assert_debug_eq(x); }) } salsa-0.26.2/tests/tracked_method_on_tracked_struct.rs000064400000000000000000000027621046102023000213070ustar 00000000000000#![cfg(feature = "inventory")] use salsa::Database; #[derive(Debug, PartialEq, Eq, Hash)] pub struct Item {} #[salsa::input] pub struct Input { name: String, } #[salsa::tracked] impl Input { #[salsa::tracked] pub fn source_tree(self, db: &dyn Database) -> SourceTree<'_> { SourceTree::new(db, self.name(db).clone()) } } #[salsa::tracked] pub struct SourceTree<'db> { name: String, } #[salsa::tracked] impl<'db1> SourceTree<'db1> { #[salsa::tracked(returns(ref))] pub fn inherent_item_name(self, db: &'db1 dyn Database) -> String { self.name(db) } } trait ItemName<'db1> { fn trait_item_name(self, db: &'db1 dyn Database) -> &'db1 String; } #[salsa::tracked] impl<'db1> ItemName<'db1> for SourceTree<'db1> { #[salsa::tracked(returns(ref))] fn trait_item_name(self, db: &'db1 dyn Database) -> String { self.name(db) } } #[test] fn test_inherent() { salsa::DatabaseImpl::new().attach(|db| { let input = Input::new(db, "foo".to_string()); let source_tree = input.source_tree(db); expect_test::expect![[r#" "foo" "#]] .assert_debug_eq(source_tree.inherent_item_name(db)); }) } #[test] fn test_trait() { salsa::DatabaseImpl::new().attach(|db| { let input = Input::new(db, "foo".to_string()); let source_tree = input.source_tree(db); expect_test::expect![[r#" "foo" "#]] .assert_debug_eq(source_tree.trait_item_name(db)); }) } salsa-0.26.2/tests/tracked_method_trait_return_ref.rs000064400000000000000000000013471046102023000211460ustar 00000000000000#![cfg(feature = "inventory")] use salsa::Database; #[salsa::input] struct Input { number: usize, } trait Trait { fn test(self, db: &dyn salsa::Database) -> &Vec; } #[salsa::tracked] impl Trait for Input { #[salsa::tracked(returns(ref))] fn test(self, db: &dyn salsa::Database) -> Vec { (0..self.number(db)).map(|i| format!("test {i}")).collect() } } #[test] fn invoke() { salsa::DatabaseImpl::new().attach(|db| { let input = Input::new(db, 3); let x: &Vec = input.test(db); expect_test::expect![[r#" [ "test 0", "test 1", "test 2", ] "#]] .assert_debug_eq(x); }) } salsa-0.26.2/tests/tracked_method_with_self_ty.rs000064400000000000000000000020151046102023000202610ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a `tracked` fn with `Self` in its signature or body on a `salsa::input` //! compiles and executes successfully. #![allow(warnings)] trait TrackedTrait { type Type; fn tracked_trait_fn(self, db: &dyn salsa::Database, ty: Self::Type) -> Self::Type; fn untracked_trait_fn(); } #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] impl MyInput { #[salsa::tracked] fn tracked_fn(self, db: &dyn salsa::Database, other: Self) -> u32 { self.field(db) + other.field(db) } } #[salsa::tracked] impl TrackedTrait for MyInput { type Type = u32; #[salsa::tracked] fn tracked_trait_fn(self, db: &dyn salsa::Database, ty: Self::Type) -> Self::Type { Self::untracked_trait_fn(); Self::tracked_fn(self, db, self) + ty } fn untracked_trait_fn() {} } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let object = MyInput::new(&mut db, 10); assert_eq!(object.tracked_trait_fn(&db, 1), 21); } salsa-0.26.2/tests/tracked_struct.rs000064400000000000000000000024601046102023000155510ustar 00000000000000#![cfg(feature = "inventory")] mod common; use salsa::{Database, Setter}; #[salsa::tracked] struct Tracked<'db> { untracked_1: usize, untracked_2: usize, } #[salsa::input] struct MyInput { field1: usize, field2: usize, } #[salsa::tracked] fn intermediate(db: &dyn salsa::Database, input: MyInput) -> Tracked<'_> { Tracked::new(db, input.field1(db), input.field2(db)) } #[salsa::tracked] fn accumulate(db: &dyn salsa::Database, input: MyInput) -> (usize, usize) { let tracked = intermediate(db, input); let one = read_tracked_1(db, tracked); let two = read_tracked_2(db, tracked); (one, two) } #[salsa::tracked] fn read_tracked_1<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { tracked.untracked_1(db) } #[salsa::tracked] fn read_tracked_2<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { tracked.untracked_2(db) } #[test] fn execute() { let mut db = salsa::DatabaseImpl::default(); let input = MyInput::new(&db, 1, 1); assert_eq!(accumulate(&db, input), (1, 1)); // Should only re-execute `read_tracked_1`. input.set_field1(&mut db).to(2); assert_eq!(accumulate(&db, input), (2, 1)); // Should only re-execute `read_tracked_2`. input.set_field2(&mut db).to(2); assert_eq!(accumulate(&db, input), (2, 2)); } salsa-0.26.2/tests/tracked_struct_db1_lt.rs000064400000000000000000000005631046102023000170000ustar 00000000000000#![cfg(feature = "inventory")] //! Test that tracked structs with lifetimes not named `'db` //! compile successfully. mod common; use test_log::test; #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked] struct MyTracked1<'db1> { field: MyTracked2<'db1>, } #[salsa::tracked] struct MyTracked2<'db2> { field: u32, } #[test] fn create_db() {} salsa-0.26.2/tests/tracked_struct_disambiguates.rs000064400000000000000000000047361046102023000204620ustar 00000000000000#![cfg(feature = "inventory")] //! Test that disambiguation works, that is when we have a revision where we track multiple structs //! that have the same hash, we can still differentiate between them. #![allow(warnings)] use std::hash::Hash; use rayon::iter::Either; use salsa::Setter; #[salsa::input] struct MyInput { field: u32, } #[salsa::input] struct MyInputs { field: Vec, } #[salsa::tracked] struct TrackedStruct<'db> { field: DumbHashable, } #[salsa::tracked] struct TrackedStruct2<'db> { field: DumbHashable, } #[derive(Debug, Clone)] pub struct DumbHashable { field: u32, } impl Eq for DumbHashable {} impl PartialEq for DumbHashable { fn eq(&self, other: &Self) -> bool { self.field == other.field } } // Force collisions, note that this is still a correct implementation wrt. PartialEq / Eq above // as keep the property that k1 == k2 -> hash(k1) == hash(k2) impl Hash for DumbHashable { fn hash(&self, state: &mut H) { (self.field % 3).hash(state); } } fn alternate( db: &dyn salsa::Database, input: MyInput, ) -> Either, TrackedStruct2<'_>> { if input.field(db) % 2 == 0 { Either::Left(TrackedStruct::new( db, DumbHashable { field: input.field(db), }, )) } else { Either::Right(TrackedStruct2::new( db, DumbHashable { field: input.field(db), }, )) } } #[salsa::tracked] fn batch( db: &dyn salsa::Database, inputs: MyInputs, ) -> Vec, TrackedStruct2<'_>>> { inputs .field(db) .iter() .map(|input| alternate(db, input.clone())) .collect() } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let inputs = MyInputs::new( &db, (0..64).into_iter().map(|i| MyInput::new(&db, i)).collect(), ); let trackeds = batch(&db, inputs); for (id, tracked) in trackeds.into_iter().enumerate() { assert_eq!(id % 2 == 0, tracked.is_left()); assert_eq!(id % 2 != 0, tracked.is_right()); } for input in inputs.field(&db) { let prev = input.field(&db); input.set_field(&mut db).to(prev); } let trackeds = batch(&db, inputs); for (id, tracked) in trackeds.into_iter().enumerate() { assert_eq!(id % 2 == 0, tracked.is_left()); assert_eq!(id % 2 != 0, tracked.is_right()); } } salsa-0.26.2/tests/tracked_struct_durability.rs000064400000000000000000000102401046102023000177740ustar 00000000000000#![cfg(feature = "inventory")] /// Test that high durabilities can't cause "access tracked struct from previous revision" panic. /// /// The test models a situation where we have two File inputs (0, 1), where `File(0)` has LOW /// durability and `File(1)` has HIGH durability. We can query an `index` for each file, and a /// `definitions` from that index (just a sub-part of the index), and we can `infer` each file. The /// `index` and `definitions` queries depend only on the `File` they operate on, but the `infer` /// query has some other dependencies: `infer(0)` depends on `infer(1)`, and `infer(1)` also /// depends directly on `File(0)`. /// /// The panic occurs (in versions of Salsa without a fix) because `definitions(1)` is high /// durability, and depends on `index(1)` which is also high durability. `index(1)` creates the /// tracked struct `Definition(1)`, and `infer(1)` (which is low durability) depends on /// `Definition.file(1)`. /// /// After a change to `File(0)` (low durability), we only shallowly verify `definitions(1)` -- it /// passes shallow verification due to durability. We take care to mark-validated the outputs of /// `definitions(1)`, but we never verify `index(1)` at all (deeply or shallowly), which means we /// never mark `Definition(1)` validated. So when we deep-verify `infer(1)`, we try to access its /// dependency `Definition.file(1)`, and hit the panic because we are accessing a tracked struct /// that has never been re-validated or re-recreated in R2. use salsa::{Durability, Setter}; #[salsa::db] trait Db: salsa::Database { fn file(&self, idx: usize) -> File; } #[salsa::input] struct File { field: usize, } #[salsa::tracked] struct Definition<'db> { #[tracked] file: File, } #[salsa::tracked] struct Index<'db> { #[tracked] definitions: Definitions<'db>, } #[salsa::tracked] struct Definitions<'db> { #[tracked] definition: Definition<'db>, } #[salsa::tracked] struct Inference<'db> { #[tracked] definition: Definition<'db>, } #[salsa::tracked] fn index(db: &dyn Db, file: File) -> Index<'_> { let _ = file.field(db); Index::new(db, Definitions::new(db, Definition::new(db, file))) } #[salsa::tracked] fn definitions(db: &dyn Db, file: File) -> Definitions<'_> { index(db, file).definitions(db) } #[salsa::tracked] fn infer<'db>(db: &'db dyn Db, definition: Definition<'db>) -> Inference<'db> { let file = definition.file(db); if file.field(db) < 1 { let dependent_file = db.file(1); infer(db, definitions(db, dependent_file).definition(db)) } else { db.file(0).field(db); index(db, file); Inference::new(db, definition) } } #[salsa::tracked] fn check(db: &dyn Db, file: File) -> Inference<'_> { let defs = definitions(db, file); infer(db, defs.definition(db)) } #[test] fn execute() { #[salsa::db] #[derive(Default, Clone)] struct Database { storage: salsa::Storage, files: Vec, } #[salsa::db] impl salsa::Database for Database {} #[salsa::db] impl Db for Database { fn file(&self, idx: usize) -> File { self.files[idx] } } let mut db = Database::default(); // Create a file 0 with low durability, and a file 1 with high durability. let file0 = File::new(&db, 0); db.files.push(file0); let file1 = File::new(&db, 1); file1 .set_field(&mut db) .with_durability(Durability::HIGH) .to(1); db.files.push(file1); // check(0) -> infer(0) -> definitions(0) -> index(0) // \-> infer(1) -> definitions(1) -> index(1) assert_eq!(check(&db, file0).definition(&db).file(&db).field(&db), 1); // update the low durability file 0 file0.set_field(&mut db).to(0); // Re-query check(0). definitions(1) is high durability so it short-circuits in shallow-verify, // meaning we never verify index(1) at all, but index(1) created the tracked struct // Definition(1), so we never validate Definition(1) in R2, so when we try to verify // Definition.file(1) (as an input of infer(1) ) we hit a panic for trying to use a struct that // isn't validated in R2. check(&db, file0); } salsa-0.26.2/tests/tracked_struct_manual_update.rs000064400000000000000000000033631046102023000204530ustar 00000000000000#![cfg(feature = "inventory")] mod common; use std::sync::atomic::{AtomicBool, Ordering}; use salsa::{Database, Setter}; static MARK1: AtomicBool = AtomicBool::new(false); static MARK2: AtomicBool = AtomicBool::new(false); #[salsa::tracked] struct Tracked<'db> { #[tracked] #[maybe_update(|dst, src| { *dst = src; MARK1.store(true, Ordering::Release); true })] tracked: usize, #[maybe_update(untracked_update)] untracked: usize, } unsafe fn untracked_update(dst: *mut usize, src: usize) -> bool { unsafe { *dst = src }; MARK2.store(true, Ordering::Release); true } #[salsa::input] struct MyInput { field1: usize, field2: usize, } #[salsa::tracked] fn intermediate(db: &dyn salsa::Database, input: MyInput) -> Tracked<'_> { Tracked::new(db, input.field1(db), input.field2(db)) } #[salsa::tracked] fn accumulate(db: &dyn salsa::Database, input: MyInput) -> (usize, usize) { let tracked = intermediate(db, input); let one = read_tracked(db, tracked); let two = read_untracked(db, tracked); (one, two) } #[salsa::tracked] fn read_tracked<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { tracked.tracked(db) } #[salsa::tracked] fn read_untracked<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { tracked.untracked(db) } #[test] fn execute() { let mut db = salsa::DatabaseImpl::default(); let input = MyInput::new(&db, 1, 1); assert_eq!(accumulate(&db, input), (1, 1)); assert!(!MARK1.load(Ordering::Acquire)); assert!(!MARK2.load(Ordering::Acquire)); input.set_field1(&mut db).to(2); assert_eq!(accumulate(&db, input), (2, 1)); assert!(MARK1.load(Ordering::Acquire)); assert!(MARK2.load(Ordering::Acquire)); } salsa-0.26.2/tests/tracked_struct_mixed_tracked_fields.rs000064400000000000000000000030761046102023000217660ustar 00000000000000#![cfg(feature = "inventory")] mod common; use salsa::{Database, Setter}; // A tracked struct with mixed tracked and untracked fields to ensure // the correct field indices are used when tracking dependencies. #[salsa::tracked] struct Tracked<'db> { untracked_1: usize, #[tracked] tracked_1: usize, untracked_2: usize, untracked_3: usize, #[tracked] tracked_2: usize, untracked_4: usize, } #[salsa::input] struct MyInput { field1: usize, field2: usize, } #[salsa::tracked] fn intermediate(db: &dyn salsa::Database, input: MyInput) -> Tracked<'_> { Tracked::new(db, 0, input.field1(db), 0, 0, input.field2(db), 0) } #[salsa::tracked] fn accumulate(db: &dyn salsa::Database, input: MyInput) -> (usize, usize) { let tracked = intermediate(db, input); let one = read_tracked_1(db, tracked); let two = read_tracked_2(db, tracked); (one, two) } #[salsa::tracked] fn read_tracked_1<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { tracked.tracked_1(db) } #[salsa::tracked] fn read_tracked_2<'db>(db: &'db dyn Database, tracked: Tracked<'db>) -> usize { tracked.tracked_2(db) } #[test] fn execute() { let mut db = salsa::DatabaseImpl::default(); let input = MyInput::new(&db, 1, 1); assert_eq!(accumulate(&db, input), (1, 1)); // Should only re-execute `read_tracked_1`. input.set_field1(&mut db).to(2); assert_eq!(accumulate(&db, input), (2, 1)); // Should only re-execute `read_tracked_2`. input.set_field2(&mut db).to(2); assert_eq!(accumulate(&db, input), (2, 2)); } salsa-0.26.2/tests/tracked_struct_recreate_new_revision.rs000064400000000000000000000015031046102023000222070ustar 00000000000000#![cfg(feature = "inventory")] //! Test that re-creating a `tracked` struct after it was deleted in a previous //! revision doesn't panic. #![allow(warnings)] use salsa::Setter; #[salsa::input] struct MyInput { field: u32, } #[salsa::tracked(debug)] struct TrackedStruct<'db> { field: u32, } #[salsa::tracked] fn tracked_fn(db: &dyn salsa::Database, input: MyInput) -> Option> { if input.field(db) == 1 { Some(TrackedStruct::new(db, 1)) } else { None } } #[test] fn execute() { let mut db = salsa::DatabaseImpl::new(); let input = MyInput::new(&db, 1); assert!(tracked_fn(&db, input).is_some()); input.set_field(&mut db).to(0); assert_eq!(tracked_fn(&db, input), None); input.set_field(&mut db).to(1); assert!(tracked_fn(&db, input).is_some()); } salsa-0.26.2/tests/tracked_struct_with_interned_query.rs000064400000000000000000000027061046102023000217240ustar 00000000000000#![cfg(feature = "inventory")] mod common; use salsa::Setter; #[salsa::input] struct MyInput { value: usize, } #[salsa::tracked] struct Tracked<'db> { value: String, } #[salsa::tracked] fn query_tracked(db: &dyn salsa::Database, input: MyInput) -> Tracked<'_> { Tracked::new(db, format!("{value}", value = input.value(db))) } #[salsa::tracked] fn join<'db>(db: &'db dyn salsa::Database, tracked: Tracked<'db>, with: String) -> String { format!("{}{}", tracked.value(db), with) } #[test] fn execute() { let mut db = salsa::DatabaseImpl::default(); let input = MyInput::new(&db, 1); let tracked = query_tracked(&db, input); let joined = join(&db, tracked, "world".to_string()); assert_eq!(joined, "1world"); // Create a new revision: This puts the tracked struct created in revision 0 // into the free list. input.set_value(&mut db).to(2); let tracked = query_tracked(&db, input); let joined = join(&db, tracked, "world".to_string()); assert_eq!(joined, "2world"); // Create a new revision: The tracked struct created in revision 0 is now // reused, including its id. The argument to `join` will hash and compare // equal to the argument used in revision 0 but the return value should be // 3world and not 1world. input.set_value(&mut db).to(3); let tracked = query_tracked(&db, input); let joined = join(&db, tracked, "world".to_string()); assert_eq!(joined, "3world"); } salsa-0.26.2/tests/tracked_with_intern.rs000064400000000000000000000006001046102023000165510ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a setting a field on a `#[salsa::input]` //! overwrites and returns the old value. use test_log::test; #[salsa::input] struct MyInput { field: String, } #[salsa::tracked] struct MyTracked<'db> { #[tracked] field: MyInterned<'db>, } #[salsa::interned] struct MyInterned<'db> { field: String, } #[test] fn execute() {} salsa-0.26.2/tests/tracked_with_struct_db.rs000064400000000000000000000030371046102023000172520ustar 00000000000000#![cfg(feature = "inventory")] //! Test that a setting a field on a `#[salsa::input]` //! overwrites and returns the old value. use salsa::{Database, DatabaseImpl, Update}; use test_log::test; #[salsa::input(debug)] struct MyInput { field: String, } #[salsa::tracked(debug)] struct MyTracked<'db> { #[tracked] data: MyInput, #[tracked] next: MyList<'db>, } #[derive(PartialEq, Eq, Clone, Debug, Update)] enum MyList<'db> { None, Next(MyTracked<'db>), } #[salsa::tracked] fn create_tracked_list(db: &dyn Database, input: MyInput) -> MyTracked<'_> { let t0 = MyTracked::new(db, input, MyList::None); MyTracked::new(db, input, MyList::Next(t0)) } #[test] fn execute() { DatabaseImpl::new().attach(|db| { let input = MyInput::new(db, "foo".to_string()); let t0: MyTracked = create_tracked_list(db, input); let t1 = create_tracked_list(db, input); expect_test::expect![[r#" MyTracked { [salsa id]: Id(401), data: MyInput { [salsa id]: Id(0), field: "foo", }, next: Next( MyTracked { [salsa id]: Id(400), data: MyInput { [salsa id]: Id(0), field: "foo", }, next: None, }, ), } "#]] .assert_debug_eq(&t0); assert_eq!(t0, t1); }) } salsa-0.26.2/tests/tracked_with_struct_ord.rs000064400000000000000000000015131046102023000174460ustar 00000000000000#![cfg(feature = "inventory")] //! Test that `PartialOrd` and `Ord` can be derived for tracked structs use salsa::{Database, DatabaseImpl}; use test_log::test; #[salsa::input] #[derive(PartialOrd, Ord)] struct Input { value: usize, } #[salsa::tracked(debug)] #[derive(Ord, PartialOrd)] struct MyTracked<'db> { value: usize, } #[salsa::tracked] fn create_tracked(db: &dyn Database, input: Input) -> MyTracked<'_> { MyTracked::new(db, input.value(db)) } #[test] fn execute() { DatabaseImpl::new().attach(|db| { let input1 = Input::new(db, 20); let input2 = Input::new(db, 10); // Compares by ID and not by value. assert!(input1 <= input2); let t0: MyTracked = create_tracked(db, input1); let t1: MyTracked = create_tracked(db, input2); assert!(t0 <= t1); }) } salsa-0.26.2/tests/warnings/main.rs000064400000000000000000000002211046102023000152750ustar 00000000000000//! Test that macros don't generate code with warnings #![deny(warnings)] mod needless_borrow; mod needless_lifetimes; mod unused_variable_db; salsa-0.26.2/tests/warnings/needless_borrow.rs000064400000000000000000000002321046102023000175470ustar 00000000000000#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] enum Token {} #[salsa::tracked] struct TokenTree<'db> { #[returns(ref)] tokens: Vec, } salsa-0.26.2/tests/warnings/needless_lifetimes.rs000064400000000000000000000010161046102023000202170ustar 00000000000000#[salsa::db] pub trait Db: salsa::Database {} #[derive(Debug, PartialEq, Eq, Hash)] pub struct Item {} #[salsa::tracked] pub struct SourceTree<'db> {} #[salsa::tracked] impl<'db> SourceTree<'db> { #[salsa::tracked(returns(ref))] pub fn all_items(self, _db: &'db dyn Db) -> Vec { todo!() } } #[salsa::tracked(returns(ref))] fn use_tree<'db>(_db: &'db dyn Db, _tree: SourceTree<'db>) {} #[allow(unused)] fn use_it(db: &dyn Db, tree: SourceTree) { tree.all_items(db); use_tree(db, tree); } salsa-0.26.2/tests/warnings/unused_variable_db.rs000064400000000000000000000000531046102023000201710ustar 00000000000000#[salsa::interned] struct Keywords<'db> {}