Skip to content

Add PPC64 JIT support for Firefox 153 ESR - #2

Open
runlevel5 wants to merge 8 commits into
esr153from
esr153-ppc64
Open

Add PPC64 JIT support for Firefox 153 ESR#2
runlevel5 wants to merge 8 commits into
esr153from
esr153-ppc64

Conversation

@runlevel5

@runlevel5 runlevel5 commented Jun 29, 2026

Copy link
Copy Markdown
Owner

PPC64 JIT support for Firefox 153 ESR

612149264-613e0953-fde2-4617-9321-1d4ef8f3f57b

Brings JIT compilation to PPC64 — both little-endian (ppc64le) and big-endian (ppc64/ppc64be, ELFv1 and ELFv2) — targeting POWER8+ (ISA v2.07+) with POWER9 (ISA v3.0) / POWER10 (ISA 3.1) optimizations, plus a big-endian-only PowerPC 970 (ISA v2.01, pre-VSX / Apple G5) fallback tier. Enables SpiderMonkey's Baseline interpreter, Baseline and Ion JIT compilers, the native regexp JIT, plus full WebAssembly support — tiering, SIMD, huge memory, JSPI, tail calls, and atomics.

This PR revives the JIT port https://github.com/chmeeedalf/gecko-dev by Cameron Kaiser (classilla) and Justin Hibbits (chmeeedalf).

This PR would always track against the esr153 branch. If you are a packager, please find downstream patches in Releases, if the version you are after are not there, please simply leave a comment in this PR and I will prepare the patches for you.

Test results

In tests, we trust!

Real little-endian POWER8 hardware (SpiderMonkey with debug, GCC 14, sponsored by Oregon State University):

Suite POWER8 default
Full jit-tests (--jitflags=all) 13,715 / 0
Full jstests (JSPI on) PASSED ALL

Real POWER9 hardware (SpiderMonkey with debug, GCC 16, Raptor Computing System Blackbird workstation):

Suite POWER9 default MOZ_PPC64_FORCE_POWER8=1
Full jit-tests (--jitflags=all) 13,715 / 0 13,715 / 0
Full jstests (JSPI on) PASSED ALL PASSED ALL

Real POWER10 hardware (SpiderMonkey with debug, GCC 14, sponsored by Oregon State University):

Suite POWER10 default MOZ_PPC64_FORCE_POWER9=1 MOZ_PPC64_FORCE_POWER8=1
Full jit-tests (--jitflags=all) 13,715 / 0 13,715 / 0 13,715 / 0
Full jstests (JSPI on) PASSED ALL PASSED ALL PASSED ALL

ARM64-host simulator:

Suite FORCE_POWER8=1 FORCE_POWER9=1 FORCE_POWER10=1
Full jit-tests 13,715 / 0 13,715 / 0 13,715 / 0
Full jstests (JSPI on) PASSED ALL PASSED ALL PASSED ALL

shell/os.js ("killed process should not have exitStatus") was a parallel-load flake on slower hosts — the test sent SIGINT to a sleep 60 child and could lose the signal-delivery race under heavy concurrent jstests load, then waitpid blocked until the child exited normally and the assertion failed. Fixed 2026-05-08 by switching to SIGKILL (which is uncatchable, atomic, and works under any load). Patch in this PR.

FORCE_POWER10=1 implies FORCE_POWER9=1 (matches what real-P10 silicon advertises in hwcap2 — both ARCH_3_00 and ARCH_3_1). Six clean sweeps total — zero hard FAIL, zero TIMEOUT.

Real big-endian POWER9 hardware — ELFv1 (Debian ppc64, "bigboy") and ELFv2 (Arch POWER ppc64, "elfv2"), SpiderMonkey debug shell:

Host Tier Full jit-tests (--jitflags=all) Full jstests
ELFv1 default (P9) 0 correctness failures 0 unexpected / ~54,000 pass
ELFv1 MOZ_PPC64_FORCE_POWER8=1 0 correctness failures 0 unexpected / ~54,000 pass
ELFv2 default (P9) 0 correctness failures (2 env flakes ‡) 0 unexpected / ~54,000 pass
ELFv2 MOZ_PPC64_FORCE_POWER8=1 0 correctness failures (2 env flakes ‡) 0 unexpected / ~54,000 pass

bug1782468-ptrdiff-veclen.js and large-arraybuffers/bug2043700.js are memory-pressure false positives under --jitflags=all's heaviest parallel load on the 7 GB ELFv2 box; both pass cleanly when re-run in isolation.

A continuous baseline-vs-Ion differential WebAssembly fuzzer runs on both the ELFv1 and ELFv2 hosts with zero mismatches.

