pi-web crashes with SIGILL on RISC-V (riscv64): next-server dies ~5s after "Ready" — needs --no-wasm-lazy-compilation
Summary
On RISC-V single-board computers (SpacemiT K1 / Lichee Pi 3A in our case), pi-web starts normally but the next-server child process is killed by SIGILL (illegal instruction) about 5–8 seconds after ✓ Ready, with no client traffic involved. The parent monitor reports:
▲ Next.js 16.3.1
- Local: http://127.0.0.1:30141
✓ Ready in 1632ms
⚠ next-swc does not have native bindings support for target triple [object Object]. ...
[pi-web] Next.js exited unexpectedly (signal SIGILL)
We traced it to a V8 bug in wasm lazy-compilation on riscv64 (see linked node report below). A one-line change to the spawn() call in bin/pi-web.js fully fixes it on our hardware. Verified stable for 90+ s with real page rendering.
Environment
|
|
| Board |
Sipeed Lichee Pi 3A — SpacemiT K1, 8× T-Heng X60, 16 GB RAM |
| OS |
Bianbu 1.0.15 "Mantic Minotaur" (Ubuntu mantic base), kernel 6.1.15, glibc 2.38 |
| CPU ISA |
rv64imafdcv_sscofpmf_sstc_svpbmt_zicbom_zicboz_zicbop_zihintpause — no Zba/Zbb/Zcb, no riscv_hwprobe syscall on this kernel |
| Node |
v26.0.0, unofficial-builds linux-riscv64 (non-pointer-compression); also reproduced on v24.20.0 |
| pi-web |
0.8.11 (Next.js 16.3.1) |
Root cause chain
- Next.js has no native SWC binding for
linux/riscv64, so it falls back to the wasm build of SWC (next/wasm/@next/swc-wasm-nodejs/wasm_bg.wasm). (The loader in next/dist/build/swc/index.js also lacks a riscv64 entry entirely — same failure mode documented here: https://dev.to/gounthar/the-one-line-patch-that-unlocked-nextjs-on-risc-v-a-detective-story-71h)
- V8's wasm lazy compilation is broken on riscv64 in these builds: a JS→wasm call jumps into a zero-filled (never-populated) code page → SIGILL. gdb shows the crash
ra inside Builtins_JSToWasmWrapperAsm+184, crash PC in an anonymous code-space mapping filled with 0x00000000:
Thread 1 "next-server (v1" received signal SIGILL, Illegal instruction.
=> 0x0000003fa86cba28: unimp (x/8i: eight consecutive `unimp`)
x/8xw $pc-16: 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
ra = Builtins_JSToWasmWrapperAsm + 184
Kernel dump (identical in every crash, page offset 0xa28 constant, t0=0x6bae constant):
next-server (v1[4259]: unhandled signal 4 code 0x1 at 0x00000011d183fa28
cause: 0000000000000002 (RISC-V: illegal instruction)
- Disabling wasm lazy compilation (
--no-wasm-lazy-compilation, i.e. eager compile at module instantiation) completely avoids the crash — everything else in pi-web works.
We verified that the following did not help (all still SIGILL): --no-wasm-code-gc, --no-wasm-tier-up, --no-liftoff, --no-flush-baseline-code --no-flush-liftoff-code --no-flush-bytecode --no-flush-code-based-on-time, OPENSSL_riscvcap= (empty), NEXT_TELEMETRY_DISABLED=1, downgrading to Node v24.20.0. Simple hand-written wasm (tiny function ×500k calls, and a 5000-function module) runs fine — the trigger requires the big Rust→wasm module (SWC).
Suggested fix
In bin/pi-web.js, the child spawn currently looks like:
const child = spawn(process.execPath, [nextBin, ...nextArgs], {
cwd: pkgDir,
stdio: ["inherit", "pipe", "inherit"],
env: { ...process.env, PI_WEB_HOSTNAME: hostname },
});
Pass the flag as a real argv element (guard on process.arch, zero impact on other platforms):
-const child = spawn(process.execPath, [nextBin, ...nextArgs], {
+const child = spawn(
+ process.execPath,
+ [
+ // riscv64-wasm-lazy-workaround: V8 wasm lazy-compilation on riscv64
+ // jumps into unpopulated code pages -> SIGILL — nodejs/node#65724.
+ ...(process.arch === "riscv64" ? ["--no-wasm-lazy-compilation"] : []),
+ nextBin,
+ ...nextArgs,
+ ],
+ {
cwd: pkgDir,
stdio: ["inherit", "pipe", "inherit"],
env: { ...process.env, PI_WEB_HOSTNAME: hostname },
-});
+ }
+);
Notes from our testing:
Reproduction (any riscv64 SBC with ≥4 GB RAM)
npm i -g @agegr/pi-web@0.8.11
pi-web --port 30141
# dies ~5-8s after "Ready"; monitor prints:
# [pi-web] Next.js exited unexpectedly (signal SIGILL)
# control:
node --no-wasm-lazy-compilation "$(npm root -g)/@agegr/pi-web/bin/pi-web.js" --port 30141
# stays alive; GET / returns the real page (we served 9756-byte HTML repeatedly for 90s, 0 SIGILL)
Upstream links
— Acidmoon, KUBUDS Tech
pi-web crashes with SIGILL on RISC-V (riscv64): next-server dies ~5s after "Ready" — needs
--no-wasm-lazy-compilationSummary
On RISC-V single-board computers (SpacemiT K1 / Lichee Pi 3A in our case),
pi-webstarts normally but thenext-serverchild process is killed by SIGILL (illegal instruction) about 5–8 seconds after✓ Ready, with no client traffic involved. The parent monitor reports:We traced it to a V8 bug in wasm lazy-compilation on riscv64 (see linked node report below). A one-line change to the
spawn()call inbin/pi-web.jsfully fixes it on our hardware. Verified stable for 90+ s with real page rendering.Environment
rv64imafdcv_sscofpmf_sstc_svpbmt_zicbom_zicboz_zicbop_zihintpause— no Zba/Zbb/Zcb, noriscv_hwprobesyscall on this kernellinux-riscv64(non-pointer-compression); also reproduced on v24.20.0Root cause chain
linux/riscv64, so it falls back to the wasm build of SWC (next/wasm/@next/swc-wasm-nodejs/wasm_bg.wasm). (The loader innext/dist/build/swc/index.jsalso lacks ariscv64entry entirely — same failure mode documented here: https://dev.to/gounthar/the-one-line-patch-that-unlocked-nextjs-on-risc-v-a-detective-story-71h)rainsideBuiltins_JSToWasmWrapperAsm+184, crash PC in an anonymous code-space mapping filled with0x00000000:0xa28constant,t0=0x6baeconstant):--no-wasm-lazy-compilation, i.e. eager compile at module instantiation) completely avoids the crash — everything else in pi-web works.We verified that the following did not help (all still SIGILL):
--no-wasm-code-gc,--no-wasm-tier-up,--no-liftoff,--no-flush-baseline-code --no-flush-liftoff-code --no-flush-bytecode --no-flush-code-based-on-time,OPENSSL_riscvcap=(empty),NEXT_TELEMETRY_DISABLED=1, downgrading to Node v24.20.0. Simple hand-written wasm (tiny function ×500k calls, and a 5000-function module) runs fine — the trigger requires the big Rust→wasm module (SWC).Suggested fix
In
bin/pi-web.js, the child spawn currently looks like:Pass the flag as a real argv element (guard on
process.arch, zero impact on other platforms):Notes from our testing:
spawn()'sexecArgvoption did not reach the child in our tests — this also holds on official linux-x64 builds, not a riscv64 quirk (riscv64: SIGILL in JS->wasm wrapper — lazy compilation enters unpopulated code pages (unofficial-builds v24.20.0 & v26.0.0) nodejs/node#65724, with the spawn() quirk in child_process: spawn()'s execArgv option is silently ignored (fork() honors it) nodejs/node#65725). Prepending into the argv list is the reliable form.PI_WEB_NODE_FLAGS) honored in the spawn would let downstream users work around V8/platform bugs without waiting for releases.Reproduction (any riscv64 SBC with ≥4 GB RAM)
Upstream links
riscv64ingetSupportedArchTriples): https://github.com/vercel/next.js — related write-up https://dev.to/gounthar/the-one-line-patch-that-unlocked-nextjs-on-risc-v-a-detective-story-71h— Acidmoon, KUBUDS Tech