Summary
A program that defines two functions — one taking a `Buffer` parameter, one taking a `Uint8Array` parameter — and invokes them in sequence on the same underlying buffer crashes with SIGTRAP (exit 133) in the second call. The same functions in isolation both work correctly. Reproducible on current `main` with zero local changes applied.
Discovered while extending the #92 / #166 buffer-read intrinsic to cover Buffer-typed params. Blocks extending the extension to Uint8Array-typed params (both shapes are identical from the intrinsic's perspective), so this issue gates that follow-up work.
Repro (crashes on main at v0.5.179)
```ts
const big = Buffer.alloc(1024);
for (let i = 0; i < 256; i++) big.writeInt32BE(i * 37, i * 4);
function sumRow(row: Buffer, n: number): number {
let s = 0;
for (let i = 0; i < n; i++) s += row.readInt32BE(i * 4);
return s;
}
console.log('param sum:', sumRow(big, 256)); // prints 1207680 correctly
function firstBytes(arr: Uint8Array): number {
return arr[0] + arr[1] + arr[2] + arr[3];
}
console.log('u8 param:', firstBytes(big)); // crashes
```
```
$ perry compile repro.ts -o repro && ./repro
param sum: 1207680
; exit: 133
```
Node and Bun both complete normally and print `u8 param: 0`.
Bisect
Each call works in isolation — removing either function eliminates the crash:
| Scenario |
Result |
| `firstBytes(big)` alone |
✅ prints 0 |
| `sumRow(big, 256)` alone |
✅ prints 1207680 |
| Both, `firstBytes` first |
(untested — may not crash) |
| Both, `sumRow` first |
❌ SIGTRAP in `firstBytes` |
Tried disabling the #166 Buffer-read intrinsic entirely at the codegen level (`try_emit_buffer_read_intrinsic` forced to return `None`): crash still happens. So the intrinsic is not the cause; this is a pre-existing bug in the interaction between the two param shapes.
Crash signal
`lldb` shows the program stops on `brk #0x1` — an ARM64 instruction LLVM emits as a `noreturn` marker after a call-to-panic. So some Perry runtime helper is calling a panic/abort function inside `firstBytes`. Without debug symbols it's hard to tell which helper, but the likely candidates:
- Runtime NaN-box unbox assertions (`unbox_buffer_ptr` / `unbox_uint8array_ptr`)
- GC write-barrier or arena-bounds assertion
- A type-check failure on the Uint8Array receiver
Worth running with `PERRY_DEBUG_SYMBOLS=1` and a fresh lldb to localize.
Fix direction
Unknown without further investigation — this isn't my area. Possible angles:
- Some per-function state in codegen is leaking across function boundaries and causing the second function to look up a stale slot
- The Uint8Array fast path at `expr.rs:4685-4699` has a latent issue that only surfaces after a Buffer fast path in the same compilation
- A runtime registry is getting corrupted by the first call's arena operations
Impact
Related
Environment
- Perry 0.5.179 on macOS arm64 (also reproducible on `main` at commit 6e57861)
Summary
A program that defines two functions — one taking a `Buffer` parameter, one taking a `Uint8Array` parameter — and invokes them in sequence on the same underlying buffer crashes with SIGTRAP (exit 133) in the second call. The same functions in isolation both work correctly. Reproducible on current `main` with zero local changes applied.
Discovered while extending the #92 / #166 buffer-read intrinsic to cover Buffer-typed params. Blocks extending the extension to Uint8Array-typed params (both shapes are identical from the intrinsic's perspective), so this issue gates that follow-up work.
Repro (crashes on main at v0.5.179)
```ts
const big = Buffer.alloc(1024);
for (let i = 0; i < 256; i++) big.writeInt32BE(i * 37, i * 4);
function sumRow(row: Buffer, n: number): number {
let s = 0;
for (let i = 0; i < n; i++) s += row.readInt32BE(i * 4);
return s;
}
console.log('param sum:', sumRow(big, 256)); // prints 1207680 correctly
function firstBytes(arr: Uint8Array): number {
return arr[0] + arr[1] + arr[2] + arr[3];
}
console.log('u8 param:', firstBytes(big)); // crashes
```
```
$ perry compile repro.ts -o repro && ./repro
param sum: 1207680
; exit: 133
```
Node and Bun both complete normally and print `u8 param: 0`.
Bisect
Each call works in isolation — removing either function eliminates the crash:
Tried disabling the #166 Buffer-read intrinsic entirely at the codegen level (`try_emit_buffer_read_intrinsic` forced to return `None`): crash still happens. So the intrinsic is not the cause; this is a pre-existing bug in the interaction between the two param shapes.
Crash signal
`lldb` shows the program stops on `brk #0x1` — an ARM64 instruction LLVM emits as a `noreturn` marker after a call-to-panic. So some Perry runtime helper is calling a panic/abort function inside `firstBytes`. Without debug symbols it's hard to tell which helper, but the likely candidates:
Worth running with `PERRY_DEBUG_SYMBOLS=1` and a fresh lldb to localize.
Fix direction
Unknown without further investigation — this isn't my area. Possible angles:
Impact
Related
Environment