The Machine
Somewhere in my lab in Louisiana, a Power Mac G5 Dual sits on a shelf. Dual 2.0 GHz PowerPC 970 processors, 8 GB of RAM, running Mac OS X Leopard 10.5. It was the fastest Mac you could buy in 2005. Apple called it "the world's fastest personal computer." Then they switched to Intel and never looked back.
Twenty years later, I'm trying to build Node.js 22 on it.
Why Would Anyone Do This?
Two reasons.
First: I run a blockchain called RustChain that uses Proof-of-Antiquity consensus. Vintage hardware earns higher mining rewards. The G5 gets a 2.0x antiquity multiplier on its RTC token earnings. But to run modern tooling on it -- specifically Claude Code, which requires Node.js -- I need a working Node runtime.
Second: Because it's there. The G5 is a beautiful piece of engineering. Dual 64-bit PowerPC cores, big-endian byte order, AltiVec SIMD. It represents a road not taken in computing history. If we want an agent internet that runs everywhere, "everywhere" should include hardware like this.
Third (okay, three reasons): I already run LLMs on a 768GB IBM POWER8 server. Once you've gone down the PowerPC rabbit hole, a G5 Node.js build seems almost reasonable.
The Setup
| Spec | Value |
|---|---|
| Machine | Power Mac G5 Dual |
| CPU | 2x PowerPC 970 @ 2.0 GHz |
| RAM | 8 GB |
| OS | Mac OS X Leopard 10.5 (Darwin 9.8.0) |
| Compiler | GCC 10.5.0 (cross-compiled, lives at /usr/local/gcc-10/bin/gcc) |
| Target | Node.js v22 |
| Byte Order | Big Endian |
The system compiler on Leopard is GCC 4.0. Node.js 22 requires C++20. So step zero was getting GCC 10 built and installed, which is its own adventure I'll spare you.
SSH requires legacy crypto flags because Leopard's OpenSSH is ancient:
ssh -o HostKeyAlgorithms=+ssh-rsa \
-o PubkeyAcceptedAlgorithms=+ssh-rsa \
selenamac@192.168.0.179
Patch 1: C++20 for js2c.cc
Node's js2c.cc tool and the Ada URL parser use C++20 string methods like starts_with() and ends_with(). The configure script doesn't propagate -std=gnu++20 to all compilation targets.
Fix: Patch all 85+ generated makefiles:
# Python script to inject -std=gnu++20 into every makefile
import glob
for mk in glob.glob('out/**/*.mk', recursive=True):
content = open(mk).read()
if 'CFLAGS_CC_Release' in content and '-std=gnu++20' not in content:
content = content.replace(
"CFLAGS_CC_Release =",
"CFLAGS_CC_Release = -std=gnu++20"
)
open(mk, 'w').write(content)
This is the first patch. There will be nine more.
Patch 2: GCC 10's C++20 Identity Crisis
GCC 10 with -std=gnu++20 reports __cplusplus = 201709L (C++17). But it actually provides C++20 library features like <bit> and std::endian. Node's src/util.h has fallback code guarded by __cplusplus < 202002L that conflicts with GCC's actual C++20 library.
// src/util.h -- before fix
#if __cplusplus < 202002L || !defined(__cpp_lib_endian)
// Fallback endian implementation that clashes with <bit>
#endif
Fix: Use feature-test macros instead of __cplusplus version:
#include <version>
#include <bit>
#ifndef __cpp_lib_endian
// Only use fallback if the feature truly isn't available
#endif
Patch 3: char8_t Cast
A reinterpret_cast<const char*>(out()) in util.h needs to be const char8_t* when C++20's char8_t is enabled. One-line fix. Moving on.
Patch 4: ncrypto.cc constexpr
// deps/ncrypto/ncrypto.cc line 1692
// GCC 10 is stricter about uninitialized variables in constexpr-adjacent contexts
size_t offset = 0, len = 0; // was: size_t offset, len;
Patch 5: libatomic for OpenSSL
OpenSSL uses 64-bit atomic operations that aren't available in the G5's default runtime libraries. The linker throws a wall of undefined reference to __atomic_* errors.
Fix: Add -L/usr/local/gcc-10/lib -latomic to the OpenSSL and node target makefiles:
# out/deps/openssl/openssl.target.mk
LIBS := ... -L/usr/local/gcc-10/lib -latomic
Four makefiles need this: openssl-cli, openssl-fipsmodule, openssl, and node.
Patch 6: OpenSSL Big Endian
OpenSSL needs to know it's running big-endian. Created gypi configuration files with B_ENDIAN defined. Standard stuff for any BE port.
Patch 7: V8 Thinks PPC = 32-bit
This is where things get interesting.
V8 has architecture defines: V8_TARGET_ARCH_PPC for 32-bit PowerPC and V8_TARGET_ARCH_PPC64 for 64-bit. Node's configure script detects the G5 as ppc (not ppc64), so it sets V8_TARGET_ARCH_PPC.
But V8's compiler/c-linkage.cc only defines CALLEE_SAVE_REGISTERS for PPC64:
#elif V8_TARGET_ARCH_PPC64
constexpr RegList kCalleeSaveRegisters = {
r14, r15, r16, r17, r18, r19, r20, r21, ...
};
No PPC case exists. Compilation fails.
Fix: Two changes. First, replace V8_TARGET_ARCH_PPC with V8_TARGET_ARCH_PPC64 in all makefiles:
find out -name '*.mk' -exec sed -i '' \
's/-DV8_TARGET_ARCH_PPC -DV8_TARGET_ARCH_PPC64/-DV8_TARGET_ARCH_PPC64/g' {} \;
Second, patch V8 source to accept either:
// deps/v8/src/compiler/c-linkage.cc line 88
#elif V8_TARGET_ARCH_PPC64 || V8_TARGET_ARCH_PPC
// deps/v8/src/compiler/pipeline.cc (4 locations)
defined(V8_TARGET_ARCH_PPC64) || defined(V8_TARGET_ARCH_PPC)
Patch 8: The 64-bit Revelation
After all those fixes, V8 hits a static assertion in globals.h:
static_assert((kTaggedSize == 8) == TAGGED_SIZE_8_BYTES);
kTaggedSize is 8 (because we told V8 it's PPC64), but sizeof(void*) is 4 because GCC is compiling in 32-bit mode by default. The G5 is a 64-bit CPU, but Darwin's default ABI is 32-bit.
Fix: Force 64-bit compilation everywhere:
CC='/usr/local/gcc-10/bin/gcc -m64' \
CXX='/usr/local/gcc-10/bin/g++ -m64' \
CFLAGS='-m64' CXXFLAGS='-m64' LDFLAGS='-m64' \
./configure --dest-cpu=ppc64 --openssl-no-asm \
--without-intl --without-inspector
This means a full rebuild. Every object file from the 32-bit build is now wrong.
The configure script also injects -arch i386 into makefiles (a bizarre default for a PPC machine), so those need to be patched out too:
find out -name '*.mk' -exec sed -i '' 's/-arch/-m64 #-arch/g' {} \;
find out -name '*.mk' -exec sed -i '' 's/^ i386/ #i386/g' {} \;
Patch 9: Two libstdc++ Libraries, One Problem
Here's the cruel twist: GCC 10.5.0 on this Mac was compiled as a 32-bit compiler. Its libstdc++.a is 32-bit only. We're now compiling 64-bit code that needs to link against a 64-bit C++ standard library.
But wait -- the system libstdc++ (/usr/lib/libstdc++.6.dylib) is a universal binary that includes ppc64:
$ file /usr/lib/libstdc++.6.dylib
/usr/lib/libstdc++.6.dylib: Mach-O universal binary with 4 architectures
# Includes: ppc, ppc64, i386, x86_64
Fix: Point the linker at the system library instead of GCC's:
# Changed from:
LIBS := -L/usr/local/gcc-10/lib -lstdc++ -lm
# To:
LIBS := -L/usr/lib -lstdc++.6 -lm
Patch 10: The Missing Symbol
The system libstdc++ is from 2007. It doesn't have __ZSt25__throw_bad_function_callv -- a C++11 symbol that std::function needs when you call an empty function object.
Fix: Write a compatibility shim:
// stdc++_compat.cpp
#include <cstdlib>
namespace std {
void __throw_bad_function_call() { abort(); }
}
Compile it 64-bit and add to the link inputs:
/usr/local/gcc-10/bin/g++ -m64 -std=gnu++20 -c stdc++_compat.cpp \
-o out/Release/obj.target/stdc++_compat.o
Current Status: Blocked
After all ten patches, the build compiles about 40 object files before the G5 went offline. It needs a physical reboot -- the machine is 20 years old and occasionally decides it's done for the day.
The next blocker will probably be something in V8's code generator. PPC64 big-endian is a rare enough target that there are likely byte-order assumptions baked into the JIT compiler. I expect at least three more patches before we see a working node binary.
What I've learned so far:
V8 has no concept of 64-bit PPC without PPC64 defines. The G5 lives in a gap: it's a 64-bit processor that Apple's toolchain treats as 32-bit by default.
Modern compilers on vintage systems create bizarre hybrid environments. GCC 10 provides C++20 features but lies about
__cplusplus. It compiles 64-bit code but ships 32-bit libraries. Feature-test macros are the only reliable truth.Every fix creates the next problem. Enabling PPC64 requires 64-bit mode. 64-bit mode requires different libraries. Different libraries are missing symbols. It's fixes all the way down.
The PowerPC architecture deserved better. These are elegant machines with real 64-bit SIMD, hardware-level big-endian support, and a clean ISA. The industry consolidated around x86 and ARM for market reasons, not engineering ones.
What's Next
Once the G5 comes back online:
- Add
stdc++_compat.oto the linker inputs fornode_js2ctarget - Verify
-m64propagated to all compilation and link flags - Brace for V8 JIT compiler byte-order issues
- If it builds:
node --versionon a Power Mac G5
The goal remains: run Claude Code on vintage PowerPC hardware, earning RustChain antiquity rewards while doing actual development work. An AI agent running on a machine old enough to vote.
I'll update this article when the G5 boots back up.
The Series: Building the Agent Internet
This is part of my ongoing series about building infrastructure for AI agents on unconventional hardware:
- I Built a Video Platform Where AI Agents Are the Creators
- The Agent Internet Has 54,000+ Users
- Proof of Antiquity: A Blockchain That Rewards Vintage Hardware
- Your AI Agent Can't Talk to Other Agents. Beacon Fixes That.
- I Run LLMs on a 768GB IBM POWER8 Server
Built by Elyan Labs in Louisiana.
Top comments (0)