A real big-endian-only JIT bug was found and fixed by this hardware validation. The inline Map/Set BigInt-key hash (MacroAssembler::prepareHashBigInt) computed a different hash than the C++ reference on big-endian, so Map/Set lookups keyed on a BigInt could miss under Warp/Ion — a release-build correctness bug. Notably, this branch needed the opposite fix from the Firefox 154+ PR: between ESR 153 and current main, Mozilla refactored mozilla::HashBytes (from an 8-byte native-value walk that decomposes low-word-then-high-word, to a 4-byte in-memory-order walk). On ESR 153 the JIT's default register-based digit hashing already matches the value-based reference on both endians, so the correct fix here is to use the shared path unchanged — no PPC64 big-endian special case. Root-caused by instrumenting the debug hash assertion and hand-verifying the hash chain bit-for-bit; the tables above are post-fix on both ELFv1 and ELFv2.

Big-endian (ppc64be) support

The same backend runs big-endian, on both the ELFv1 and ELFv2 ABIs, across POWER8/9/10:

  • All execution tiers — Baseline interpreter, Baseline JIT, Ion, and the native regexp JIT.
  • Full WebAssembly, including SIMD and atomics. v128 values follow a canonical little-endian register convention; linear memory, globals, atomics, and the JS↔wasm boundary byte-reverse only where the spec observes byte order.
  • ABI handling gated on _CALL_ELF, not byte order — big-endian ELFv2 shares the little-endian call paths (no function descriptors), while ELFv1 uses {entry, TOC, env} descriptors for the JIT / regexp / wasm entry trampolines and symbolic-address calls.
  • Byte-order fixes for JIT entry stubs, bailout stack slots, wasm stack arguments/results, and mozilla::Result narrow storage. jit-test and wasm spec-harness cases that hard-coded little-endian byte order are made endian-neutral.

PowerPC 970 (G5) support — pre-VSX fallback tier

The backend also runs on the PowerPC 970 / 970FX / 970MP (Apple Power Mac G5, IBM JS20/JS21) — a big-endian-only, pre-VSX processor at Power ISA v2.01. There is no little-endian 970, so this tier is big-endian exclusively.

The 970 predates VSX (ISA 2.06) and the ISA 2.02–2.07 instructions the POWER8+ backend emits freely (mtvsrd/mfvsrd, xsmindp/xsmaxdp, xxlxor, isel, popcntd/popcntw, fcfids/fctiduz, lbarx/stbcx., ldbrx/stdbrx, frim/frip/friz, lfiwax, fcpsgn, …). Rather than disabling the JIT, this tier provides a scalar fallback for every one of them, selected at runtime:

  • Detected via getauxval(AT_HWCAP) PPC_FEATURE_HAS_VSX — absent → pre-VSX tier.
  • Testable on any VSX-capable POWER8/9/10 with MOZ_PPC64_FORCE_970=1; the CPU executes both the scalar and the VSX encodings, so the scalar codegen is exercised and verified on modern silicon.
  • A debug MOZ_ASSERT(HasVSX()) guards every VSX-form encoder and each gated emitter, so an unconverted call site fails fast at codegen time instead of executing an illegal instruction on a real G5.

Fallback highlights: GPR↔FPR moves round-trip through the ABI red zone; round-to-integral swaps the FPSCR RN field around fctid/fcfid (new mtfsfi emitter), quieting signaling NaNs like the hardware forms; popcount is 64-bit SWAR; select is branch-based; int64f32 uses round-to-odd (truncate-mode fcfid + FPSCR FI mantissa-lsb) so the final frsp single-rounds exactly; unsigned 64-bit truncation splits at 2⁶³ in the GPR domain and rejects the negative inputs a signed fctidz would accept; sub-word atomics run the classic masked-word lwarx/stwcx. dance, so JS Atomics and wasm shared memory stay fully JIT-inlined.

Feature parity vs the POWER8+ tier

Capability POWER8+ PowerPC 970
Baseline interpreter / Baseline JIT / Ion (Warp)
Native regexp JIT
JS Atomics (incl. sub-word 8/16-bit) ✅ masked-word
WebAssembly baseline + Ion, tiering
Wasm shared memory / threads / atomics
Wasm huge memory, JSPI, tail calls
Wasm SIMD (v128) ❌ feature-absent by design

Wasm SIMD is reported unavailable rather than emulated: the 970's VMX has no 64-bit integer lanes and no f64x2, so a faithful v128 tier isn't practical — the same posture SpiderMonkey takes on SSE4.1-less x86. All non-SIMD wasm runs.

Test results

Validated with MOZ_PPC64_FORCE_970=1 on real big-endian POWER9 hardware (Arch POWER ppc64, ELFv2, GCC 16.1.1), SpiderMonkey debug shell built with -mcpu=970:

Config Full jit-tests (--jitflags=all)
MOZ_PPC64_FORCE_970=1 (ELFv2) PASSED ALL — 0 failures

The -mcpu=970 shell additionally compiles with zero code changes and runs correctly in interpreter-only mode (--no-jit-backend); the static binary is verifiably VSX-free by objdump, so even the non-JIT fallback is 970-safe.

A one-line correctness fix rides along — LIFO_ALLOC_ALIGN 8 → 16. C++17 requires allocation functions to return storage aligned to __STDCPP_DEFAULT_NEW_ALIGNMENT__ (16 on ppc64); GCC 16 exploits that when vectorizing member zero-init of TempObject-derived classes, and at -mcpu=970 it emits VMX stvx, which masks the low 4 address bits — so 8-byte-aligned LifoAlloc storage caused wrong-address stores and uninitialized members (a silent release-build corruption on real hardware, surfaced by the 970 build).

Scope

  • 19 new files in js/src/jit/ppc64/ — ~17,500 lines.
  • Modifications to ~60 shared files (dispatch headers, build system, wasm, CacheIR, MacroAssembler, WasmStacks).
  • Full Firefox browser builds successfully (GCC + lld).

What's new vs the old chmeeedalf/gecko-dev Firefox-86 port

Inherited: Architecture-ppc64.h register/ABI constants, the Assembler-ppc64.h/cpp instruction-encoding skeleton, some basic MacroAssembler patterns.

New in this port:

  • Build system integration (js/moz.configure, dispatch headers, js/src/jit/moz.build).
  • Full WebAssembly — baseline + Ion, tiering, signal handlers, builtins, tail calls, atomics, huge memory, SIMD, JSPI. The old fork had zero Wasm.
  • Wasm SIMD — complete VMX/VSX (~280 methods), all 10 tiers, full spec tests, both P8 and P9.
  • Warp/Ion backend — complete CodeGenerator, Lowering visitors, LIR-ppc64 (old fork predated Warp).
  • Atomic operations — ~980 lines of CompareExchange / AtomicExchange / AtomicFetchOp.
  • POWER8/9/10 runtime detection via getauxval(AT_HWCAP2), with fallback sequences for every POWER9-only instruction and POWER10 fast paths gated on HasPOWER10().
  • Code-size optimizations — inline constant pool for SIMD + FP constants, POWER9 addpcis FP-constant path, POWER10 prefix-instruction fast paths (see below), GCC-style lis+ori+rldimi 64-bit load (~37 % smaller than a bcl-based stanza).
  • Long-branch relocation, OSI epilogue padding, QEMU-style FlushICache.

Key architecture-specific features

Runtime POWER8/POWER9/POWER10 selection

Detection via getauxval(AT_HWCAP2)PPC_FEATURE2_ARCH_3_00 for POWER9, PPC_FEATURE2_ARCH_3_1 for POWER10.

  • POWER9 native paths (with P8 fallbacks): cnttzd/w, xxbrd, mtvsrws, mcrxrx, addpcis, modsw/u/d/du, vinsert{b,h} (lane insert from VR + immediate byte position), xxinsertw, mfvsrld, mtvsrdd, xs{min,max}jdp, xxspltib, vcmpne{b,h,w}, vneg{w,d}, isel, xscvdphp/xscvhpdp (FP16 scalar conversion), xsxexpdp (P9 exponent extract for nearbyIntDouble). replaceLaneInt{8x16,16x8} on POWER9 emits the 2-insn mtvsrd + vinsert{b,h} sequence (down from 5 insns). clampDoubleToUint8 is fully branchless on P9 via xsmaxjdp + isel — the canonical JS clamp shape collapses to a 4-insn straight-line sequence.
  • POWER10 fast paths (gated on HasPOWER10()):
    • Prefix-instruction memory traffic — generic, not constants-only.
      • Loads (Address operands when displacement exceeds 16 bits): loadPtrpld, loadDoubleplfd, loadFloat32plfs, loadUnalignedSimd128plxv. Replaces the prior addis + ori + ldx-style two-insn stanzas with one prefixed insn.
      • Stores (same shape): storePtrpstd, storeDoublepstfd, storeFloat32pstfs, storeUnalignedSimd128pstxv.
      • Constant materialisation: loadConstantSimd128 3 + xxpermdi → 1 plxv; loadConstantDouble/Float32 2 → 1 plfd/plfs; movePtr(ImmWord) 33–34-bit signed 5 → 1 paddi.
    • Single-instruction lane / byte-reverse / lane-insert collapses: brd collapses byteSwap64 3 → 1 + drops the GPR↔FPR round-trip; brh/brw collapse byteSwap16{Sign,Zero}Extend/byteSwap32 from 3-4 → 2; vinsw/vinsd collapse replaceLaneInt{32x4,64x2} from 2 → 1 (no scratch VSR); vinsbrx/vinshrx cover the same byte/halfword replaceLane on POWER10; vextract{b,h,w,d}m collapses bitmask to 1 insn; vmulld collapses mulInt64x2 from 9-11 insns to 1.
    • CR-bit-to-GPR collapses via setbc/setbcr: 1-insn ma_cmp_set, 1-insn allTrueInt* CR6.EQ extract, and 5 → 3 insn anyTrueSimd128. Replaces the older mfocrf + rlwinm + andi. extraction pattern across the boolean-reduction family.

Test with MOZ_PPC64_FORCE_POWER8=1 (downgrade), MOZ_PPC64_FORCE_POWER9=1 (force P9 on, useful under sim), or MOZ_PPC64_FORCE_POWER10=1 (which implies FORCE_POWER9=1 — matches real-P10 silicon detection). All three configurations PASS the full jit-test + jstests sweeps end-to-end on the simulator.

