Files
parallel-ps2/common/FastJmp.cpp
T
WizzardSK 5f2eea7bfb AArch64 (arm64) port: native EE/IOP/VU recompilers
Upstream PCSX2 emulation relies on x86-64 recompilers for the EE (R5900),
IOP (R3000A) and the VUs, none of which exist on ARM. This adds native
AArch64 recompilers plus the supporting infrastructure to run PS2 games
correctly and reasonably fast on 64-bit ARM Linux devices (developed and
tested on a Snapdragon-class device: Adreno 618, 4 KB-page Linux, glibc
RetroArch flatpak + musl test host). Verified booting to real in-game
content (Mega Man X7 gameplay, Gran Turismo 3 intro/FMV and in-race) on
Vulkan/Turnip.

Native components:
- IOP (R3000A) JIT ~98%: ALU incl. trap forms + RFE, aligned/unaligned
  mem, branches + delay slots, block linking, mult/div + HI/LO, COP0
  moves, write-back register cache, RAM fastmem, event-gated tails,
  native J (only the HLE import-stub form stays interpreted, by design),
  cap-chaining, idle-loop fast-forward. Only the J import stub (HLE) and
  SYSCALL still break blocks.
- EE (R5900) JIT ~89%: ALU, loads/stores incl. LQ/SQ + unaligned family
  (fused canonical pairs), branches + likely branches + BC0x/BC1x, block
  linking/chaining, MULT/DIV/MADD + HI/LO (both pipelines), complete
  COP1/FPU (data moves + all S-format arithmetic + CVT + C.cond,
  interpreter-exact clamp/flag semantics), MMI -> NEON (packed integer
  incl. PPAC5, PMFHI/PMFLO/PMTHI/PMTLO, PLZCW, PREVH), CFC1/CTC1,
  MFSA/MTSA, ERET, COP0 moves + EI/DI, kernel idle-loop skip,
  block-revalidation cache, write-back GPR register cache. Only SYSCALL /
  TLB / exceptions still break blocks.
- VU0 macro (COP2) ~78%: runs inline in the EE thread; all SPECIAL1/2 ALU
  ops emit native NEON directly into EE blocks (microVU0 single-op
  emitters) with run-local flag liveness; DIV/SQRT/RSQRT, ITOF/FTOI, VI
  ALU, VI load/store, RNG, MOVE/MR32, MTIR/MFIR, CLIP; COP2 transfers
  native. Only CALLMS(R) falls to the interpreter (as x86).
- VU1 micro (microVU1) ~70%: full microVU recompiler port incl. the
  register allocator; runs on the MTVU worker thread by default.
- VIF 100%: NEON-optimized unpack.

Overall recompiler-suite progress ~88% (weighted by remaining work;
dynamic instruction coverage on the tested titles is much higher).

Supporting infrastructure:
- VIXL vendored as the AArch64 assembler (3rdparty/vixl).
- Build gating: x86 recompiler/emitter sources gated off aarch64; build
  detects and accepts aarch64.
- Fastmem: a 4 GB host mapping where every memory-backed guest page sits
  at its own guest address, so a load/store is one instruction; HW-reg /
  unmapped touches fault into a compile-time backpatch stub.
- Emitted-code quality passes: x19 guest-reg base, displacement folding,
  pinned vmap/fastmem base (x28), pinned &microVU base (x27), out-of-line
  cold code, fused chain epilogues, constant-LUT block chaining,
  adrp+pageoff folds, run-local COP2 flag liveness, lazy MTVU kick.
- Env-gated diagnostics (LRPS2_*) for A/B bisection + an in-tree SIGPROF
  sampling profiler bucketed by JIT code cache.

