Skip to content

i32 fast-path: copying a local seeded with a >2^31 integer literal wraps (third face of #6072) #6319

Description

@proggeramlug

Severity: P1 (silent data corruption)
Found while fixing: #6072 repro 2 (PR #6318)

What

An integer literal beyond i32 range still seeds integer_locals, so a plain copy of such a local is judged "strictly i32-bounded" and gets a wrapping i32 shadow:

let x = 3000000000;
let y = 0;
y = x;
console.log("y:", y);   // node: 3000000000   perry: -1294967296

let z = 0;
z = x + 4;
console.log("z:", z);   // node: 3000000004   perry: 3000000004  (correct — Add isn't strict)

Reproduces on main (v0.5.1258, 0960415ad) and is unaffected by #6258 and #6318 — it is a third, distinct face of #6072's title ("i32 fast-path locals silently wrap at 2^31"), reached through the copy path rather than through x++ (#6258) or a dynamic loop bound (#6318).

Why

collect_integer_let_ids (crates/perry-codegen/src/collectors/i32_locals.rs:615) seeds any Expr::Integer(_) init into integer_locals, regardless of magnitude. is_strictly_i32_bounded_expr's Expr::LocalGet(id) arm then answers known_int_locals.contains(id) — so y = x counts as a strictly-i32-bounded write, y gets an i32 shadow at its Let site, and the write is lowered via lower_expr_as_i32, which ToInt32-wraps the 3e9 double. Reads then prefer the shadow (issue #48), so the wrapped value is what console.log sees.

x itself stays correct: its own Let-site slot is gated on init_in_i32_range. Only locals that copy it inherit the bogus "i32-bounded" judgement.

Fix sketch

Narrow the Expr::Integer seed in collect_integer_let_ids to literals that fit in i32 (i32::try_from(*n).is_ok()). That also removes a latent poison fptosi in the arr.length-hoist and static-i < n loop paths, which seed a counter's i32 shadow from integer_locals membership alone.

Not a drive-by: integer_locals also feeds can_lower_expr_as_i32 / lower_expr_as_i32, so a const SEED = 0x9E3779B9 (2654435769 > INT32_MAX) operand would drop out of the i32 arithmetic chain that the compiler_output_regression image-convolution / FNV-1a gate watches. Wants its own PR with that gate run.

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