What happened
An array built with push inside a function, returned from a direct call,
and stored into any variable (local or global) is lost once its backing store
exceeds 32MiB. Deterministic, default GC, perry run and compiled binaries
alike. Numeric arrays come back with length === 0 (OOB reads give NaN);
object-element arrays get a garbage length (e.g. 2775006632) — memory
corruption. Exact boundary: build(4194304) (2^22 f64 slots = 32MiB) is
correct, build(4194305) is empty; object arrays fail from 2^21 (same 32MiB).
Same size works via: direct use (build(n).length), indirect call
(var f = build; f(n)), IIFE, or inline module-scope build.
What you expected
b.length === 4194305 (Node agrees). Perry prints 0.
Minimal reproduction
function build(n) {
var a = [];
for (var i = 0; i < n; i++) a.push(i);
return a;
}
var b = build(4194305); // 2^22 elements is fine; 2^22+1 loses everything
console.log(b.length, b[0]);
Command you ran:
Environment
- Perry version: 0.5.1220
- Host OS: macOS 27.0 (arm64)
- Target: native
- Installed via: npm
Diagnostic output
$ perry run arrayloss.js
0 NaN
$ node arrayloss.js
4194305 0
PERRY_GC_DIAG=1 on the compiled binary: one full collection frees ~32MiB —
the array's live buffer is swept (evacuation never runs, moved_objects: 0):
[gc] blocks: general=1 (1 live), longlived=10 (1 live), freed_bytes=33538224 retained_forwarded_stub_bytes=16368 retained_forwarded_stub_objects=7
Looks like the grow past 32MiB triggers a full GC that doesn't see the
direct-call return value as a root.
Anything else
Reproduces with PERRY_NO_CACHE=1; no PERRY_GEN_GC* / stack-scan setting
changes the outcome. Distinct from #5467/#5459/#6219, which need
PERRY_GC_FORCE_EVACUATE=1 or explicit gc() — this is default settings,
and no evacuation is involved. Found while benchmarking for #6223.
What happened
An array built with
pushinside a function, returned from a direct call,and stored into any variable (local or global) is lost once its backing store
exceeds 32MiB. Deterministic, default GC,
perry runand compiled binariesalike. Numeric arrays come back with
length === 0(OOB reads giveNaN);object-element arrays get a garbage length (e.g.
2775006632) — memorycorruption. Exact boundary:
build(4194304)(2^22 f64 slots = 32MiB) iscorrect,
build(4194305)is empty; object arrays fail from 2^21 (same 32MiB).Same size works via: direct use (
build(n).length), indirect call(
var f = build; f(n)), IIFE, or inline module-scope build.What you expected
b.length === 4194305(Node agrees). Perry prints0.Minimal reproduction
Command you ran:
Environment
Diagnostic output
PERRY_GC_DIAG=1on the compiled binary: one full collection frees ~32MiB —the array's live buffer is swept (evacuation never runs,
moved_objects: 0):Looks like the grow past 32MiB triggers a full GC that doesn't see the
direct-call return value as a root.
Anything else
Reproduces with
PERRY_NO_CACHE=1; noPERRY_GEN_GC*/ stack-scan settingchanges the outcome. Distinct from #5467/#5459/#6219, which need
PERRY_GC_FORCE_EVACUATE=1or explicitgc()— this is default settings,and no evacuation is involved. Found while benchmarking for #6223.