Skip to content

perry-codegen: integer_locals analysis silently truncates Add/Sub/Mul accumulators that exceed i32 range #435

@proggeramlug

Description

@proggeramlug

Summary

is_int32_producing_expr (crates/perry-codegen/src/collectors.rs:1645-1661) treats Add | Sub | Mul as int-stable when both operands are int-stable. This is wrong: signed-i32 closure under +/−/× only holds when each step's mathematical result fits in i32. needs_i32_slot then installs an i32 shadow that silently truncates 64-bit results from runtime updates.

The recent >>> 0 exclusion (commit 817c4b56) patched one instance of this class. The underlying defect — "int-int closure under +/−/× ⇒ i32-safe" — is intact and produces wrong output in idiomatic JS/TS shapes.

Concrete repros (all diff against Bun)

# Pattern Bun Perry Severity
1 let sum=0; for(...) sum += compute(i) (benchmarks/suite/14_closure.ts) 2,500,000,000,000,000 -1,678,753,792 HIGH
2 const big = a*b both int-stable, product > i32 10,000,000,000 1,410,065,408 HIGH
3 const big = 100000*100000 (literal × literal) 10,000,000,000 1,410,065,408 HIGH
4 let acc=0; for(...) acc -= 100 past -2³¹ -5,000,000,000 -705,032,704 HIGH
5 let prod=1; for(i<=15) prod *= i (factorial) 1,307,674,368,000 2,004,310,016 HIGH
6 const big = a+b both int-stable, sum > i32 4,000,000,000 -294,967,296 HIGH
7 let sum: number = 0; ... += (TS-typed number ignored) 2,500,000,000,000,000 -1,678,753,792 HIGH
8 const c = b + b via forward-closure pass 4,000,000,000 -294,967,296 HIGH
9 i = i + 100 strided counter past i32 4,000,000,000 -294,967,296 HIGH
10 i++ past i32 max 2,147,483,740 -2,147,483,556 MED

Repro for Bug #1

bun benchmarks/suite/14_closure.ts
# sum:2500000000000000

target/release/perry compile benchmarks/suite/14_closure.ts -o /tmp/c14 && /tmp/c14
# sum:-1678753792    ← WRONG

Confirmed safe paths (no fix needed)

  • Math.imul — JS spec mandates i32 truncation; perry matches Bun.
  • (expr) | 0 — JS ToInt32 produces signed i32.
  • Single Integer literal init guarded by i32::try_from(*n).is_ok().
  • (expr) >>> 0 — excluded after 817c4b56.
  • returns_int_expr post-817c4b56 — only accepts Integer + signed-spec bitwise + Math.imul.

Affected sites

  • crates/perry-codegen/src/collectors.rs:1645-1661 — Add/Sub/Mul arm of is_int32_producing_expr (root cause).
  • crates/perry-codegen/src/collectors.rs:1762 — Integer-literal seed feeding mutable accumulators.
  • crates/perry-codegen/src/collectors.rs:1494-1525collect_extra_integer_let_ids forward-closure pass.
  • crates/perry-codegen/src/stmt.rs:532-541needs_i32_slot should consult HIR Number type for explicit annotations.

Proposed fix shape

One of:

  • Reject Add/Sub/Mul in is_int32_producing_expr unless operands are statically bounded.
  • Maintain f64 as the read source whenever any Add/Sub/Mul write could overflow.
  • Honor : number type annotations as opting OUT of int-shadow at the seed site.

Found during the May-2026 release-readiness sweep, after fixing the related >>> 0 bug class (817c4b56). 14_closure is the canonical case; the same shape appears anywhere TS code computes a large total via +=.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions