Repository navigation
CSE hoists across mutations and loop_unroll cannot fire: the census completes 4-for-4 #2629
Description
#2628 left both passes "probed once, not reproduced" and said explicitly that
this is not the same as safe. It wasn't.
Why the first probe missed
expr_key returns Some only for ExprBinary. My probe used a bare call,
compute(x), which has no key — so the pass never saw the input. A probe the
pass cannot see proves nothing, which is why #2628 recorded it as
"did not reproduce" rather than as a clearance.
Reading the trigger first, then constructing to it, found three defects.
Three defects
Stale operand — silent.
var x = a; var p = x + 1; x = 99; var q = x + 1;
emits
const p = x + 1;
const _cse1 = x + 1; // hoisted BEFORE the mutation
x = 99;
const q = _cse1; // q gets the stale value
q should be 100 and is a + 1. Compiles perfectly.
A call deduplicated, and the output malformed.
var p = launch(a) + 1; var q = launch(a) + 1;
emits
const p = launch(a) + 1;
const q = _cse1; // used before it is declared
const _cse1 = ; // empty initialiser
One launch lost, and find_first_non_local put the declaration after its own
use.
Removed rather than guarded
Soundness needs to know that no operand is written between occurrences and that
the expression is pure — dataflow this emitter does not have. Zig's backend does
CSE anyway. Same call as strength_reduce in #2628.
loop_unroll cannot fire at all
Better than "not reproduced" — it is unreachable. The pass requires
children[0] of a StmtFor to be an ExprBinary with op ... The parse of
for i in 0..3 puts three iterables there:
kind: StmtFor
ExprIdentifier "i"
ExprIdentifier "in" <-- `in` parsed as an identifier
...
children[0] is the loop variable, never a range. The condition is never true.
Dead code, not a latent bug.
A defect that fell out of looking
for i in 0..3 emits for (i, in, 0 .. 3) — not Zig in any reading. 5 sites
in 3 files, counted before deciding it was worth chasing. It isn't, yet.
Measured
| before | after | |
|---|---|---|
| valid specs | 130/520 | 131/520 |
| valid AND asserting | 92/454 | 93/454 |
| specs behind a wall | 137 | 134 |
| total errors | 965 | 970 |
| parse gate | 111 / 0 | 111 / 0 |
| regressed | — | 0 |
The +5 errors come from 2 files: CSE was collapsing expressions rather than
fixing them, so removing it exposes what was underneath.
Census complete
| pass | outcome |
|---|---|
copy_propagate |
defect, guarded (#2626) |
const_propagate |
had a precondition |
dead_store_elim |
2 defects, guarded (#2627) |
strength_reduce |
2 defects, removed (#2628) |
common_subexpr_elim |
3 defects, removed |
loop_unroll |
unreachable |
Every pass that could run carried a defect. One had a precondition; the
other four did not, and four of four were wrong.
User Scenarios
- Given the optimization-pass census is complete with all six passes audited, When the census specifications are updated to encode the final outcomes (CSE removed, loop_unroll unreachable, four passes guarded or removed), Then the census tool validates the new baselines: 131 valid specs, 93 valid-and-asserting, 134 behind wall, 970 total errors, 0 regressed.
Requirements
- FR-001: The census spec MUST record that
common_subexpr_elimis removed (not guarded) andloop_unrollis unreachable. - FR-002: The census spec MUST assert the post-census metrics: valid specs = 131, valid AND asserting = 93, specs behind wall = 134, total errors = 970, regressed = 0.
- FR-003: The census tool MUST exit 0 when run against the updated spec with the current compiler build.
Success Criteria
t27c specs/tools/tri/census.t27exits 0t27c specs/tri/mutate/census.t27exits 0- Census tool reports: valid specs 131/520, valid AND asserting 93/454, specs behind wall 134, total errors 970, regressed 0
Boundary
- specs/tools/tri/census.t27
- specs/tri/mutate/census.t27
- cli/tri/src/census.rs
- gen/rust/tri/mutate/census.rs
Activity
fix(opt): remove common_subexpr_elim; loop_unroll is unreachable
gHashTag commentedon Oct 7, 2026
Reopened 2026-10-07: the bulk close of issues without a
## Boundarysection was reverted by the owner. Nothing to do here; this comment replaces the earlier close notice.optimizer: a repeated expression takes a temp only while its operands…
gHashTag commentedon Oct 11, 2026
The CSE half of this issue (the stale operand and the hoist over a mutation) is fixed by #8991 (merged as 122c3ed; rule in
specs/compiler/cse_hoist.t27, cases inspecs/compiler/codegen_conformance.t27). The loop_unroll half was not part of that PR, so I am leaving this open for it.