Skip to content

perf(tla): cache the compiled IR in the action-call guard path - #189

Merged
zoratu merged 1 commit into
mainfrom
perf/cache-guard-path-action-ir
Jul 20, 2026
Merged

perf(tla): cache the compiled IR in the action-call guard path#189
zoratu merged 1 commit into
mainfrom
perf/cache-guard-path-action-ir

Conversation

@zoratu

@zoratu zoratu commented Jul 20, 2026

Copy link
Copy Markdown
Owner

try_eval_compiled_guard_as_action recompiled the resolved action's IR — compile_action_ir + CompiledActionIr::from_ir + compile_expr/lower — on every guard evaluation. This guard is on the hot path: each \E-quantified disjunct of a Next disjunction (e.g. ACP's \E i,j : parDie(i) \/ parProgNB(i,j)) resolves through it.

For a fairness spec that re-explores the state graph during trace reconstruction (reconstruct_trace_limited, a full re-BFS when the parent-map isn't available), this meant re-compiling parProg/decideOnForward/abortOnTimeout on every re-visited state. A gdb sample of the stall showed 4+ worker threads deep in from_ircompile_action_clause_textcompile_expr/lower.

The fix reuses the existing thread-local + prewarmed get_or_compile_action cache (already used by the inline execute_branch path) instead of the fresh compile.

Result

ACP_NB_WRONG_TLC now completes in ~143s — previously it stalled past 300s in trace reconstruction — with the same verdict (invariant 'AC1' violated). A gdb backtrace after the fix confirms from_ir is gone from the hot path.

Validation

  • scripts/diff_tlc.sh: 19/19.
  • cargo test --release: clean (the lone prop_pause_visibility blip is the known-flaky checkpoint-concurrency proptest — passes in isolation).
  • Pure caching change — get_or_compile_action applies the same resolution schema as every other compiled-IR consumer, so no behavior difference (confirmed by the gate).

Note: this speeds up ACP's completion but the spec still over-generates its distinct count (~13.7k vs TLC 12,893) and that count is run-to-run nondeterministic — a separate correctness issue tracked independently.

`try_eval_compiled_guard_as_action` recompiled the resolved action's IR
(`compile_action_ir` + `CompiledActionIr::from_ir` + `compile_expr`/`lower`)
on EVERY guard evaluation. This guard is on the hot path — each
`\E`-quantified disjunct of a Next disjunction (e.g. ACP's
`\E i,j : parDie(i) \/ parProgNB(i,j)`) resolves through it — so a fairness
spec that re-explores the state graph during trace reconstruction spun for
minutes re-compiling `parProg`/`decideOnForward`/`abortOnTimeout` on every
re-visited state (gdb showed 4+ worker threads deep in
`from_ir`/`compile_expr`/`lower` under `reconstruct_trace_limited`).

Reuse the existing thread-local/prewarmed `get_or_compile_action` cache
instead. ACP_NB_WRONG_TLC now completes in ~143s (previously stalled past
300s in trace reconstruction) with the same verdict (AC1 violated).

Gate: diff_tlc 19/19, cargo test --release clean (the lone
`prop_pause_visibility` blip is the known flaky checkpoint-concurrency
proptest — passes in isolation). Pure caching change; no behavior difference.
@zoratu
zoratu merged commit 4aea62d into main Jul 20, 2026
6 checks passed
@zoratu
zoratu deleted the perf/cache-guard-path-action-ir branch July 20, 2026 04:59
zoratu added a commit that referenced this pull request Jul 20, 2026
`split_action_body_clauses` and `split_action_body_disjuncts` (action_ir.rs)
are pure functions of their input text — the v2 layout-aware parse is
deterministic and `v2_enabled()` is fixed per process — but they are
re-invoked on the SAME action/clause text on every `next_states` call. During
full-space exploration, and especially trace reconstruction (which
re-explores the whole graph), re-running the Pratt parse + string scans per
visit dominated the hot path for fairness/reconstruction specs once #189
removed the compiled-IR recompilation (gdb showed
`split_action_body_clauses`/`split_top_level`/`matches_keyword_at`).

Wrap each splitter as an `_uncached` inner fn behind a thread-local
memoization keyed by the input string (returns a cheap `Vec<String>` clone
instead of re-parsing). Thread-local, so no cross-worker contention.

Result: ACP_NB_WRONG_TLC completes in ~29s (was ~143s post-#189, ~5x), same
verdict (AC1 violated). Gate: diff_tlc 20/20, cargo test --release 1486
passed / 0 failed — pure memoization, no behavior change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant