Repro
function copy(src: Buffer, n: number): Buffer {
console.log(' entered copy, src[0]=', src[0]);
const dst = Buffer.alloc(n);
console.log(' dst alloc ok, dst.length=', dst.length);
for (let i = 0; i < n; i++) dst[i] = src[i];
console.log(' fill done, dst[0]=', dst[0]);
return dst;
}
const N = 25 * 1024 * 1024;
const buf = Buffer.alloc(N);
for (let i = 0; i < N; i++) buf[i] = i & 0xff;
console.log('before call, buf[0]=', buf[0], 'buf.length=', buf.length);
const out = copy(buf, N);
console.log('after call, out[0]=', out[0]);
Expected
before call, buf[0]= 0 buf.length= 26214400
entered copy, src[0]= 0
dst alloc ok, dst.length= 26214400
fill done, dst[0]= 0
after call, out[0]= 0
Actual (Perry 0.5.30)
before call, buf[0]= 0 buf.length= 26214400
entered copy, src[0]= 0.0000…0007949928895127363
dst alloc ok, dst.length= 26214400
(exit=0, no further output)
Observations
src[0] inside the function reads as a tiny denormal float, not 0. That's a NaN-boxed pointer-bit-pattern being interpreted as a double — src has lost its Buffer-ness at the function boundary.
Buffer.alloc(n) inside copy() succeeds.
- The subsequent bracket-write loop silently corrupts the process. No SIGSEGV, no stderr, just
exit=0 before 'fill done' prints.
Hypothesis
Param src lands in a callee-saved register that the conservative stack scan can't see. The Buffer.alloc(n) inside the body crosses the GC threshold, sweeps src, and either the for-loop reads the freed header or the write corrupts adjacent state.
Workaround (used in honest_bench/workloads/3_image_convolution/perry/image_conv.ts)
Hoist both buffers to module-level const globals. v0.5.28 registers module globals as explicit GC roots, so they survive collection.
Scale
- At ~25 MB (4K RGB image size) reproduces reliably.
- Smaller buffers work — haven't bisected the exact threshold.
Repro
Expected
Actual (Perry 0.5.30)
Observations
src[0]inside the function reads as a tiny denormal float, not0. That's a NaN-boxed pointer-bit-pattern being interpreted as a double —srchas lost its Buffer-ness at the function boundary.Buffer.alloc(n)insidecopy()succeeds.exit=0before'fill done'prints.Hypothesis
Param
srclands in a callee-saved register that the conservative stack scan can't see. TheBuffer.alloc(n)inside the body crosses the GC threshold, sweepssrc, and either the for-loop reads the freed header or the write corrupts adjacent state.Workaround (used in
honest_bench/workloads/3_image_convolution/perry/image_conv.ts)Hoist both buffers to module-level
constglobals. v0.5.28 registers module globals as explicit GC roots, so they survive collection.Scale