PPU: Cleanup MacOs executable write protection guards

This commit is contained in:
Elad 2026-05-09 15:29:34 +03:00
parent d6e813cd72
commit 99d15190e5
5 changed files with 55 additions and 67 deletions

View File

@ -434,13 +434,35 @@ namespace asmjit
#endif #endif
} }
#ifdef __APPLE__
struct jit_write_guard
{
jit_write_guard() noexcept
{
pthread_jit_write_protect_np(false);
// Ensure stores are not reordered by the compiler
atomic_fence_acq_rel();
}
~jit_write_guard() noexcept
{
// Ensure stores are not reordered by the compiler
atomic_fence_seq_cst();
pthread_jit_write_protect_np(true);
}
};
#else
#define jit_write_guard [[maybe_unused]] int
#endif
// Build runtime function with asmjit::X86Assembler // Build runtime function with asmjit::X86Assembler
template <typename FT, typename Asm = native_asm, typename F> template <typename FT, typename Asm = native_asm, typename F>
inline FT build_function_asm(std::string_view name, F&& builder, ::jit_runtime* custom_runtime = nullptr, bool reduced_size = false) inline FT build_function_asm(std::string_view name, F&& builder, ::jit_runtime* custom_runtime = nullptr, bool reduced_size = false)
{ {
#ifdef __APPLE__ jit_write_guard jit_guard;
pthread_jit_write_protect_np(false);
#endif
using namespace asmjit; using namespace asmjit;
auto& rt = custom_runtime ? *custom_runtime : get_global_runtime(); auto& rt = custom_runtime ? *custom_runtime : get_global_runtime();

View File

@ -2175,18 +2175,12 @@ void ppu_thread::cpu_task()
} }
case ppu_cmd::lle_call: case ppu_cmd::lle_call:
{ {
#ifdef __APPLE__
pthread_jit_write_protect_np(true);
#endif
const vm::ptr<u32> opd(arg < 32 ? vm::cast(gpr[arg]) : vm::cast(arg)); const vm::ptr<u32> opd(arg < 32 ? vm::cast(gpr[arg]) : vm::cast(arg));
cmd_pop(), fast_call(opd[0], opd[1]); cmd_pop(), fast_call(opd[0], opd[1]);
break; break;
} }
case ppu_cmd::entry_call: case ppu_cmd::entry_call:
{ {
#ifdef __APPLE__
pthread_jit_write_protect_np(true);
#endif
cmd_pop(), fast_call(entry_func.addr, entry_func.rtoc, true); cmd_pop(), fast_call(entry_func.addr, entry_func.rtoc, true);
break; break;
} }
@ -2197,9 +2191,6 @@ void ppu_thread::cpu_task()
} }
case ppu_cmd::opd_call: case ppu_cmd::opd_call:
{ {
#ifdef __APPLE__
pthread_jit_write_protect_np(true);
#endif
const ppu_func_opd_t opd = cmd_get(1).as<ppu_func_opd_t>(); const ppu_func_opd_t opd = cmd_get(1).as<ppu_func_opd_t>();
cmd_pop(1), fast_call(opd.addr, opd.rtoc); cmd_pop(1), fast_call(opd.addr, opd.rtoc);
break; break;
@ -2218,9 +2209,6 @@ void ppu_thread::cpu_task()
} }
case ppu_cmd::initialize: case ppu_cmd::initialize:
{ {
#ifdef __APPLE__
pthread_jit_write_protect_np(false);
#endif
cmd_pop(); cmd_pop();
ppu_initialize(); ppu_initialize();
@ -2232,9 +2220,6 @@ void ppu_thread::cpu_task()
spu_cache::initialize(); spu_cache::initialize();
#ifdef __APPLE__
pthread_jit_write_protect_np(true);
#endif
#ifdef ARCH_ARM64 #ifdef ARCH_ARM64
// Flush all cache lines after potentially writing executable code // Flush all cache lines after potentially writing executable code
asm("ISB"); asm("ISB");
@ -2381,6 +2366,11 @@ void ppu_thread::cpu_wait(bs_t<cpu_flag> old)
void ppu_thread::exec_task() void ppu_thread::exec_task()
{ {
#ifdef __APPLE__
// Ensure correct state before executing JIT code
pthread_jit_write_protect_np(true);
#endif
if (g_cfg.core.ppu_decoder != ppu_decoder_type::_static) if (g_cfg.core.ppu_decoder != ppu_decoder_type::_static)
{ {
// HVContext push to allow recursion. This happens with guest callback invocations. // HVContext push to allow recursion. This happens with guest callback invocations.
@ -2462,9 +2452,6 @@ ppu_thread::ppu_thread(const ppu_thread_params& param, std::string_view name, u3
syscall_history.data.resize(g_cfg.core.ppu_call_history ? syscall_history_max_size : 1); syscall_history.data.resize(g_cfg.core.ppu_call_history ? syscall_history_max_size : 1);
syscall_history.count_debug_arguments = static_cast<u32>(g_cfg.core.ppu_call_history ? std::size(syscall_history.data[0].args) : 0); syscall_history.count_debug_arguments = static_cast<u32>(g_cfg.core.ppu_call_history ? std::size(syscall_history.data[0].args) : 0);
#ifdef __APPLE__
pthread_jit_write_protect_np(true);
#endif
#ifdef ARCH_ARM64 #ifdef ARCH_ARM64
// Flush all cache lines after potentially writing executable code // Flush all cache lines after potentially writing executable code
asm("ISB"); asm("ISB");
@ -4008,9 +3995,8 @@ extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_
named_thread_group workers("SPRX Worker ", std::min<u32>(software_thread_limit, cpu_thread_limit), [&] named_thread_group workers("SPRX Worker ", std::min<u32>(software_thread_limit, cpu_thread_limit), [&]
{ {
#ifdef __APPLE__ jit_write_guard jit_guard;
pthread_jit_write_protect_np(false);
#endif
// Set low priority // Set low priority
thread_ctrl::scoped_priority low_prio(-1); thread_ctrl::scoped_priority low_prio(-1);
u32 inc_fdone = 1; u32 inc_fdone = 1;
@ -4236,9 +4222,6 @@ extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_
return; return;
} }
#ifdef __APPLE__
pthread_jit_write_protect_np(false);
#endif
// Set low priority // Set low priority
thread_ctrl::scoped_priority low_prio(-1); thread_ctrl::scoped_priority low_prio(-1);
@ -4617,6 +4600,8 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
progress_dialog.emplace(get_localized_string(localized_string_id::PROGRESS_DIALOG_LOADING_PPU_MODULES)); progress_dialog.emplace(get_localized_string(localized_string_id::PROGRESS_DIALOG_LOADING_PPU_MODULES));
} }
jit_write_guard jit_guard;
// Permanently loaded compiled PPU modules (name -> data) // Permanently loaded compiled PPU modules (name -> data)
jit_module& jit_mod = g_fxo->get<jit_module_manager>().get(cache_path + "_" + std::to_string(std::bit_cast<usz>(info.segs[0].ptr))); jit_module& jit_mod = g_fxo->get<jit_module_manager>().get(cache_path + "_" + std::to_string(std::bit_cast<usz>(info.segs[0].ptr)));
@ -4797,9 +4782,6 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
// Try to make the code fit in 16 bytes, may fail and fallback // Try to make the code fit in 16 bytes, may fail and fallback
if (*full_sample && abs_diff(*full_sample, reinterpret_cast<u64>(jit_runtime::peek(true) + 3 * 4)) < (128u << 20)) if (*full_sample && abs_diff(*full_sample, reinterpret_cast<u64>(jit_runtime::peek(true) + 3 * 4)) < (128u << 20))
{ {
#ifdef __APPLE__
pthread_jit_write_protect_np(false);
#endif
u8* code = jit_runtime::alloc(12, 4, true); u8* code = jit_runtime::alloc(12, 4, true);
code_ptr = reinterpret_cast<u64>(code); code_ptr = reinterpret_cast<u64>(code);
@ -4914,6 +4896,11 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
#endif #endif
}, runtime.get(), true); }, runtime.get(), true);
#ifdef __APPLE__
// Restore write-protection state (modified by build_function_asm)
pthread_jit_write_protect_np(false);
#endif
// Full sample may exist already, but is very far away // Full sample may exist already, but is very far away
// So in this case, a new sample is written // So in this case, a new sample is written
ensure(code_size_until_jump != umax); ensure(code_size_until_jump != umax);
@ -5307,9 +5294,8 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
// Set low priority // Set low priority
thread_ctrl::scoped_priority low_prio(-1); thread_ctrl::scoped_priority low_prio(-1);
#ifdef __APPLE__ jit_write_guard jit_guard;
pthread_jit_write_protect_np(false);
#endif
for (u32 i = (*work_cv)++; i < workload.size(); i = (*work_cv)++, (*work_done)++, g_progr_pdone++) for (u32 i = (*work_cv)++; i < workload.size(); i = (*work_cv)++, (*work_done)++, g_progr_pdone++)
{ {
if (cpu ? cpu->state.all_of(cpu_flag::exit) : Emu.IsStopped()) if (cpu ? cpu->state.all_of(cpu_flag::exit) : Emu.IsStopped())
@ -5332,10 +5318,6 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
} }
core_lock.unlock(); core_lock.unlock();
#ifdef __APPLE__
pthread_jit_write_protect_np(true);
#endif
} }
}; };
@ -5487,10 +5469,6 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
} }
} }
#ifdef __APPLE__
// Symbol resolver is in JIT mem, so we must enable execution
pthread_jit_write_protect_np(true);
#endif
{ {
usz index = umax; usz index = umax;
@ -5503,11 +5481,6 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
} }
} }
#ifdef __APPLE__
// Symbol resolver is in JIT mem, so we must enable execution
pthread_jit_write_protect_np(false);
#endif
// Find a BLR-only function in order to copy it to all BLRs (some games need it) // Find a BLR-only function in order to copy it to all BLRs (some games need it)
for (const auto& func : info.get_funcs()) for (const auto& func : info.get_funcs())
{ {

View File

@ -207,9 +207,7 @@ static void ghc_cpp_trampoline(u64 fn_target, native_asm& c, auto& args)
DECLARE(spu_runtime::tr_dispatch) = [] DECLARE(spu_runtime::tr_dispatch) = []
{ {
#ifdef __APPLE__ jit_write_guard jit_guard;
pthread_jit_write_protect_np(false);
#endif
#if defined(ARCH_X64) #if defined(ARCH_X64)
// Generate a special trampoline to spu_recompiler_base::dispatch with pause instruction // Generate a special trampoline to spu_recompiler_base::dispatch with pause instruction
u8* const trptr = jit_runtime::alloc(32, 16); u8* const trptr = jit_runtime::alloc(32, 16);
@ -237,6 +235,7 @@ DECLARE(spu_runtime::tr_dispatch) = []
DECLARE(spu_runtime::tr_branch) = [] DECLARE(spu_runtime::tr_branch) = []
{ {
jit_write_guard jit_guard;
#if defined(ARCH_X64) #if defined(ARCH_X64)
// Generate a trampoline to spu_recompiler_base::branch // Generate a trampoline to spu_recompiler_base::branch
u8* const trptr = jit_runtime::alloc(32, 16); u8* const trptr = jit_runtime::alloc(32, 16);
@ -262,6 +261,7 @@ DECLARE(spu_runtime::tr_branch) = []
DECLARE(spu_runtime::tr_interpreter) = [] DECLARE(spu_runtime::tr_interpreter) = []
{ {
jit_write_guard jit_guard;
#if defined(ARCH_X64) #if defined(ARCH_X64)
u8* const trptr = jit_runtime::alloc(32, 16); u8* const trptr = jit_runtime::alloc(32, 16);
u8* raw = move_args_ghc_to_native(trptr); u8* raw = move_args_ghc_to_native(trptr);
@ -283,6 +283,8 @@ DECLARE(spu_runtime::tr_interpreter) = []
DECLARE(spu_runtime::g_dispatcher) = [] DECLARE(spu_runtime::g_dispatcher) = []
{ {
jit_write_guard jit_guard;
// Allocate 2^20 positions in data area // Allocate 2^20 positions in data area
const auto ptr = reinterpret_cast<std::remove_const_t<decltype(spu_runtime::g_dispatcher)>>(jit_runtime::alloc(sizeof(*g_dispatcher), 64, false)); const auto ptr = reinterpret_cast<std::remove_const_t<decltype(spu_runtime::g_dispatcher)>>(jit_runtime::alloc(sizeof(*g_dispatcher), 64, false));
@ -296,6 +298,8 @@ DECLARE(spu_runtime::g_dispatcher) = []
DECLARE(spu_runtime::tr_all) = [] DECLARE(spu_runtime::tr_all) = []
{ {
jit_write_guard jit_guard;
#if defined(ARCH_X64) #if defined(ARCH_X64)
u8* const trptr = jit_runtime::alloc(32, 16); u8* const trptr = jit_runtime::alloc(32, 16);
u8* raw = trptr; u8* raw = trptr;
@ -834,6 +838,8 @@ void spu_cache::add(const spu_program& func)
void spu_cache::initialize(bool build_existing_cache) void spu_cache::initialize(bool build_existing_cache)
{ {
jit_write_guard jit_guard;
spu_runtime::g_interpreter = spu_runtime::g_gateway; spu_runtime::g_interpreter = spu_runtime::g_gateway;
if (g_cfg.core.spu_decoder == spu_decoder_type::_static || g_cfg.core.spu_decoder == spu_decoder_type::dynamic) if (g_cfg.core.spu_decoder == spu_decoder_type::_static || g_cfg.core.spu_decoder == spu_decoder_type::dynamic)
@ -964,15 +970,7 @@ void spu_cache::initialize(bool build_existing_cache)
// every exit path (return, exception, etc.). Leaving a worker // every exit path (return, exception, etc.). Leaving a worker
// in write mode at teardown can leave per-thread state // in write mode at teardown can leave per-thread state
// inconsistent on AArch64. // inconsistent on AArch64.
pthread_jit_write_protect_np(false); jit_write_guard jit_guard;
struct jit_write_guard
{
~jit_write_guard()
{
pthread_jit_write_protect_np(true);
}
} _jit_guard;
#endif #endif
// Set low priority // Set low priority
thread_ctrl::scoped_priority low_prio(-1); thread_ctrl::scoped_priority low_prio(-1);
@ -2207,6 +2205,9 @@ void spu_recompiler_base::dispatch(spu_thread& spu, void*, u8* rip)
return; return;
} }
#if defined(__APPLE__)
pthread_jit_write_protect_np(false);
#endif
auto program = spu.jit->analyse(spu._ptr<u32>(0), spu.pc); auto program = spu.jit->analyse(spu._ptr<u32>(0), spu.pc);
#ifdef ARCH_ARM64 #ifdef ARCH_ARM64
const auto func = compile_spu_llvm_with_retry(spu.jit, program); const auto func = compile_spu_llvm_with_retry(spu.jit, program);

View File

@ -1252,16 +1252,8 @@ extern void ppu_execute_syscall(ppu_thread& ppu, u64 code)
if (const auto func = g_ppu_syscall_table[code].first) if (const auto func = g_ppu_syscall_table[code].first)
{ {
#ifdef __APPLE__
pthread_jit_write_protect_np(false);
#endif
func(ppu, {}, vm::_ptr<u32>(ppu.cia), nullptr); func(ppu, {}, vm::_ptr<u32>(ppu.cia), nullptr);
ppu_log.trace("Syscall '%s' (%llu) finished, r3=0x%llx", ppu_syscall_code(code), code, ppu.gpr[3]); ppu_log.trace("Syscall '%s' (%llu) finished, r3=0x%llx", ppu_syscall_code(code), code, ppu.gpr[3]);
#ifdef __APPLE__
pthread_jit_write_protect_np(true);
// No need to flush cache lines after a syscall, since we didn't generate any code.
#endif
return; return;
} }
} }

View File

@ -1893,7 +1893,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
struct jit_write_guard struct jit_write_guard
{ {
~jit_write_guard() ~jit_write_guard() noexcept
{ {
pthread_jit_write_protect_np(true); pthread_jit_write_protect_np(true);
} }