- Shell 83.4%
- C 15.6%
- Dockerfile 1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
Build / build-native-linux (push) Successful in 2m8s
Release / build-native-linux (push) Successful in 3m47s
Release / package-linux (push) Successful in 1m45s
Build / build-windows-cross (push) Successful in 2m10s
Release / build-windows-cross (push) Successful in 1m40s
Release / package-windows (push) Successful in 2m16s
PS2-194. The EE wrapper config and the libc++, emutls and compiler-rt builds use the PS2 triple, which implies the R5900 and n32 so lld can link -flto bitcode. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
| .forgejo/workflows | ||
| docker | ||
| lib | ||
| scripts | ||
| src/iopmini | ||
| steps | ||
| wrappers | ||
| .gitignore | ||
| build.sh | ||
| config.sh | ||
| README.md | ||
llvm-toolchain
⚠️ Work in progress. This is part of the experimental LLVM-based PS2 toolchain. It changes often, has gaps, and is not ready for production use. ⚠️
Build scripts and CI for the LLVM-based PS2 toolchain (EE and IOP): clang and lld from the
ps2/llvm fork (r5900-ee), LLVM binutils replacements, compiler-rt builtins, newlib from
ps2/newlib-llvm, pthread-embedded, libc++, libc++abi and libatomic.
Building
PS2DEV=/opt/ps2dev-llvm ./build.sh # everything
PS2DEV=/opt/ps2dev-llvm ./build.sh 3 4 # only steps 3 and 4
Steps (steps/): 1 LLVM, 2 per-target wrappers, 3 compiler-rt, 4 newlib, 5 newlib-nano,
6 pthread-embedded, 7 libc++/libc++abi/libatomic. newlib-nano installs libc_nano.a, libm_nano.a
and libg_nano.a beside the full libraries; link with -lc_nano -lm_nano to use them. Source pins are in config.sh. Set PS2_CROSS_HOST=x86_64-w64-mingw32 to build the Windows-hosted
compiler from Linux (steps 1 and 2 only; the target libraries come from a native build).
The install is relocatable: llvm/bin holds the compiler and tools, ee/bin and iop/bin hold
the target-prefixed wrappers (mips64r5900el-ps2-elf-clang, -ar, ...) with their .cfg files,
and ee/mips64r5900el-ps2-elf, iop/mipsel-none-elf are the sysroots.
scripts/sanity-check.sh <install-dir> compiles small EE and IOP programs against the install.
Local Linux run
docker/run-local.sh <out-dir> [steps] runs the build in the CI image with sources and build
trees in a Docker volume and the install bind-mounted to <out-dir>. It needs LLVM_SRC_DIR
and NEWLIB_SRC_DIR pointing at local clones of ps2/llvm and ps2/newlib-llvm. The container is limited
to 12 GB and LLVM_COMPILE_JOBS (default 6): more parallel clang compiles exhaust the Docker VM.
CI
.forgejo/workflows/build.yml builds and sanity-checks on every push. release.yml runs on
v* tags: native Linux build, mingw cross build for Windows, then publishes both zips to the
release.
Self-contained LLVM/clang cross-toolchain for PS2 homebrew (EE and IOP), built from the LLVM
fork at llvm/llvm-project (patch: llvm/patches/r5900-vu0-vux-phase2-*.patch). Experimental,
parallel to the GCC-based toolchain/ - nothing here depends on toolchain/ staying in place,
and nothing in toolchain/ depends on this. No GCC component is used anywhere in this
toolchain or in anything built with it - verified end to end, see "Zero GCC, verified" below.
Layout
llvm-fa360c3b8dbf/ a real, versioned install (named after the LLVM fork's commit) - not a
live reference into the dev build tree. `bin/clang.exe`,
`lib/clang/24/include/` (resource headers). Produced by
`ninja install-clang install-clang-resource-headers` from
`llvm/build`, copied here. Re-run that and copy over again to pick up a
rebuilt compiler - see "Updating the compiler" below.
ee/
bin/
mips64r5900el-ps2-elf-clang.bat wrapper: execs ../../llvm-fa360c3b8dbf/bin/clang.exe
--config=<the .cfg below>
mips64r5900el-ps2-elf-clang.cfg default flags (target triple, ABI, -fno-pic, header/lib
search paths, ...)
mips64r5900el-ps2-elf-{ar,ld,objcopy,ranlib,nm,strip,objdump,...}.exe
real GNU binutils (no llvm-ar/lld built yet - see gaps)
mips64r5900el-ps2-elf/
include/ newlib headers (clang-built, `newlib/build-clang-msys2-v2` install output)
lib/ libc.a, libm.a, libg.a, libpthread.a, libclang_rt.builtins-mips64el.a (ALL
clang-built - no libgcc.a here, see "Zero GCC" below), crt0.o (newlib's generic
PS2 startup - most PS2 homebrew uses its own crt0 from sdk-core instead, kept
for completeness)
iop/
bin/
mipsel-none-elf-clang.bat wrapper: execs ../../llvm-fa360c3b8dbf/bin/clang.exe
--config=<the .cfg below>
mipsel-none-elf-clang.cfg default flags
mipsel-none-elf-{ar,ld,objcopy,ranlib,nm,strip,objdump,...}.exe
mipsel-none-elf/
lib/ crt0.o (unused by real IRX modules - see note), crti/crtbegin/crtend/crtn.o
(GCC's real default IOP link convention - the objects themselves are GCC's,
see gaps below), libclang_rt.builtins-mipsel.a (clang-built), ldscripts/
No IOP include/ - the IOP target has no C library at all (matches the existing GCC IOP
toolchain); sdk-core provides IOP module headers itself, per package, same as it always has.
Using it
Point CC at the relevant bin/*-clang.bat and treat it like a normal cross-compiler - it's a
thin wrapper (a couple lines, %~dp0-relative) that runs the versioned clang.exe with its
target's .cfg explicitly (--config=...), so mips64r5900el-ps2-elf-clang.bat foo.c -c just
works without extra flags. Verified working invoked from PowerShell, Git-Bash, and the portable
MSYS2 bash bundled with toolchain/ (real builds should use that MSYS2, not this machine's global
make - see feedback_llvm_toolchain_gotchas memory for why). Link with the bin/*-ld.exe next
to it and the libs under <target>/lib/. See scratch/llvm-makefile-test/Makefile for a complete,
working example.
Why a wrapper script + a real versioned install, not a hardlink per target (two earlier
iterations did that): a hardlinked binary copy is live - it drifts the moment llvm/build/bin/ clang.exe is rebuilt with no version attached to what's actually in there, and doesn't survive
being zipped/copied elsewhere (a hardlink becomes an independent full copy on transfer). Real
toolchains (Android NDK's target-triple wrapper scripts, the LLVM project's own releases) do a
deliberate install step into an immutable, versioned directory instead, and point every wrapper at
that fixed path. .bat wrappers still avoid duplicating the compiler per target (one binary,
several thin scripts), same reasoning as before - just backed by a real install now.
C++: the install carries only clang.exe (the clang++, clang-cl, clang-cpp and clang-dxc names were identical 100 MB copies and are gone). Compile C++ by passing --driver-mode=g++ to the wrapper, for example mips64r5900el-ps2-elf-clang.bat --driver-mode=g++ -std=c++17 -c foo.cpp.
Updating the compiler: cd llvm/build && ninja install-clang install-clang-resource-headers
installs to llvm/install/ by default; copy bin/clang.exe and lib/clang/ from there into a
new (or, during active same-session iteration, the same) llvm-fa360c3b8dbf/-style directory here,
then repoint the .bat/.cfg files if the directory name changed. A proper "cut a new version"
step (new directory per real change, wrappers bumped explicitly) is the natural next refinement
once this stops being continuous same-session iteration.
Real PS2 programs (both EE homebrew and IOP IRX modules) don't use the generic crt0/crti+
crtbegin+crtend+crtn startup convention above in practice - EE homebrew boots via EELOAD
into sdk-core's own startup code, and IOP modules load via loadcore's IRX import/export table
convention, not a normal ELF _start. Those objects are here for toolchain completeness (so a
bare clang foo.c -o foo works, same as it does with the GCC toolchain) - see
scratch/llvm-spinningcube/, scratch/llvm-iop-rpc/, and scratch/llvm-makefile-test/ for how
real sdk-core-based programs are actually built and linked.
Zero GCC, verified
scratch/llvm-makefile-test/ is a real Makefile-driven project built entirely against this
toolchain - not just main.c, but the TTY diagnostic path too (tty.c/deci2.c compiled fresh
from sdk-core source, plus a hand-extracted single-syscall stub and a minimal DIntr/EIntr,
not prebuilt GCC .o files or the full kernel.S/glue.c - see that Makefile's header comment
for exactly why). Confirmed:
readelf -p .commenton the built ELF:clang version 24.0.0git (...)- nothing else.nmon the built ELF: nogcc-named symbols anywhere.mips64r5900el-ps2-elf-ld.exe --version: real GNU Binutils - a separate project from GCC, not a GCCcollect2wrapper.- Ran clean in PCSX2: real libc (
strcpy/strlen/memcmp) and real libm (sqrtf) both verified working at runtime, not just linking cleanly.
The one library still built by GCC's own crt objects is IOP's crti/crtbegin/crtend/crtn.o (the
objects are GCC's; nothing links against a GCC-built library there since IOP has no libc at
all) - real IOP modules don't use that startup path anyway (see above), so it's untested whether a
real IRX build needs anything from it.
Known gaps
- No
llvm-ar/llvm-ranlib/lldbuilt - GNU binutils are vendored in instead. Fine for now, ELF tooling is toolchain-agnostic (a separate project from GCC either way). newlib-nanoand a real IOP IRX module build/boot test (as opposed to the EE-side verification above) are not done - this is still the experimental, parallel-track toolchain, not the default.ps2build --llvm(on its ownllvmbranch) now drives a real, fullps2workspace.yamlbuild against this toolchain - seeps2build/PLAN.mdfor where that stands. Not merged tops2build's default branch and not installed byps2build update.
IOP CPU (fixed 2026-09-20): -mcpu=r3000a, not generic mips1
The IOP's real CPU (R3000A-based, also the original PlayStation's CPU) is
now a named LLVM target CPU, r3000a, instead of the generic mips1
bucket used before. The IOP .cfg uses it in place of -mcpu=mips1.
Motivation: existing sdk-core/newlib IOP asm (sysclib's hand-optimized
memset.S/memcpy.S/etc) uses cache/pref directly - real MIPS
III/32-lineage instructions per the real GNU opcode table, not something
GNU leniency invented; real GNU as -march=mips1 rejects them too. r3000a
adds a narrow FeaturePS2Cache feature that accepts just those two
mnemonics, without pulling in real FeatureMips3_32's actual MIPS III/32
codegen implications - the compiler's own code-generation baseline for
ordinary C stays exactly as conservative as plain mips1. FeatureR5900
gets the same feature, which also closes the R5900 cache gap this file
used to list under "Known gaps" - same root cause (INSN_MIPS3_32_NOT_32R6_64R6,
upstream's CACHE/PREF gate, pulls in more than just mnemonic
acceptance), same fix. Both byte-diff verified against real GNU as.
Gotcha: clang's driver validates -mcpu= against its own hardcoded CPU
list (clang/lib/Basic/Targets/Mips.cpp's ValidCPUNames), separate from
the LLVM backend's own TableGen-derived CPU list that llvm-mc already
accepts new names from directly. Adding a backend CPU needs both lists
updated, not just the backend - llvm-mc -mcpu=<name> working is not
proof clang -mcpu=<name> will.
R5900 MMI instruction set (fixed 2026-09-20)
The gap flagged in earlier revisions of this section - upstream LLVM missing the full R5900 MMI
(paired/packed 128-bit SIMD) instruction set - is closed. ~100 instructions added to
MipsVU0InstrInfo.td: the full MMI group (pand/pnor/pcpyld/pcpyud/psubb/pxor/...),
the pipeline-1 HI/LO instructions (mult1/div1/madd1/mfhi1/mflo1/mthi1/mtlo1/...), the
SA-register instructions (mfsa/mtsa/mtsab/mtsah), and movn/movz (standard MIPS IV
conditional move, never enabled for this subtarget before). Every encoding was mechanically
derived from real GNU binutils' own opcode table
(toolchain/build/ee/binutils-gdb/opcodes/mips-opc.c) rather than hand-transcribed from the EE
manual, then spot-checked byte-identical against real mips64r5900el-ps2-elf-as across every
distinct operand shape in the set (3-reg, 2-reg, 1-reg, shift-immediate, 16-bit-immediate).
Confirmed against the real motivating case: sdk-core's kernel.S (the file that originally
exposed this gap - its hand-optimized memcpy/memset/strlen/strncpy use MMI directly) now
assembles those functions with zero MMI-related errors.
Two small, deliberate scope choices, not bugs: div1/divu1 accept div1 $rs, $rt here instead
of gas's div1 $0, $rs, $rt (gas requires a literal $0 placeholder with no corresponding bit in
the encoding; verified byte-identical machine code either way, just a different accepted syntax);
mult1/multu1/madd1/maddu1 implement only the simpler 2-operand form that writes HI1/LO1
only, not the 3-operand form that also writes a GPR (mips-opc.c has both).
scratch/llvm-mmi-test/ is a real runtime correctness test, not just an assembly-level check: 22
individual PASS/FAIL cases covering every instruction family added (bitwise AND/OR/NOR/XOR,
byte-lane add/sub with wraparound, signed saturation, conditional move both branches, the
pipeline-1 multiply/HI1/LO1, the SA register, a shift-immediate, and the two real 128-bit-aware
ops pcpyld/pcpyud via lq/sq), run on real PCSX2 EE emulation - 22/22 pass. Two of the
expected values (pcpyud's hi/lo half-mapping direction, plzcw's leading-zero count) weren't
safe to hand-derive from memory, so they were taken from PCSX2's own first run instead of guessed
- see the comment in
main.cfor detail;plzcwmatched the hand-derived value,pcpyuddid not (initial guess had the halves backwards, corrected once).
Building libc++, libc++abi and libatomic
scripts/build-libcxx.sh configures the runtimes build from the r5900-ee LLVM fork
(LLVM_SRC, default ../llvm/llvm-project), builds libc++, libc++abi and libunwind with C++ exceptions on (LIBCXX_EXCEPTIONS=OFF for a build without),
builds scripts/atomic.c into libatomic.a, and installs all three into
ee/mips64r5900el-ps2-elf. Set INSTALL_ROOT to install somewhere else. Link with
-lc++ -lc++abi -lunwind -latomic. Programs that use exceptions also need the .eh_frame rules
in the SDK's EE linkfile.
thread_local uses emulated TLS: the EE driver config passes -femulated-tls (the R5900 has no rdhwr, so
native TLS cannot fetch a thread pointer). steps/08-emutls.sh adds compiler-rt's emutls.o to
libclang_rt.builtins-mips64el.a, and libc++abi is built with __cxa_thread_atexit for thread-exit destructors.
The sysroot's newlib comes from ps2/newlib-llvm, branch ee-v4.6.0-llvm: the ps2/newlib
ee-v4.6.0 branch plus one commit enabling the POSIX threads profile for __ps2sdk__.