Prebuilt Node.js 22.11.0 for Wii Linux PPC32 big-endian.
This project distributes a standalone node executable. It does not require
building Node.js on the Wii.
node-v22.11.0-wii-ppc32-jit-r3 runs with V8 JIT enabled, including the
Sparkplug baseline tier. Earlier builds had to be started with --jitless;
this one does not.
jit-r3 fixes V8 fast API calls on PPC32. In jit-r2, npm install crashed
with SIGSEGV after tens of thousands of fast API calls unless it was run with
--no-turbo-fast-api-calls. See Fast API Calls below.
The 750CL (Broadway) CPU is missing instructions that V8's PPC backend assumes, and V8 removed its PPC 32-bit code paths in 2024. This build restores those code paths and substitutes the missing instructions in the code generator, rather than working around the symptoms in JavaScript. As a result:
Math.sqrtand the rounding functions work. The previousjitless-r2build crashes with SIGILL onMath.sqrt.- The native
Bufferstring converters work, so thelib/buffer.jsfallback thatjitless-r2needed is gone. - Compute-bound JavaScript is several times faster (see Measurements).
The binary SHA-256 is recorded in SHA256SUMS.node-v22.11.0-wii-ppc32-jit-r3.
The source changes are in patches/ for review.
This repository distributes a tested binary and the patches used to produce it. It does not claim a fully reproducible upstream Node.js cross-build.
The chunks branch contains the gzip-compressed executable split into 1 MB
chunks. Clone metadata only, retrieve the installer, then let it fetch and
verify each chunk separately:
git clone --depth 1 --filter=blob:none --no-checkout --branch chunks https://github.com/tanpang-dev/node-wii-ppc32.git node22-chunks
cd node22-chunks
git show HEAD:install-jit-from-chunks-r3.sh > install-jit-from-chunks-r3.sh
chmod 755 install-jit-from-chunks-r3.sh
./install-jit-from-chunks-r3.sh /root/node22-jit-r3The installer retries each missing chunk, verifies every chunk, verifies the combined gzip file and the final executable, then runs a self-test. It does not replace the system Node.js installation.
jit-r3 was validated on Wii Linux (PowerPC 750CL, 729 MHz, 73 MB RAM):
- Node.js startup,
npm 10.9.0,npm installof a real package Bufferhex/base64/UTF-8 conversion, including multi-byte and 100 KB buffersMath.sqrt,floor/ceil/round/trunc, int/float conversion- BigInt arithmetic,
AtomicsonBigInt64Array,DataViewendianness crypto: SHA-256, HMAC,randomBytes, EC key generation, sign/verify- HTTPS against a live API, running as a long-lived service
- TurboFan optimization of hot loops,
switchjump tables, and regular expressions
The generated builtin code was checked with objdump: 654,056 instructions
contain none that the 750CL lacks.
It is an ELF32 PowerPC big-endian executable. Hardware behavior outside this environment remains a separate compatibility concern.
Steady-state, same machine, jit-r2 versus jitless-r2:
| Benchmark | jit-r2 | jitless-r2 |
|---|---|---|
| Arithmetic loop, 3,000,000 iterations | 221 ms | 2739 ms |
| Regular expression, 50,000 matches | 245 ms | 684 ms |
| Integer array sort, 50,000 elements | 336 ms | 490 ms |
| JSON round trip, 2,000 objects | 115 ms | 108 ms |
Math.sqrt |
works | SIGILL |
Resident memory is comparable (35 MB versus 33 MB). Startup is slower with JIT enabled, roughly 2-4 seconds against roughly 2 seconds.
jit-r2 adds the Sparkplug baseline compiler, which jit-r1 did not have.
V8 only dispatches its PPC baseline assembler for V8_TARGET_ARCH_PPC64, so
PPC32 fell through to #error Unsupported target architecture. That looks like
a missing port, but it is not: baseline-assembler-ppc-inl.h is written in
terms of pointer-width operations, and its eleven explicit 64-bit uses already
lower to lwz/stw on PPC32. Adding PPC32 to the two dispatch conditions is
the entire change.
Baseline code is about 1.8x faster than the interpreter here, measured with
--no-opt at both 1,000 and 10,000 calls per function. In practice the
difference does not show, because hot code reaches TurboFan either way:
| jit-r2 | --no-sparkplug |
|
|---|---|---|
| Steady state, request-shaped workload | 236 ms | 232 ms |
| First phase, including compilation | 1540 ms | 925 ms |
Sparkplug compiles on the main thread, and at 729 MHz that costs a few milliseconds per function. Short-lived processes lose; long-lived ones break even. It is enabled because it makes the tier structure correct, not because it is faster.
Fast API calls let TurboFan call a C++ function directly instead of going
through the generic API path. The callback receives v8::Local<T> by value:
int32_t FastInternalModuleStat(Local<Object> unused, Local<Object> recv,
const FastOneByteString&,
FastApiCallbackOptions&);Under the PPC32 SysV C++ ABI a pointer-sized class type is not passed in a
register. It is passed by invisible reference: the caller stores a copy and
passes its address. V8 knows this -- it is ABI_PASSES_HANDLES_IN_REGS == 0 in
codegen/ppc/constants-ppc.h, and the slow path in builtins-ppc.cc adds the
extra indirection.
The fast API call argument setup did not. GraphAssembler::AdaptLocalArgument
builds one level, which is correct on x86-64 where a Local<T> value is the
handle slot address, but one level short on PPC32. The callee then dereferenced
the tagged value as if it were a handle slot:
lwz r29,0(r4) ; recv.val_ -- should be a slot address
mr r4,r29 ; this
lwz r3,0(r4) ; *this -- dereferences the tagged value
clrrwi r3,r3,19 ; rounds it to a 512 KB boundary
bl MemoryChunk::GetHeap ; SIGSEGV once that address is unmapped
This is why it was non-deterministic: reading a tagged value as an address is harmless while it happens to land in a mapped region. Measured crash points ranged from 80,000 to 290,000 calls.
The fix separates the two uses. AdaptLocalValue keeps the single level, for
places that want the handle contents such as the FastApiCallbackOptions::data
field. AdaptLocalArgument adds the second level on PPC32 only. The nested
form is also GC-safe: only the inner slot holds a tagged value and is tracked,
while the outer slot holds its fixed address.
Verified on hardware: 300,000 fast API calls complete, and npm install
succeeds without --no-turbo-fast-api-calls.
| Patch | Contents |
|---|---|
0001-v8-ppc32-restore.patch |
Restores V8's PPC 32-bit code paths (shifts, multiply/divide, zero extension, memory operations, C linkage ABI, SIMD guards) and substitutes the instructions the 750CL lacks: fsqrt, fcfid/fctidz, fri[mpzn], popcnt. Also adds Int32Pair and Word32AtomicPair instruction selection. |
0002-v8-ppc32-sysv-abi.patch |
Restores the PPC32 Linux SysV stack linkage constants. Without this, JSEntry saves the link register into the wrong slot of its caller's frame and JavaScript execution crashes immediately. |
0003-v8-simulator-32bit-host.patch |
Lets the PPC simulator compile on a 32-bit host, which is required because V8 builds its host tools with -m32 when the target is PPC32. |
0004-v8-snapshot-header-endianness.patch |
The startup snapshot header is little-endian while its payload is target-endian. Makes the header accessors explicitly little-endian so they do not follow the target byte order. |
0006-v8-enable-sparkplug-ppc32.patch |
Adds PPC32 to the two Sparkplug dispatch conditions and enables the feature. Eleven lines, most of them comments. |
0007-v8-fast-api-call-ppc32-abi.patch |
Passes v8::Local<T> to fast API callbacks the way the PPC32 SysV C++ ABI expects. Without it, npm install crashes non-deterministically. |
0005-build-cross-and-openssl-ppc.patch |
Cross-build configuration, and the missing OpenSSL linux-ppc branch. Without it, the arch selection falls back to linux-x86_64 and bakes -DL_ENDIAN into a big-endian target. |
WebAssembly is disabled. Liftoff's PPC implementation assumes an i64 fits in one
register, but PPC32 sets kNeedI64RegPair, so every 64-bit operation needs a
register pair. The PPC file is 2979 lines against ia32's 4985, and that gap is
the work. jump-table-assembler has no PPC32 branch either.
SIMD is not the obstacle. SupportsWasmSimd128() already requires PPC_9_PLUS,
so the 750CL reports no SIMD support and modules that avoid it would still run.
node-v22.11.0-wii-ppc32-jit-r1 is the same port without Sparkplug. Its
installer is install-jit-from-chunks.sh.
node-v22.11.0-wii-ppc32-jitless-r2 remains available for comparison. It must
be run with --jitless, and it crashes on Math.sqrt. Its installer is
install-bufferfix-from-chunks.sh and its Buffer patch is
patches/0001-ppc32-buffer-fallback.patch.
The original release asset node22-ppc32-20260717 remains available for
compatibility testing. Do not replace the system Node.js installation.
sha256sum node22-ppc32-20260717
chmod 755 node22-ppc32-20260717
./node22-ppc32-20260717 --versionNode.js is distributed under the MIT License. See LICENSE.