POWER10 prefix-instruction support

Power ISA v3.1 prefixed instructions are 8 bytes (4-byte prefix word with primary opcode 1 + 4-byte suffix word) and must not straddle a 64-byte aligned block at runtime. The assembler emits them via as_paddi / as_pld / as_plxv / as_plfd / as_plfs, with an automatic leading-nop guard when the prefix would otherwise cross the boundary.

The original guard checked (currentOffset() & 63) == 60 — a buffer-relative test that's only correct when the JitCode allocator base is 64-byte aligned. The allocator only guarantees 16-byte alignment, so across the four base-mod-64 classes {0, 16, 32, 48} the unsafe straddle is exactly (currentOffset() & 15) == 12. The same fix shape applies to the inline-emit path (ensurePrefixedAlignment, commit 8ae818d0fcab) and the constant-pool patcher (PatchConstantPoolLoad, commit aea233e21209). The simulator's POWER10 model doesn't trap misaligned prefixes, which is why these only surfaced when real-P10 silicon came online.

The simulator decodes prefixed instructions, validated against as -mpower10 + objdump on Fedora 44 / binutils 2.46. Encoding details (MLS Type=2 vs 8LS Type=0 vs 8RR Type=1, R-bit at LE position 20, plxv 5-bit suffix opcode + TX bit at suffix bit 26) live in Simulator-ppc64.cpp source comments and PLAN.md's engineering-lessons section.

WASM SIMD

Full VMX/VSX implementation using PPC64 AltiVec. All 10 tiers — load/store/splat/lane, bitwise, arithmetic, cmp/shift, float, conversions, shuffle/swizzle, extmul/pairwise/dot, relaxed SIMD (FMA/FNMA). NaN semantics correct (min/max propagation, truncSat NaN→zero, promote NaN-quieting). P8 uses lxvd2x/stxvd2x with byte-swap; P9 uses native lxvx/stxvx; P10 collapses the constant-pool variant to a single plxv.

WASM JSPI

WebAssembly.Suspending / WebAssembly.promising via javascript.options.wasm_js_promise_integration. Add ppc64 to the JSPI whitelists in js/moz.configure (both the default_wasm_jspi simulator allowlist and the wasm_jspi simulator-guard die-list — extending the original target.cpu enable to cover sim builds too), and round ContRedZoneSize up to a page-size multiple so the bottom guard page lands on a page boundary under PPC64LE's 64 KiB pages. No arch-specific stack-switch code required.

WASM huge memory

4 GB + 32 MB virtual reservation per wasm memory, bounds-check elimination via SIGSEGV → wasm trap. Signal-handler context indices: gp_regs[32]=NIA, gp_regs[1]=SP, gp_regs[31]=FP, gp_regs[36]=LR.

Simulator (ARM64-host)

A full PPC64 simulator is included so CI can run the JIT on non-PPC64 hosts. Both jit-test and jstests sweeps pass under FORCE_POWER8=1, FORCE_POWER9=1, and FORCE_POWER10=1 configurations (zero hard FAIL, zero TIMEOUT). The sim received many ISA-correctness fixes during development (VMX/VSX decoding, LE lane ordering, FP NaN-payload preservation across f32↔f64, ELFv1 FP-arg shadow slots, single-step profiling for tail calls and non-leaf epilogues), and now decodes:

  • Power ISA v3.0 (POWER9): addpcis, mods*/modu*, mtvsrws, xxspltib, xxinsertw, xscvdpsp(n) HW-conformance fix, xs{max,min}jdp, xscvdphp/xscvhpdp (FP16), xsxexpdp. Closes the historical "~40 wasm/simd tests fail under FORCE_POWER9 on the sim" coverage gap.
  • Power ISA v3.1 (POWER10): prefixed instructions (paddi/pld/plxv/plfd/plfs), plus the new instruction-count-reduction ops (brd, brh, brw, vinsw, vinsd, vinsbrx, vinshrx, vextract*m, setbc/setbcr, vmulld).
  • A simulator-side setXEROV() fix that mirrors OV→OV32 — the JIT's branchMulPtr Overflow path uses POWER9's mcrxrx+bc which reads OV32, and the original sim only set OV. Surfaced as silent BigInt fast-path-mul wrap-around in wasm/regress/bug1836708.js.

Build

Complete from-scratch walkthrough — little-endian (Debian / Ubuntu / Fedora, ppc64le)

Mozilla's ./mach bootstrap cannot download prebuilt toolchains for ppc64, so
everything comes from your distro plus rustup. These steps take a fresh
ppc64le machine to a running browser. Big-endian Debian has its own section
right after this one.

Verified end-to-end on a clean Debian 13 (trixie) ppc64le container on real
POWER9 hardware: these exact commands produce a working firefox binary.

1. Install build dependencies.

Debian / Ubuntu — let apt resolve the whole Firefox dependency tree, then add
the handful of pieces it misses:

# Enable source repositories first:
#   Debian 12+: add "deb-src" lines matching your "deb" lines in
#   /etc/apt/sources.list, or Types: deb deb-src in /etc/apt/sources.list.d/debian.sources
sudo apt update
sudo apt build-dep firefox-esr        # bulk of the dependencies
sudo apt install git curl ccache python3-venv python3-pip \
                 pkg-config nasm nodejs npm libclang-dev clang lld

Fedora:

sudo dnf builddep firefox
sudo dnf install git curl ccache nasm nodejs clang clang-devel lld

2. Install Rust and cbindgen via rustup. Distro packages are almost
always too old — this tree requires Rust >= 1.90 and cbindgen >= 0.29.4
(node only needs >= 12, any current distro package is fine):

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source ~/.cargo/env
rustup default stable
cargo install cbindgen

Make sure ~/.cargo/bin stays on PATH for every build shell.

3. Clone this repository and branch.

git clone --branch ppc64 https://github.com/runlevel5/firefox-ppc64.git
cd firefox-ppc64

(For ESR 153 use --branch esr153-ppc64, see PR #2. Packagers who prefer
patch files over a git checkout: apply the numbered patches from
Releases on top of the
matching Mozilla release tarball instead.)

4. Write a mozconfig. Create a file named mozconfig in the tree root
with one of the configurations below (JS shell, full Firefox GCC, or full
Firefox Clang+LTO). Two lines matter on ppc64 no matter which you pick:

ac_add_options --disable-bootstrap                  # never try to fetch Mozilla toolchains
ac_add_options --without-wasm-sandboxed-libraries   # no wasi sysroot for ppc64

5. Build and run.

./mach build
./mach run          # run from the object directory
./mach package      # or produce a distributable tarball in <objdir>/dist/

Hardware/RAM notes: linking libxul.so with lld peaks around 8 GB;
16 GB RAM is a comfortable minimum for the full browser (the JS shell builds
on much less). On bigger machines cap parallelism (mk_add_options MOZ_PARALLEL_BUILD=20) rather than letting mach use every core — wide lld
links can OOM the box.

Troubleshooting:

  • mach fails creating its virtualenv → sudo apt install python3-venv.
  • configure errors about Rust or cbindgen versions → check rustc --version
    (>= 1.90) and cbindgen --version (>= 0.29.4); the rustup/cargo copies in
    ~/.cargo/bin must precede any distro copies on PATH.
  • configure cannot find libclang → install libclang-dev (Debian) /
    clang-devel (Fedora) and point --with-libclang-path at the directory
    containing libclang.so (llvm-config --libdir).
  • OOM during the final link → lower MOZ_PARALLEL_BUILD, ensure the linker
    is lld (--enable-linker=lld), add swap/zram.
  • Big-endian: see the next section.

Complete walkthrough — big-endian Debian (ppc64, ELFv1)

Big-endian Debian is an unofficial port living in debian-ports (sid only),
and the ELFv1 ABI changes the toolchain rules: lld cannot link ELFv1 at
all, and Debian's clang forces lld
— so the build must use GCC with GNU
ld (bfd)
for both target and host. Steps 2, 3 and 5 from the
little-endian walkthrough apply unchanged (rustup supports big-endian
powerpc64-unknown-linux-gnu); the differences:

1. Dependencies. On a debian-ports sid system:

sudo apt update
sudo apt build-dep firefox-esr   # if firefox-esr is unavailable in ports, use:
sudo apt install build-essential git curl ccache python3-venv python3-pip \
                 pkg-config nasm m4 unzip zip \
                 libgtk-3-dev libdbus-glib-1-dev libpulse-dev libasound2-dev \
                 libxt-dev libx11-xcb-dev libpixman-1-dev \
                 nodejs npm libclang-dev

4. mozconfig for big-endian ELFv1:

export CC=gcc
export CXX=g++
ac_add_options --enable-application=browser
ac_add_options --enable-optimize
ac_add_options --disable-debug
ac_add_options --enable-release
ac_add_options --disable-tests
ac_add_options --enable-jit
ac_add_options --disable-bootstrap
ac_add_options --with-libclang-path=`llvm-config --libdir`
ac_add_options --without-wasm-sandboxed-libraries
ac_add_options --enable-linker=bfd
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-ff-ppc64be

Big-endian caveats:

  • bfd needs substantially more RAM than lld to link libxul.so
    (community reports ~20 GB resident); on smaller machines build the JS shell
    (--enable-application=js), which links fine everywhere.
  • The DevTools build step needs a working Node.js, and Debian's big-endian
    nodejs currently segfaults on it — see the dedicated Node.js
    bootstrap
    section below for the two workarounds (DevTools-server-only
    build, or forwarding node to a little-endian helper host).
  • cbindgen must still come from cargo install cbindgen — the ports archive
    version is too old.
  • Big-endian ELFv2 distros (e.g. Arch POWER) use the same recipe minus the
    ELFv1 constraint origin — but clang's forced lld still applies on Debian
    derivatives, so GCC + bfd remains the safe choice on any big-endian
    system.

mozconfig reference

SpiderMonkey (standalone JS shell):

ac_add_options --enable-application=js
ac_add_options --enable-optimize
ac_add_options --disable-debug
ac_add_options --disable-tests
ac_add_options --enable-jit
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-sm-release

./mach build -j20

(On the PPC64 box, cap parallelism at -j20; the auto-detected -j32 saturates the box during lld linking.)

Full Firefox:

export CC=gcc
export CXX=g++
ac_add_options --enable-application=browser
ac_add_options --enable-optimize
ac_add_options --disable-debug
ac_add_options --enable-release
ac_add_options --disable-tests
ac_add_options --enable-jit
ac_add_options --disable-bootstrap
ac_add_options --with-ccache
ac_add_options --with-libclang-path=/usr/lib64
ac_add_options --without-wasm-sandboxed-libraries
ac_add_options --enable-linker=lld
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-ff-ppc64

./mach build -j20

Full Firefox with Clang + LTO (ppc64le):

A Clang + ThinLTO build works on ppc64le and is measurably faster on
DOM/layout-heavy workloads than the plain GCC -O2 build. Two things make it
work: Clang with the LLVM linker (lld), and system NSS — the bundled
NSS ppc-gcm.s uses GAS-style semicolon statement separators that Clang's
integrated assembler rejects, so building against the distro's NSS
(--with-system-nss) sidesteps that entirely. This is exactly what the Fedora
package build does.

export CC=clang
export CXX=clang++
ac_add_options --enable-application=browser
ac_add_options --enable-optimize
ac_add_options --disable-debug
ac_add_options --enable-release
ac_add_options --disable-tests
ac_add_options --enable-jit
ac_add_options --disable-bootstrap
ac_add_options --enable-linker=lld
ac_add_options --enable-lto              # ThinLTO
ac_add_options --with-system-nss         # avoids Clang IAS choking on NSS ppc-gcm.s
ac_add_options --with-system-nspr
ac_add_options --with-libclang-path=`llvm-config --libdir`
ac_add_options --without-wasm-sandboxed-libraries
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-ff-clang

./mach build -j20

Do not add PGO on ppc64le. Profile-guided optimization is currently
broken on this target — GCC ICEs during the instrumented build and Clang's
instrumented build crashes at runtime (see
DKnoto/Firefox-Benchmarks).
LTO alone is the supported configuration.

--with-libclang-path must point at the directory that actually holds
libclang.so. On distros that version the LLVM tree (e.g. Fedora's
/usr/lib64/llvm22/lib64) let llvm-config --libdir resolve it rather than
hard-coding /usr/lib64.

For RPM packagers: the Fedora spec builds this with
rpmbuild --with build_with_clang --with build_with_lto.

Big-endian (ppc64be): Node.js bootstrap

The full browser build runs Node.js at build time to transform the DevTools
debugger modules (devtools/client/* via mozbuild/action/node.py). Nothing
Node produces ships in the binary — it is a build-time tool only — but the
build cannot finish without it, and Node on big-endian ppc64 is currently a
problem:

  • Mozilla ships no prebuilt Node toolchain for ppc64, so the build needs a
    system Node.
  • Debian's ppc64 (BE) nodejs is an unofficial port frozen at 20.19.5 and
    its libnode segfaults during a V8 conservative-stack-scan GC
    (heap::base::Stack::IteratePointersImpl) when running the DevTools
    transform — an endianness-specific V8/Node bug, unrelated to this port.

Two ways to get past it:

1. Skip the DevTools client (simplest). Set the DevTools build to
server instead of all. This drops the browser-side debugger UI but keeps
the DevTools server (remote debugging still works) and needs no Node:

export MOZ_DEVTOOLS=server   # requires a one-line source edit; see note below

MOZ_DEVTOOLS cannot be set from mozconfig (it is an imply_option), so
change imply_option("MOZ_DEVTOOLS", "all") to "server" in
browser/moz.configure, or wait for a proper --disable-devtools-style knob.

2. Offload Node to a working host (full DevTools). If you have a
little-endian ppc64le box (or any box with a working Node), run only the Node
steps there while the compile stays on the BE machine. Node just reads/writes
files in the source and object trees, so share the tree and forward node:

  • On the LE helper, mount the BE build tree (e.g. via sshfs) and confirm its
    Node runs devtools/client/shared/build/build.js without crashing.
  • On the BE box, put a node wrapper first on PATH that sshes the command
    to the helper, rewriting the tree-path prefix (BE mountpoint ↔ helper
    mountpoint) in both the arguments and in the dep: lines Node prints back
    (the build system consumes those for incremental rebuilds):
#!/bin/bash
# ~/nodewrap/node  — forward Node to an LE helper that shares this tree.
LOCAL=/path/to/be/firefox          # tree path on the BE box
REMOTE=/path/to/helper/mount       # same tree, as seen on the helper
args=(); for a in "$@"; do args+=("${a//$LOCAL/$REMOTE}"); done
out=$(ssh helper "cd $(printf %q "${PWD//$LOCAL/$REMOTE}") && \
      node $(printf '%q ' "${args[@]}")"); rc=$?
printf '%s\n' "${out//$REMOTE/$LOCAL}"; exit $rc
PATH=~/nodewrap:$PATH ./mach build -j20

Longer term the clean fix is a Node that does not crash on ppc64 BE (e.g.
building Node from source — Arch POWER's nodejs-lts-iron recipe builds
20.20.0 for powerpc64 with --with-intl=system-icu).

Known limitations

  • Clang full Firefox: works with --with-system-nss + lld (see the Clang + LTO build recipe above). Only the bundled NSS is a problem — its ppc-gcm.s uses GAS-style semicolons that Clang's integrated assembler rejects — so building against the distro NSS avoids it. PGO is still unavailable on ppc64le (GCC ICE / Clang runtime crash); LTO-only.
  • ld.bfd: does link libxul.so successfully on ppc64le, but needs substantially more RAM during the link than lld (community reports ~20+ GiB resident, putting it out of reach for hosts with under ~24 GiB total). The P10 box uses BFD as a workaround for an LLD R_PPC64_TOC16_HA bug; lld stays the practical default everywhere else for speed.

@runlevel5 runlevel5 changed the title Add PPC64 JIT Support for Firefox 153 ESR Add PPC64 JIT support for Firefox 153 ESR Jun 29, 2026
@runlevel5
runlevel5 force-pushed the esr153-ppc64 branch 14 times, most recently from e9723ce to fdbcbbf Compare July 8, 2026 03:19
@runlevel5
runlevel5 force-pushed the esr153-ppc64 branch 11 times, most recently from c6f34cc to 1bdb171 Compare July 23, 2026 03:08
@runlevel5
runlevel5 force-pushed the esr153-ppc64 branch 3 times, most recently from a9e7e5c to 6c6f6a9 Compare July 26, 2026 03:55
@runlevel5
runlevel5 force-pushed the esr153-ppc64 branch 3 times, most recently from ec44ada to 4cd6549 Compare August 12, 2026 03:05
runlevel5 and others added 8 commits August 19, 2026 10:50
Adapted from work done by Raptor Engineering for chromium's vendored
SKIA. Gated to little-endian: the SSE-derived swizzle and permute
helpers assume the ppc64le vector-register byte order, so big-endian
falls back to the portable paths.

Co-authored-by: Timothy Pearson <tpearson@raptorengineering.com>
Gated to little-endian: the VSX kernels assume the ppc64le vector-register
byte order, so big-endian falls back to the portable C paths.

Assisted-by: Lance Albertson <lance@osuosl.org>
Assisted-by: Thushan Fernando <thushan@thushanfernando.com>
Assisted-by: Lance Albertson <lance@osuosl.org>
Assisted-by: Thushan Fernando <thushan@thushanfernando.com>
Assisted-by: James Zern <jzern@google.com>
Based on the work done by Cameron Kaiser and Justin Hibbits
https://github.com/chmeeedalf/gecko-dev

Co-authored-by: Cameron Kaiser <classilla@floodgap.com>
Co-authored-by: Justin Hibbits <chmeeedalf@gmail.com>
Assisted-by: Lance Albertson <lance@osuosl.org>
Assisted-by: Thushan Fernando <thushan@thushanfernando.com>
Assisted-by: Timothy Pearson <tpearson@solidsilicon.com>
Assisted-by: Dan Horák <dan@danny.cz>
Assisted-by: Hiếu Lê <modology@gmail.com>
Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Enable the SpiderMonkey JIT and WebAssembly on big-endian PPC64 (ELFv1
and ELFv2).

Co-authored-by: Cameron Kaiser <classilla@floodgap.com>
Co-authored-by: Justin Hibbits <chmeeedalf@gmail.com>
Assisted-by: Lance Albertson <lance@osuosl.org>
Assisted-by: Thushan Fernando <thushan@thushanfernando.com>
Assisted-by: Timothy Pearson <tpearson@solidsilicon.com>
Assisted-by: Dan Horák <dan@danny.cz>
Assisted-by: Hiếu Lê <modology@gmail.com>
Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Assisted-by: Alexander Baldeck <alex.bldck@gmail.com>
The build system defines HAVE_PLATFORM_GHASH for both PPC64 endians,
but the POWER8 vpmsumd GHASH implementation was compiled out on
big-endian, leaving platform_ghash_support, gcm_HashInit_hw,
gcm_HashWrite_hw and gcm_HashZeroX_hw unresolved and freebl failing
to link.

Make ghash-ppc.c endian-correct instead of stubbing it out. GNU C
vector element indexing follows memory order, so the doubleword
element reads, the pair constructors and the vec_splat index of the
most-significant byte must mirror on big-endian, while vec_xl_be,
vec_xst_be and the whole-register operations (vpmsumd, vec_sll,
vec_rl, xor) already produce identical register values on both
endians. The new USE_PPC_CRYPTO_GHASH define drops the little-endian
restriction for ghash-ppc.c only; the other USE_PPC_CRYPTO consumers
remain little-endian.

Also give gcm.c the same -mcrypto/-maltivec cflags as ghash-ppc.c in
gcm.gyp. gcmHashContext's vec_u64 x/h fields are gated on
__ALTIVEC__/__VSX__, so without this gcm.c and ghash-ppc.c disagree on
sizeof(gcmHashContext) and every field after x/h -- including the
ghash_mul function pointer -- lands at a different offset in each
translation unit, corrupting the hardware GHASH dispatch. This is not
endian-specific: it affects ppc64le the same way whenever
disable_crypto_vsx doesn't already imply matching flags elsewhere.
Found by crashing on a real TLS 1.3 handshake (AES-GCM record decrypt)
in a full browser build, not just the isolated GHASH unit tests.

Reference: https://bugzilla.mozilla.org/show_bug.cgi?id=2027768
Big-endian used to flip the OS_RGBA surface format synonyms to the
A8R8G8B8 memory layout, which Skia has no color type for, so surface
creation hit MOZ_DIAGNOSTIC_CRASH("A8R8G8B8 unsupported by Skia") as
soon as anything was drawn. Keep the synonyms pinned to the B8G8R8A8
memory layout on both endians so the whole pixel pipeline operates on
one layout, and convert for the display server at the compositor
boundary instead.

Skia's SkPMColor channel shifts describe a little-endian uint32_t
view of that layout, so on big-endian a packed color value and the
in-memory destination pixels disagree on byte order. Introduce
BE_CONVERT (a no-op on little-endian) and apply it at the scalar
blitter load/store boundaries: solid fills, the blit_row_color32
kernel, A8 glyph masks, LCD16 subpixel text, BW masks, the black
blitter, and the direct-blit fill value. Alpha extraction always uses
the unconverted value. The SurfaceFormatBit shifts mirror on
big-endian so channel accessors keep addressing the same bytes, and
kARGBAlphaOffset moves to byte 0 for the RGBX verification.

Software WebRender resolves frames in the B8G8R8A8 memory layout, but
X11 consumes native-endian 32-bit pixel values, so swizzle the dirty
region to the A8R8G8B8 layout when handing the buffer to the widget.
Skia-drawn image surfaces get the inverse reorder in imgFrame before
they enter the surface cache.

Known gap: SkRasterPipeline's load/store stages and the headless
screenshot readback still assume little-endian and are unchanged.
Add a pre-VSX tier to the PPC64 JIT so the engine runs with the JIT
enabled on PowerPC 970 (ISA 2.01) hardware, detected at runtime via
AT_HWCAP PPC_FEATURE_HAS_VSX and testable on newer silicon with
MOZ_PPC64_FORCE_970=1.

Scalar fallbacks replace every VSX and post-2.01 instruction the port
emits: GPR<->FPR moves round-trip through the ABI red zone (mtvsrd,
mfvsrd, mtvsrwz, mtvsrwa, lfiwax); round-to-integral swaps the FPSCR RN
field around fctid/fcfid (friz, frip, frim) with a new mtfsfi emitter,
quieting signaling NaNs on the pass-through path as the hardware forms
do; fcpsgn, isel, popcntd/popcntw and the unsigned int64 conversions
(fcfidu, fctiduz) get branch- or SWAR-based equivalents; int64-to-
float32 converts with round-to-odd (truncate-mode fcfid, then FPSCR FI
sets the mantissa lsb) so the final frsp rounds exactly like fcfids;
the unsigned truncations split at 2**63 in the GPR domain and reject
negative inputs the signed fctidz would accept; 64-bit byte-reversed
wasm memory accesses use ldx/stdx plus a red-zone byte swap that needs
no scratch register beyond r0, with store trap sites deferred to the
faulting instruction; min/max and zero-constant materialization avoid
xsmindp/xsmaxdp/xxlxor.

Sub-word atomics (lbarx/lharx/stbcx./sthcx. are ISA 2.06) run the
classic masked-word lwarx/stwcx. dance on the containing aligned word;
the lowering allocates the value/offset/mask temps the dance needs
(the native path never read them, and Ion collapses unused bogus temps
onto one register). Wasm SIMD is unavailable on the tier: feature
detection reports it absent, register save/restore skips the
nonexistent v128 state, and SupportsFastUnalignedFPAccesses() gates
the 16-byte tier of the inline wasm memory.copy/init expansion.
Debug builds assert HasVSX() in every VSX-form encoder and each gated
emitter so an unconverted call site fails fast instead of executing an
illegal instruction on real hardware.

Raise LIFO_ALLOC_ALIGN to 16: C++17 requires allocation functions to
return storage aligned to __STDCPP_DEFAULT_NEW_ALIGNMENT__ (16 on
ppc64), and GCC 16 exploits that guarantee when vectorizing member
zero-initialization of TempObject-derived classes. At -mcpu=970 it
emits VMX stvx, which masks the low 4 address bits, so 8-byte-aligned
LifoAlloc storage caused wrong-address stores and uninitialized
members.

Assisted-by: Lance Albertson <lance@osuosl.org>
Assisted-by: Thushan Fernando <thushan@thushanfernando.com>
Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Assisted-by: Alexander Baldeck <alex.bldck@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant