Skip to content

engine: ltm_discovery_mode is still a salsa input; flipping it discards the other mode's memos #1056

Description

@bpowers

Problem

ltm_discovery_mode is a bool field on the SourceProject salsa input (src/simlin-engine/src/db/input.rs, setter db::set_project_ltm_discovery_mode in src/simlin-engine/src/db.rs). Two tracked queries read it directly:

  • model_ltm_mode (src/simlin-engine/src/db/ltm/mod.rs) returns LtmMode::Discovery when the flag is set, before any SCC gate is consulted.
  • model_ltm_variables (same file) reads it as is_discovery_user to decide whether every edge gets a link score and whether the auto-flip warning is emitted.

Everything under model_ltm_variables (the shaped link scores, the loop/relative-loop scores, the LTM fragments, the LTM-overlay layout and assembly) therefore depends on the flag. Because it is an input, every flip is a new salsa revision:

  • the other mode's model_ltm_mode / model_ltm_variables memos are replaced on the next demand (the values differ, so nothing backdates and the whole LTM tier re-derives), and
  • every memo in the database is deep-verified on its next read.

This is exactly the shape the LTM overlay had before it became an explicit query argument (db::LtmOverlay { Off, On }, keyed on the compile rather than stored on the project). That change removed a revision per overlay flip; on C-LEARN the flip had been re-executing ~2.9 G instructions for a warm LTM sim after a plain diagnostics pass and ~2.5 G for the reverse. docs/design/engine-performance.md section C7 ("The LTM overlay is an argument, not an input") records that change and its measurements; discovery mode is the remaining input of the same shape.

Who flips it

Production (non-test) callers:

  • simlin_engine::analysis::analyze_model (src/simlin-engine/src/analysis.rs) sets the flag to true for its pipeline and restores the caller's prior value before returning. The set is a no-op write when the caller is already in discovery mode, but for a caller in exhaustive mode every analyze_model call is two revisions (set, restore), and the exhaustive-mode LTM memos are gone afterwards.
  • wasmgen::compile_datamodel_to_artifact (src/simlin-engine/src/wasmgen/module.rs) sets it on a fresh SimlinDb per call, so it does not thrash a shared database, but it is the same flag threaded as a bool.
  • libsimlin's simlin_analyze_discover_loops (src/libsimlin/src/analysis.rs) and the MCP read_model / edit_model tools (src/simlin-mcp-core/src/tools/) call analyze_model on the project's shared SimlinDb.

simlin_sim_new(.., enable_ltm) does not set the flag itself. An LTM simulation created through it runs in whatever mode the project is in (exhaustive by default, subject to the auto-flip gate). So the concrete thrash pattern is a caller alternating an exhaustive-mode LTM sim (simlin_sim_new with LTM enabled, which is the editor's sparkline / link-score path) with simlin_analyze_discover_loops on one project: each round trip re-derives the LTM tier for both modes and re-verifies every memo.

Why it matters

  • Performance of the shared-database callers. libsimlin and simlin-serve keep one SimlinDb per project precisely so warm queries are cheap. This input defeats that for any caller that uses both modes.
  • Consistency of the salsa design. The overlay is a query argument; the mode is an input. Two knobs of the same shape with two different mechanisms is a trap for the next reader (and the analyze_model set/restore dance, with its own regression tests ltm_discovery_mode_reset_after_analyze / ..._after_failed_analysis, exists only to keep the input clean).
  • Deferred-delete residency. Each replaced memo sits in the ingredient's deferred-delete list until the next revision; a flip-heavy caller carries both modes' LTM values plus the stale generation between them.

Components affected

  • src/simlin-engine (db/input.rs, db.rs, db/ltm/mod.rs, analysis.rs, wasmgen/module.rs)
  • src/libsimlin (analysis.rs comment and the shared-db call sites)
  • src/simlin-mcp-core (tool call sites, transitively)

Proposed approach

Make the requested mode a key of the LTM derivation the way the overlay is a key of the compile:

  1. Introduce a requested-mode argument (e.g. LtmModeRequest { Exhaustive, Discovery }, distinct from the existing db::LtmMode which is the effective mode after the auto-flip gate) on model_ltm_mode and model_ltm_variables, and thread it through their dependents; layout/assembly take it alongside LtmOverlay.
  2. Delete the ltm_discovery_mode input field, set_project_ltm_discovery_mode, and the set/restore in analyze_model (which then just passes Discovery). compile_datamodel_to_artifact and libsimlin pass the argument instead of setting the flag.
  3. Pin both modes staying memoized side by side, mirroring src/simlin-engine/src/db/ltm_overlay_tests.rs.

Cost: a second set of LTM memos when both modes have been used on one project (the overlay change paid about 5 MiB on C-LEARN for the analogous fragment duplication; the LTM tier is larger, so expect more).

Measure first

Before doing this, measure how often real callers actually alternate modes on one project: the editor's sparkline path through libsimlin, and the MCP tools (which today only ever request discovery via analyze_model). If nothing alternates in practice, the cleanup is worth doing for consistency but the memory cost should be weighed against the instruction savings.

Context

Identified on branch salsa-roofline-engine (PR pending) while converting the LTM overlay from a project input to a query argument; the discovery-mode flag was left as is to keep that change reviewable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions