Skip to content

codegen: typed-array element loop reads wrong sum when object has a sibling Array field #6132

Description

@proggeramlug

Summary

Summing a TypedArray field of an object with an element-index for loop returns the wrong result when the object also has a plain-Array field. Reproduces with a single object, synchronously, with no async and no GC pressure — so it is a codegen bug, not a GC bug. In simple cases it's a deterministic wrong answer; under retention/allocation it becomes non-deterministic (mis-reads varying adjacent memory), which is how it surfaced while validating the moving GC.

Repro (deterministic)

const n = { id: 5, payload: [1, 2, 3, 4], buf: new Uint32Array([10, 20, 30]) };
let s = 0;
for (const p of n.payload) s += p;                       // 10
for (let i = 0; i < n.buf.length; i++) s += n.buf[i];    // +60
console.log(s);   // node: 70   |   Perry: 10   (the buf loop contributes 0)

Key observations

  • Direct field access is CORRECTn.buf.length is 3, n.buf[0..2] are 10,20,30, n.buf.constructor.name is Uint32Array, and this holds with the typed array as the only field, first field, or after the array field. So the object literal stores the typed array fine.
  • The bug is specifically the element-index loop for (let i=0; i<n.buf.length; i++) ... n.buf[i] reading a typed-array field of an object that also has an Array field. Perry’s packed-numeric loop optimizations (crates/perry-codegen/src/stmt/loops.rsmatch_packed_f64_versioned_loop / packed i32/u32 paths, typed-feedback guards) emit raw element loads; a guard/alias misfire when the receiver object has a sibling Array field is the likely cause.
  • Control cases that are CLEAN: {id, a:number[], b:number[]} (two plain arrays); {id, buf} (typed array only); reading n.buf[i] directly rather than in the summing loop.

Impact

Silent wrong results for a common pattern (objects holding both a JS array and a typed array, iterated by index). Because the mis-read can also read/write adjacent heap memory, it corrupts the heap and makes any subsequent collector (moving or not) fragile — this is what made a moving-GC stress test crash; the GC was choking on a heap this bug had already corrupted.

Next step

Localize with --trace hir --focus on the loop and --trace llvm; check the packed-numeric-loop guard/versioning when the loop receiver is a typed-array field on an object with other pointer fields.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions