Skip to content

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

Closed

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-1525 — collect_extra_integer_let_ids forward-closure pass.
  • crates/perry-codegen/src/stmt.rs:532-541 — needs_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 +=.

Activity

  1. added a commit that references this issue on May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions