atoll-db is the memoized composition layer shared by the CLI and language
server. It wraps compiler stages in Salsa queries; it does not define a second
parser, checker, or backend.
Tracked inputs
The database tracks two kinds of input:
| Input | Examples |
|---|---|
SourceFile |
Stable path, module path, mutable file contents |
Project |
Root module, ordered files, dependencies, host allowlist, datasource routing |
Changing file contents invalidates queries that read that file. Adding, removing, or reordering project files changes project enumeration and therefore invalidates queries that depend on the project shape. Configuration changes must behave like source changes when they affect resolution or lowering.
Long-lived consumers update a SourceFile through Salsa’s setter. The one-shot
CLI driver materializes the same tracked inputs in a fresh database for each
command, preserving pipeline parity without pretending that a process-local
cache survives the command.
Query graph
The graph has both whole-project and fine-grained paths:
SourceFile ─→ parse_file ───────────────┐
├→ declaration surface
Project files ─→ parse_all_q ──────────┘
↓
resolve and declarations
↓
per-function checks and assembly
↓
project HIR / diagnostics
↓
specialization and MIR queries
↓
unit slicing and unit emissionResults are generally shared behind Arc. A query cache hit clones a handle,
not the complete AST or project.
Parse granularity
parse_file is memoized by SourceFile. Editing one file creates a new parsed
value for that file; unchanged files retain their prior Arc<ParsedFile>.
parse_all_q returns a new ordered outer collection when necessary while
sharing the unchanged inner entries.
The language server can populate a separate incremental parse cache for an
exact (path, contents) pair. The tracked query accepts that parse only when
the contents match. Otherwise it performs a normal cold parse, preserving one
semantic result for one source snapshot.
Declaration firewall
Function-body edits should not force project-wide import and declaration resolution when no declaration surface changed. The database computes a body-independent hash per file and combines those hashes into a project declaration snapshot.
On a body-only edit:
- the edited file reparses;
- its declaration-surface query reruns;
- the body-stripped hash remains equal;
- Salsa backdates the declaration result;
- project resolution remains a cache hit;
- affected body checks can rerun against the same frozen declaration base.
A signature, visibility, import, type declaration, or other surface edit changes the hash and correctly invalidates dependent resolution.
The hash is an invalidation firewall, not semantic authority. Parity tests must ensure it includes every source fact that can affect another body.
Per-function analysis
The database exposes per-function raw, checked, lowered, and assembly queries for editor and incremental consumers. Each function can be checked against a frozen declaration base into local arenas, then merged in canonical order.
Whole-project aggregation still has a role:
- it produces the canonical project visible to existing compiler APIs;
- it prevents repeated deep project cloning from turning a query fan-out into quadratic work;
- it centralizes project-wide diagnostics and layouts;
- it serves as an oracle for parity tests while the fine-grained path evolves.
Queries should be lazy. A top-level project request must not eagerly execute every per-function convenience query if the canonical aggregate can produce the result once.
Lower-stage boundaries
Incrementality is mixed, not uniform:
| Boundary | Current useful granularity |
|---|---|
| Parsing | Per file |
| Declaration surface | Per file, combined at project level |
| Body checking | Per function with canonical assembly |
| Specialization | Per-key query surfaces exist, but project mutation and reachable closure still create shared boundaries |
| MIR | Symbol-subset builds exist; cross-function analyses and unit-wide transforms limit isolation |
| Emission | Unit slicing and per-unit emission queries |
A per-function query name therefore does not prove that no whole-project work occurred transitively. Performance claims must be measured from query execution and cache-hit data, not inferred from API shape.
Parity gates
Every incremental shortcut needs an oracle:
- cold and incremental parses must agree;
- assembled semantic output must agree with canonical analysis;
- per-function worklist union must agree with the whole-project reachable set;
- incremental unit emission must agree byte-for-byte with canonical emission;
- diagnostic content and order must remain stable.
The database exposes parity validators for MIR aggregation, worklist union, and incremental emission. These are engineering tools as well as tests: when a new side table or implicit dependency is added, a parity failure identifies the missing query edge.
Query design rules
When adding a query:
- key it on the smallest stable semantic identity, not a transient pointer;
- read every tracked input that can change the result;
- return immutable shared snapshots;
- define equality or backdating only for facts truly irrelevant downstream;
- avoid ambient options that Salsa cannot observe;
- add a no-edit cache-hit test and an affected-edit invalidation test;
- compare output with the canonical non-incremental path.
The goal is not merely fewer recomputations. It is the same compiler result with a smaller dirty subgraph.