Verification: every phase is checked bit-identically against a golden
framebuffer (pixel-sum + nonzero count) on a headless Vulkan harness and,
for deep coverage, a Gran Turismo 3 in-race save state:
- GT3 1500f = 106138289 / 860124, GT3 12000f = 29063788 / 855955
- MMX7 1500f = 111415017 / 800725
- GT3 in-race 600f = 49204167 / 858359
Most opcode/emitter changes carry a kill switch and leave these
byte-identical; the microVU work is additionally checked with an
interpreter-vs-microVU shadow differential.

Known limits: SYSCALL/TLB/exceptions and the IOP HLE import stub stay on
the interpreter by design; demanding titles (e.g. GT3) run below full
speed on this hardware class, so audio underruns in heavy scenes (the
emulated audio itself is correct -- a raw-performance ceiling, not a bug).
2026-07-17 21:58:23 +02:00

93 lines
2.0 KiB
C++

/* SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
* SPDX-License-Identifier: LGPL-3.0+ */
#include "FastJmp.h"
/* Win32 uses Fastjmp.asm, because MSVC doesn't support inline asm. */
#if !defined(_WIN32) || (defined(_M_ARM64) || defined(__aarch64__))
#if defined(__APPLE__)
#define PREFIX "_"
#else
#define PREFIX ""
#endif
#if defined(_M_X86) || defined(_M_X64) || defined(_M_AMD64) || defined(__amd64__) || defined(__x86_64__) || defined(__x86_64)
asm(
"\t.global " PREFIX "fastjmp_set\n"
"\t.global " PREFIX "fastjmp_jmp\n"
"\t.text\n"
"\t" PREFIX "fastjmp_set:" R"(
movq 0(%rsp), %rax
movq %rsp, %rdx # fixup stack pointer, so it doesn't include the call to fastjmp_set
addq $8, %rdx
movq %rax, 0(%rdi) # actually rip
movq %rbx, 8(%rdi)
movq %rdx, 16(%rdi) # actually rsp
movq %rbp, 24(%rdi)
movq %r12, 32(%rdi)
movq %r13, 40(%rdi)
movq %r14, 48(%rdi)
movq %r15, 56(%rdi)
xorl %eax, %eax
ret
)"
"\t" PREFIX "fastjmp_jmp:" R"(
movl %esi, %eax
movq 0(%rdi), %rdx # actually rip
movq 8(%rdi), %rbx
movq 16(%rdi), %rsp # actually rsp
movq 24(%rdi), %rbp
movq 32(%rdi), %r12
movq 40(%rdi), %r13
movq 48(%rdi), %r14
movq 56(%rdi), %r15
jmp *%rdx
)");
#elif (defined(_M_ARM64) || defined(__aarch64__))
asm(
"\t.global " PREFIX "fastjmp_set\n"
"\t.global " PREFIX "fastjmp_jmp\n"
"\t.text\n"
"\t.align 16\n"
"\t" PREFIX "fastjmp_set:" R"(
mov x16, sp
stp x16, x30, [x0]
stp x19, x20, [x0, #16]
stp x21, x22, [x0, #32]
stp x23, x24, [x0, #48]
stp x25, x26, [x0, #64]
stp x27, x28, [x0, #80]
str x29, [x0, #96]
stp d8, d9, [x0, #112]
stp d10, d11, [x0, #128]
stp d12, d13, [x0, #144]
stp d14, d15, [x0, #160]
mov w0, wzr
br x30
)"
".align 16\n"
"\t" PREFIX "fastjmp_jmp:" R"(
ldp x16, x30, [x0]
mov sp, x16
ldp x19, x20, [x0, #16]
ldp x21, x22, [x0, #32]
ldp x23, x24, [x0, #48]
ldp x25, x26, [x0, #64]
ldp x27, x28, [x0, #80]
ldr x29, [x0, #96]
ldp d8, d9, [x0, #112]
ldp d10, d11, [x0, #128]
ldp d12, d13, [x0, #144]
ldp d14, d15, [x0, #160]
mov w0, w1
br x30
)");
#endif
#endif /* __WIN32 */