fix(tla): keep CONSTANTS out of the fingerprinted state - #193
Merged
Conversation
…t, not state)
In TLA+/TLC a state is a valuation of the declared VARIABLES only; CONSTANTS
belong to the fixed model context. We were seeding every config constant into
each initial state (so Init predicates could read them), and since the active
state schema is derived from the first init state's keys, those constants ended
up in the schema — compiling every constant reference to a per-state
`StateVar { slot }` and carrying the constant in EVERY fingerprinted state. That
never changed distinct-state counts (constants are identical across all states)
but bloated every fingerprint (bincode-serialized + hashed per state) and every
stored state, and conflated the constant context with the mutable state.
Two prior attempts to strip constants were reverted because they removed the
data from the state but left the schema/compilation still pointing at constant
slots. This fixes it at the root and in lockstep:
- A (TlaModel::from_files): after the initial states are materialized and BEFORE
`set_active_schema`, retain only `module.variables` in each state. This one
central filter covers the brute-force, joint-symbolic (T5.5) and streaming Init
paths. Because the active schema is then variable-only, constant references
compile to name-based `Var` (resolved via injected definitions / the
ModelValue fallback) instead of `StateVar { slot }`.
- C (inject_constants_into_module_tree): do not inject the SELF-REFERENTIAL def
for a model-value constant whose name equals its value (`NULL = NULL` ->
`NULL == NULL`), which would recurse to MAX_DEPTH once the state binding is
gone; the `ModelValue(name)` identifier fallback resolves it. An ALIASED model
value (`NoBlock = NoBlockVal`, name != value) still injects its
non-self-referential def `NoBlock == NoBlockVal` — otherwise NoBlock would
wrongly fall back to ModelValue("NoBlock") instead of NoBlockVal.
Successors inherit the change for free (they clone the current state and only
overwrite staged variables), and symmetry is count-neutral: the held-fixed
constant self-bindings were identical across all permutation candidates, so
removing them cannot change which permutation wins — `fixed_keys` now simply
never matches a state key. Symmetry comments updated to reflect this.
Probe tests updated to probe the model's INJECTED module (matching production
`analyze-tla`, which injects constants before probing) rather than the raw parse
while relying on constants-in-state.
Gate: diff_tlc 22/22 with byte-identical distinct counts (adds `const_state_omit`
regression: a scalar constant guard + a model value in Init and an invariant ==
TLC); Paxos/SimplifiedFastPaxos still 1,207 distinct == TLC (symmetry intact);
cargo test --release 1486 passed, 0 failed. Verified via --dump that constants
no longer appear as state entries.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In TLA+/TLC a state is a valuation of the declared
VARIABLESonly;CONSTANTSbelong to the fixed model context. We were seeding every config constant into each initial state (soInitpredicates could read them), and since the active state schema is derived from the first init state's keys, those constants ended up in the schema — compiling every constant reference to a per-stateStateVar { slot }and carrying the constant in every fingerprinted state.This never changed distinct-state counts (constants are identical across all states) but bloated every fingerprint (bincode-serialized + hashed per state) and every stored state, and conflated the constant context with mutable state.
Two prior attempts to strip constants were reverted because they removed the data from the state but left the schema/compilation still pointing at constant slots. This fixes it at the root and in lockstep.
Change
TlaModel::from_files): after the initial states are materialized and beforeset_active_schema, retain onlymodule.variablesin each state. One central filter covers the brute-force, joint-symbolic (T5.5) and streaming Init paths. The active schema is then variable-only, so constant references compile to name-basedVar(resolved via injected definitions / theModelValuefallback) instead ofStateVar { slot }.inject_constants_into_module_tree): do not inject the self-referential def for a model-value constant whose name equals its value (NULL = NULL→NULL == NULL), which would recurse toMAX_DEPTHonce the state binding is gone; theModelValue(name)identifier fallback resolves it. An aliased model value (NoBlock = NoBlockVal, name ≠ value) still injects its non-self-referential def — otherwiseNoBlockwould wrongly fall back toModelValue("NoBlock").Successors inherit the change for free (they clone the current state and only overwrite staged variables). Symmetry is count-neutral: the held-fixed constant self-bindings were identical across all permutation candidates, so removing them cannot change which permutation wins —
fixed_keysnow simply never matches a state key (comments updated). Probe tests updated to probe the model's injected module (matching productionanalyze-tla) rather than relying on constants-in-state.Design review
Design and plan were reviewed independently (codex, read-only) and cross-checked against the source before implementation — root cause, model-value self-ref loop, successor propagation, operator-ref constants,
TransitionContext, and symmetry count-neutrality all confirmed; conclusion was A+C is minimal-complete (no separate constant-env context field required). The gate then caught a real bug in the first (too-broad) Impl C, which is why it's narrowed to the self-referential case only.Gate
scripts/diff_tlc.sh22/22 with byte-identical distinct counts — addsconst_state_omitregression (scalar-constant guard + model value inInitand an invariant, == TLC)cargo test --release1486 passed, 0 failed--dumpthat constants no longer appear as state entries