You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
repsel: canonical-i32 requires index-use or a proven i32 bound, so a bare integer accumulator never promotes anywhere #7110
A bare integer accumulator or loop counter never gets canonical-i32 storage
anywhere — function body or module top level — because the eligibility rule
requires the local to be used as an array index or to be provably i32-bounded.
Being proven integer-valued is not enough.
Mechanism
crates/perry-codegen/src/stmt/let_stmt.rs:1224:
let i32_safe_local = ctx.index_used_locals.contains(&id)
|| ctx.strictly_i32_bounded_locals.contains(&id)
|| is_unsigned_i32_local;
and at :1250 the canonical gate widens it only by int_valued_ta_locals:
let canonical_safe_local =
i32_safe_local || ctx.native_facts.int_valued_ta_locals().contains(&id);
So a local in integer_locals that is never indexed with, never proven
i32-bounded, not >>> 0-written and not an int-typed-array read falls through
to the boxed/parallel-shadow model.
Evidence
--opt-report=json --no-link, --profile perry-dev, macOS arm64. This is in a function body, so the module-init exclusion (separate issue) is not in play:
Report: no canonical-slot entry — neither i nor sum. Add a single a[i] read to the body and i is immediately canonical-slot selected … rep=I32. The only difference is index use.
This is the shape of benchmarks/suite/02_loop_overhead.ts (definitionally a
loop counter), 06_math_intensive.ts, 13_factorial.ts, 14_closure.ts, and
the iter/px/py counters in 15_mandelbrot.ts.
Why the rule is this narrow
The audit note in crates/perry-codegen/src/expr/slot_rep.rs (Range soundness,
2026-07-27) explains it: admission via integer_locals ∩ index_used_locals
accepts Add/Sub/Mul chains that can in principle exceed i32, and it is sound
today only because the pre-phase shadow model already read the i32 slot for
exactly that set of locals. So the precondition is not a range proof — it is a
"this set already behaved this way" compatibility bound. Widening it needs a
real range argument (or a wrap-on-overflow contract like the one int_valued_ta_locals carries), not just dropping the term.
Now visible
#7106 follow-up (this PR) records this as a denial with rule not_index_used_or_bounded, tier compiler-limitation, instead of no entry at
all. --opt-report on 02_loop_overhead.ts now names the rule for i and sum rather than showing an empty report.
Activity
test(repsel): make "no analysis ran" a visible, gated state
test(repsel): make "no analysis ran" a visible, gated state (#7113)
perf(repsel): admit constant-bounded loop induction variables to cano…
perf(repsel): admit constant-bounded loop induction variables to cano…