From a0b2485c079dab3538769e042b41473f9efdb3b4 Mon Sep 17 00:00:00 2001 From: AurisDSP <235008503+AurisDSP@users.noreply.github.com> Date: Fri, 8 May 2026 23:20:24 -0400 Subject: [PATCH] Emu: enable JIT write mode in SPRX Loader on Apple Silicon The SPRX Loader thread spawned in Emulator::Load() invokes ppu_initialize() and ppu_precompile(), both of which write into the PPU JIT code cache. On AArch64 Apple Silicon, MAP_JIT pages default to execute mode for newly created threads, so any write into the cache from this thread segfaults before the game can boot. Reproducible on a clean RPCS3 0.0.40-19340 boot of Red Dead Redemption (BLUS30418) on macOS arm64: the emulator crashes ~12s into boot with "Segfault writing location 0x300010000" originating from the {SPRX Loader} thread, never reaching the title screen. Mirror the W^X handling already used by the PPU thread's ppu_cmd::initialize handler in PPUThread.cpp: enable write mode at thread entry and pair it with an RAII guard so execute mode is restored on every exit path (normal return, early-exit on Emu.IsStopped(), exception). No effect on x86_64 or non-Apple ARM64 builds (entire block is inside #ifdef __APPLE__). --- rpcs3/Emu/System.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 199aad38ee..a64c0618a9 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -1835,6 +1835,17 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, g_fxo->init("SPRX Loader"sv, [this, dir_queue, is_fast = m_precompilation_option.is_fast]() mutable { +#ifdef __APPLE__ + // Apple Silicon W^X: SPRX Loader writes JIT code via ppu_initialize(). + // Without entering write mode, writes to MAP_JIT pages segfault. + pthread_jit_write_protect_np(false); + + // RAII guard so execute mode is restored on every exit path + // (return, exception, etc.). + struct jit_write_guard { + ~jit_write_guard() { pthread_jit_write_protect_np(true); } + } _jit_guard; +#endif std::vector*> mod_list; if (auto& _main = *ensure(g_fxo->try_get>()); !_main.path.empty())