Repository navigation
Description
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 ← WRONGConfirmed safe paths (no fix needed)
Math.imul— JS spec mandates i32 truncation; perry matches Bun.(expr) | 0— JSToInt32produces signed i32.- Single Integer literal init guarded by
i32::try_from(*n).is_ok(). (expr) >>> 0— excluded after817c4b56.returns_int_exprpost-817c4b56— only accepts Integer + signed-spec bitwise + Math.imul.
Affected sites
crates/perry-codegen/src/collectors.rs:1645-1661— Add/Sub/Mul arm ofis_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_idsforward-closure pass.crates/perry-codegen/src/stmt.rs:532-541—needs_i32_slotshould consult HIRNumbertype for explicit annotations.
Proposed fix shape
One of:
- Reject Add/Sub/Mul in
is_int32_producing_exprunless operands are statically bounded. - Maintain f64 as the read source whenever any Add/Sub/Mul write could overflow.
- Honor
: numbertype 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
perf(codegen): #436 close image_convolution gap — strict-i32-bounded …