Skip to content

i32 fast-path locals and loop counters silently wrap at 2^31 #6072

Description

@proggeramlug

Severity: P1 (silent data corruption; potential infinite loop)
Found by: audit fable-audit-perry-2.md, confirmed by probe.

What

Locals and loop counters that codegen classifies into i32 slots do a plain add i32 with no overflow handling, so they wrap at 2³¹ instead of promoting to double.

let big = 2147483640;
for (let k = 0; k < 10; k++) big++;
console.log(big);           // node: 2147483650   perry: -2147483646

// runtime-bound loop crossing 2^31:
for (let i = 2147483640; i < lim; i++) { ... }   // lim = 2147483653
// node: 13 iterations      perry: i wraps negative past 2^31 → spins (only a safety break stopped the probe)

Why

crates/perry-codegen/src/expr/literals_vars.rs:796 (let new_i32 = blk.add(I32, &old_i32, delta);) and the raw add(I32, …) sites in expr/i32_fast_path.rs:635,994 and stmt/loops.rs:912,1980 emit unchecked i32 adds. The range guards validate the initializer, not that increments stay below 2³¹ when the bound is a runtime value.

Fix

Emit llvm.sadd.with.overflow.i32 on the update path and bail to the double representation on overflow, or gate the i32 slot on a range_facts proof that the loop/accumulator bound is provably < 2^31.

Confidence: high (probe + source).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions