You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
v26.0.0 (node -v), unofficial-builds linux-riscv64.tar.xz, V8 14.6.202.33-node.19, pointer compression off
(also reproduces on v24.20.0 of the same line; both are currently-supported releases)
Platform
Linux lpi3a 6.1.15 #1.0.15.1 SMP PREEMPT Sat Sep 7 07:20:18 UTC 2024 riscv64 riscv64 riscv64 GNU/Linux
Hardware: Sipeed Lichee Pi 3A — SpacemiT K1, 8× T-Heng X60, 16 GB RAM.
OS: Bianbu 1.0.15 "Mantic Minotaur" (Ubuntu mantic base), glibc 2.38.
CPU ISA (per /proc/cpuinfo): rv64imafdcv_sscofpmf_sstc_svpbmt_zicbom_zicboz_zicbop_zihintpause — no Zba/Zbb/Zcb/Zfa; MMU sv39; RVV 1.0 works (verified with userspace vector benchmarks).
Kernel 6.1.15 has no riscv_hwprobe syscall, so V8 must fall back to AT_HWCAP for feature detection.
The node binary itself runs general workloads fine on this machine (npm install trees, 500k-call JS/wasm loops, NAPI addons incl. sharp/clipboard prebuilds — none crash).
We could not reduce the trigger below a large Rust→wasm module (see notes under “Additional information” about synthetic attempts), so the shortest reliable repro uses one publicly-published npm app that ships the wasm fallback of SWC inside next:
# on the riscv64 machine (Node >= 22.19 from https://unofficial-builds.nodejs.org/download/release/ )
npm i -g @agegr/pi-web@0.8.11
pi-web --port 30141
Output observed (no client traffic involved):
▲ 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)
Control (only difference = one V8 flag):
node --no-wasm-lazy-compilation "$(npm root -g)/@agegr/pi-web/bin/pi-web.js" --port 30141
# → stays alive indefinitely; serving real pages (verified 90+ s, HTTP 200s at 15/45/75 s, zero SIGILL)
The crashing wasm module is node_modules/next/wasm/@next/swc-wasm-nodejs/wasm_bg.wasm (Next.js 16.3.1), which Next.js loads automatically on platforms without a native SWC binding.
How often does it reproduce? Is there a required condition?
Deterministic, ~100% on this machine. Required conditions:
riscv64 node from unofficial-builds, default V8 flags (both the standard and the pointer-compression flavors reproduce — see controls below);
a large wasm module whose functions are called lazily in a long-running process;
crash fires 5–8 s after the app becomes ready with zero requests — it is self-triggered, not traffic-triggered.
All of the following were tested and do not change the outcome (still SIGILL at the same code-page offset): --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, --no-riscv-b-extension (already default-off on this build), and Node v24.20.0 vs v26.0.0.
What is the expected behavior? Why is that the expected behavior?
Expected: WebAssembly functions compile lazily on first call (or eagerly if lazy is disabled) and execute correctly, as on all other supported architectures.
Why we believe this is an engine bug and not userland / not “unsupported hardware”:
The crash PC is inside an anonymous executable mapping owned by V8's code space, filled with 0x00000000 (RISC-V zero word = illegal instruction) — i.e. execution entered a code page that was never populated.
The return address at the crash is inside Node's own V8 builtin Builtins_JSToWasmWrapperAsm — the JS→wasm call trampoline itself. Nothing userland controls that jump target.
The crashing instruction offset is byte-identical across runs (page offset 0xa28, constant register values) — a deterministic codegen/stub-patching defect, not corruption.
Pure V8 flag (--no-wasm-lazy-compilation) makes it disappear; every non-wasm path on this binary is healthy.
The CPU fully supports the base ISA V8 uses here (rv64gc); no Zb*/Zfa instructions are involved (V8's B-extension use is default-off, and the same flag matrix shows the fault follows wasm lazy compilation, not ISA extensions).
(cause 2 = RISC-V "illegal instruction"; the low page offset is always 0xa28; t0 is always 0x6bae.)
Under gdb:
Thread 1 "next-server (v1" received signal SIGILL, Illegal instruction.
0x0000003fa86cba28 in ?? ()
(gdb) x/8i $pc
=> 0x3fa86cba28: unimp (eight consecutive `unimp`)
(gdb) x/8xw $pc-16
0x3fa86cba18: 0x00000000 0x00000000 0x00000000 0x00000000
0x3fa86cba28: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) p/x $ra
$1 = 0x2aabf96458
(gdb) info symbol $ra
Builtins_JSToWasmWrapperAsm + 184 in section .text of .../bin/node
Additional information
Relationship to #64538 (important). We are aware of the concurrent riscv64 wasm SIGILL thread (#64538, RVV/SIMD on SpacemiT chips) and believe our non-pc-flavor report is a distinct mechanism, not the same root cause:
On the exact crashing binary (non-pointer-compression v26.0.0), V8 refuses to compile SIMD-containing modules — we verified directly: a SIMD wasm (llhttp.wasm shipped with Next.js) dies with CompileError: Wasm SIMD unsupported instead of executing, so none of the VU::SetSimd128* RVV lowering paths can be reached by the crashing app;
the fixer is orthogonal to SIMD: --no-wasm-lazy-compilation (SIMD support untouched) eliminates our crash 100%.
Caveat for honesty: the pointer-compression flavor also SIGILLs on this board, but we have not yet captured its kernel badaddr signature — if it turns out to be a real RVV instruction word, that flavor's crash belongs to #64538 and only the non-pc zero-page crash is new. We will capture it when our board is next reachable and post an update here.
Build-flavor control. The linux-riscv64-pointer-compression variant of the same v26.0.0 release also crashes, at the same page offset 0xa28 with the same register signature — so the bug is riscv64-wide, not tied to the non-pointer-compression flavor.
Synthetic-reduction attempts (negative results). A 45-byte add module called 500k times, and generated modules with thousands of trivial exported functions called in sequence, all run fine — so a minimal pure-builtins snippet does not (yet) reproduce. The trigger appears tied to how large Rust→wasm modules are consumed (we suspect re-entrant import→export calls and/or wide first-call coverage across many functions). We can share our stress-generator scripts, and — more usefully — test candidate patches/flags on this exact board via ssh (8-core X60, always reachable); we can also try to continue bisecting with --trace-wasm-* on request.
Why userland is exonerated (per the guideline about userland modules): the only userland code involved is Next.js choosing its documented wasm-SWC fallback on an unmapped platform; the fault itself is in Node's V8 trampoline→code-space boundary as shown above. The identical app works on x86_64/aarch64. A prior riscv64 field report of the same fallback crashing is documented here: https://dev.to/gounthar/the-one-line-patch-that-unlocked-nextjs-on-risc-v-a-detective-story-71h
Related platform facts (context, independent of this crash):
QEMU control (negative — very likely the key localization clue): the same node binary and app under qemu-system-riscv64 -cpu max (all extensions enabled) + our mainline 7.2.0-rc7 kernel (withriscv_hwprobe, so V8 detects Zba/Zbb/Zfa/…) does not crash — ran 900 s with traffic every 30 s, GET / consistently OK (wasm-SWC config compile took 4.1 min under TCG and completed). The board differs in two coupled ways: no hwprobe on kernel 6.1.15 → V8 falls back to AT_HWCAP → reduced feature set → no-B fallback code path. Suspect the bug lives in V8's riscv64 code path taken when B-class extensions are not detected. (We could not test -cpu with B off using this guest image — its userspace is RVA23 and fails to boot without Zb*; would need an rv64gc userspace rootfs, e.g. Debian riscv64. Happy to run that test for you — the board is not required if a suitable rootfs is specified.)
Because kernel 6.1.15 lacks riscv_hwprobe, V8 additionally reports “Wasm SIMD unsupported” and cleanly refuses SIMD-containing modules (e.g. llhttp.wasm → CompileError). This SIGILL is on non-SIMD modules.
Version
v26.0.0 (
node -v), unofficial-buildslinux-riscv64.tar.xz, V8 14.6.202.33-node.19, pointer compression off(also reproduces on v24.20.0 of the same line; both are currently-supported releases)
Platform
Hardware: Sipeed Lichee Pi 3A — SpacemiT K1, 8× T-Heng X60, 16 GB RAM.
OS: Bianbu 1.0.15 "Mantic Minotaur" (Ubuntu mantic base), glibc 2.38.
CPU ISA (per
/proc/cpuinfo):rv64imafdcv_sscofpmf_sstc_svpbmt_zicbom_zicboz_zicbop_zihintpause— no Zba/Zbb/Zcb/Zfa; MMU sv39; RVV 1.0 works (verified with userspace vector benchmarks).Kernel 6.1.15 has no
riscv_hwprobesyscall, so V8 must fall back toAT_HWCAPfor feature detection.The
nodebinary itself runs general workloads fine on this machine (npm install trees, 500k-call JS/wasm loops, NAPI addons incl. sharp/clipboard prebuilds — none crash).Subsystem
webassembly (V8 wasm runtime on riscv64; JS→wasm wrapper / lazy compilation entry)
What steps will reproduce the bug?
We could not reduce the trigger below a large Rust→wasm module (see notes under “Additional information” about synthetic attempts), so the shortest reliable repro uses one publicly-published npm app that ships the wasm fallback of SWC inside
next:# on the riscv64 machine (Node >= 22.19 from https://unofficial-builds.nodejs.org/download/release/ ) npm i -g @agegr/pi-web@0.8.11 pi-web --port 30141Output observed (no client traffic involved):
Control (only difference = one V8 flag):
The crashing wasm module is
node_modules/next/wasm/@next/swc-wasm-nodejs/wasm_bg.wasm(Next.js 16.3.1), which Next.js loads automatically on platforms without a native SWC binding.How often does it reproduce? Is there a required condition?
Deterministic, ~100% on this machine. Required conditions:
nodefrom unofficial-builds, default V8 flags (both the standard and the pointer-compression flavors reproduce — see controls below);All of the following were tested and do not change the outcome (still SIGILL at the same code-page offset):
--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,--no-riscv-b-extension(already default-off on this build), and Node v24.20.0 vs v26.0.0.What is the expected behavior? Why is that the expected behavior?
Expected: WebAssembly functions compile lazily on first call (or eagerly if lazy is disabled) and execute correctly, as on all other supported architectures.
Why we believe this is an engine bug and not userland / not “unsupported hardware”:
0x00000000(RISC-V zero word = illegal instruction) — i.e. execution entered a code page that was never populated.Builtins_JSToWasmWrapperAsm— the JS→wasm call trampoline itself. Nothing userland controls that jump target.0xa28, constant register values) — a deterministic codegen/stub-patching defect, not corruption.--no-wasm-lazy-compilation) makes it disappear; every non-wasm path on this binary is healthy.What do you see instead?
Kernel log (repeats identically for every run):
(
cause 2= RISC-V "illegal instruction"; the low page offset is always0xa28;t0is always0x6bae.)Under gdb:
Additional information
Relationship to #64538 (important). We are aware of the concurrent riscv64 wasm SIGILL thread (#64538, RVV/SIMD on SpacemiT chips) and believe our non-pc-flavor report is a distinct mechanism, not the same root cause:
llhttp.wasmshipped with Next.js) dies withCompileError: Wasm SIMD unsupportedinstead of executing, so none of theVU::SetSimd128*RVV lowering paths can be reached by the crashing app;swc-wasm-nodejs), and the fault is a jump into zero-filled, never-written code (badaddr 0x0; in RISC-V: "Illegal instruction" crash when vector instructions detected by V8 #64538 the kernel's badaddr carries a real RVV encoding such asvmv.s.x 0x4209ec57);--no-wasm-lazy-compilation(SIMD support untouched) eliminates our crash 100%.Caveat for honesty: the pointer-compression flavor also SIGILLs on this board, but we have not yet captured its kernel
badaddrsignature — if it turns out to be a real RVV instruction word, that flavor's crash belongs to #64538 and only the non-pc zero-page crash is new. We will capture it when our board is next reachable and post an update here.Build-flavor control. The
linux-riscv64-pointer-compressionvariant of the same v26.0.0 release also crashes, at the same page offset0xa28with the same register signature — so the bug is riscv64-wide, not tied to the non-pointer-compression flavor.Synthetic-reduction attempts (negative results). A 45-byte
addmodule called 500k times, and generated modules with thousands of trivial exported functions called in sequence, all run fine — so a minimal pure-builtins snippet does not (yet) reproduce. The trigger appears tied to how large Rust→wasm modules are consumed (we suspect re-entrant import→export calls and/or wide first-call coverage across many functions). We can share our stress-generator scripts, and — more usefully — test candidate patches/flags on this exact board via ssh (8-core X60, always reachable); we can also try to continue bisecting with--trace-wasm-*on request.Why userland is exonerated (per the guideline about userland modules): the only userland code involved is Next.js choosing its documented wasm-SWC fallback on an unmapped platform; the fault itself is in Node's V8 trampoline→code-space boundary as shown above. The identical app works on x86_64/aarch64. A prior riscv64 field report of the same fallback crashing is documented here: https://dev.to/gounthar/the-one-line-patch-that-unlocked-nextjs-on-risc-v-a-detective-story-71h
Related platform facts (context, independent of this crash):
qemu-system-riscv64 -cpu max(all extensions enabled) + our mainline 7.2.0-rc7 kernel (withriscv_hwprobe, so V8 detects Zba/Zbb/Zfa/…) does not crash — ran 900 s with traffic every 30 s,GET /consistently OK (wasm-SWC config compile took 4.1 min under TCG and completed). The board differs in two coupled ways: no hwprobe on kernel 6.1.15 → V8 falls back to AT_HWCAP → reduced feature set → no-B fallback code path. Suspect the bug lives in V8's riscv64 code path taken when B-class extensions are not detected. (We could not test-cpuwith B off using this guest image — its userspace is RVA23 and fails to boot without Zb*; would need an rv64gc userspace rootfs, e.g. Debian riscv64. Happy to run that test for you — the board is not required if a suitable rootfs is specified.)riscv_hwprobe, V8 additionally reports “Wasm SIMD unsupported” and cleanly refuses SIMD-containing modules (e.g.llhttp.wasm→ CompileError). This SIGILL is on non-SIMD modules.--no-wasm-lazy-compilationon riscv64.Artifacts:
node -vv26.0.0; tarball sha256071bb81d19de45393b91412ec4f01ae85bfde2b7e78bb62c5fabfa72347b8ff8; app@agegr/pi-web@0.8.11;next@16.3.1; V814.6.202.33-node.19; uv 1.52.1; openssl 3.5.5.— Acidmoon, KUBUDS Tech