1- // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
1+ // SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
22// SPDX-License-Identifier: CC-BY-NC-ND-4.0
33
44#include " bus.h"
2020#include " common/log.h"
2121#include " common/memmap.h"
2222
23+ #if defined(__APPLE__) && defined(CPU_ARCH_ARM64)
24+ #include < pthread.h> // pthread_jit_write_protect_np()
25+ #endif
26+
2327LOG_CHANNEL (CodeCache);
2428
2529// Enable dumping of recompiled block code size statistics.
@@ -69,6 +73,11 @@ static constexpr u32 RECOMPILER_FAR_CODE_CACHE_SIZE = 16 * 1024 * 1024;
6973#define USE_CODE_BUFFER_SECTION 1
7074#endif
7175
76+ // / JIT write protect for Apple Silicon. Needs to be called prior to writing to any RWX pages.
77+ #if defined(__APPLE__) && defined(CPU_ARCH_ARM64)
78+ #define NEEDS_JIT_WRITE_PROTECT 1
79+ #endif
80+
7281static void AllocateLUTs ();
7382static void DeallocateLUTs ();
7483static void ResetCodeLUT ();
@@ -98,6 +107,7 @@ template<PGXPMode pgxp_mode>
98107static void BacklinkBlocks (u32 pc, const void * dst);
99108static void UnlinkBlockExits (Block* block);
100109static void ResetCodeBuffer ();
110+ static void JITWriteProtect (bool enabled);
101111
102112static void CompileASMFunctions ();
103113static bool CompileBlock (Block* block);
@@ -138,6 +148,10 @@ struct Locals
138148 u32 total_host_instructions_emitted = 0 ;
139149 u32 total_host_code_used_by_instructions = 0 ;
140150#endif
151+
152+ #ifdef NEEDS_JIT_WRITE_PROTECT
153+ bool jit_write_protect_enabled = false ;
154+ #endif
141155};
142156} // namespace
143157
@@ -200,6 +214,11 @@ bool CPU::CodeCache::ProcessStartup(Error* error)
200214 if (!PageFaultHandler::Install (error))
201215 return false ;
202216
217+ #ifdef NEEDS_JIT_WRITE_PROTECT
218+ // Write protect starts enabled.
219+ s_locals.jit_write_protect_enabled = true ;
220+ #endif
221+
203222 return true ;
204223}
205224
@@ -219,8 +238,7 @@ void CPU::CodeCache::Reset()
219238 if (IsUsingRecompiler ())
220239 {
221240 ResetCodeBuffer ();
222- CompileASMFunctions ();
223- ResetCodeLUT ();
241+ JITWriteProtect (true );
224242 }
225243}
226244
@@ -598,8 +616,6 @@ void CPU::CodeCache::InvalidateBlocksWithPageIndex(u32 index)
598616 if (!ppi.first_block_in_page )
599617 return ;
600618
601- MemMap::BeginCodeWrite ();
602-
603619 Block* block = ppi.first_block_in_page ;
604620 while (block)
605621 {
@@ -610,7 +626,7 @@ void CPU::CodeCache::InvalidateBlocksWithPageIndex(u32 index)
610626 ppi.first_block_in_page = nullptr ;
611627 ppi.last_block_in_page = nullptr ;
612628
613- MemMap::EndCodeWrite ( );
629+ JITWriteProtect ( true );
614630}
615631
616632CPU::CodeCache::PageProtectionMode CPU::CodeCache::GetProtectionModeForPC (u32 pc)
@@ -646,7 +662,6 @@ void CPU::CodeCache::InvalidateBlock(Block* block, BlockState new_state)
646662void CPU::CodeCache::InvalidateAllRAMBlocks ()
647663{
648664 // TODO: maybe combine the backlink into one big instruction flush cache?
649- MemMap::BeginCodeWrite ();
650665
651666 for (Block* block : s_locals.blocks )
652667 {
@@ -657,13 +672,14 @@ void CPU::CodeCache::InvalidateAllRAMBlocks()
657672 }
658673 }
659674
675+ JITWriteProtect (true );
676+
660677 for (PageProtectionInfo& ppi : s_page_protection)
661678 {
662679 ppi.first_block_in_page = nullptr ;
663680 ppi.last_block_in_page = nullptr ;
664681 }
665682
666- MemMap::EndCodeWrite ();
667683 Bus::ClearRAMCodePageFlags ();
668684}
669685
@@ -1289,7 +1305,6 @@ void CPU::CodeCache::CompileOrRevalidateBlock(u32 start_pc)
12891305{
12901306 // TODO: this doesn't currently handle when the cache overflows...
12911307 DebugAssert (IsUsingRecompiler ());
1292- MemMap::BeginCodeWrite ();
12931308
12941309 Block* block = LookupBlock (start_pc);
12951310 if (block)
@@ -1301,7 +1316,7 @@ void CPU::CodeCache::CompileOrRevalidateBlock(u32 start_pc)
13011316 DebugAssert (block->host_code );
13021317 SetCodeLUT (start_pc, block->host_code );
13031318 BacklinkBlocks (start_pc, block->host_code );
1304- MemMap::EndCodeWrite ( );
1319+ JITWriteProtect ( true );
13051320 return ;
13061321 }
13071322
@@ -1319,7 +1334,7 @@ void CPU::CodeCache::CompileOrRevalidateBlock(u32 start_pc)
13191334 ERROR_LOG (" Failed to read block at 0x{:08X}, falling back to uncached interpreter" , start_pc);
13201335 SetCodeLUT (start_pc, g_recompiler_functions.interpret_block );
13211336 BacklinkBlocks (start_pc, g_recompiler_functions.interpret_block );
1322- MemMap::EndCodeWrite ( );
1337+ JITWriteProtect ( true );
13231338 return ;
13241339 }
13251340
@@ -1333,22 +1348,31 @@ void CPU::CodeCache::CompileOrRevalidateBlock(u32 start_pc)
13331348 free_far_code_space < Recompiler::MIN_CODE_RESERVE_FOR_BLOCK)
13341349 {
13351350 ERROR_LOG (" Out of code space while compiling {:08X}. Resetting code cache." , start_pc);
1336- CodeCache::Reset ();
1351+ ResetCodeBuffer ();
13371352 }
13381353
1339- if ((block = CreateBlock (start_pc, s_locals.block_instructions , metadata)) == nullptr || block->size == 0 ||
1340- !CompileBlock (block))
1354+ if ((block = CreateBlock (start_pc, s_locals.block_instructions , metadata)) == nullptr || block->size == 0 )
1355+ {
1356+ ERROR_LOG (" Failed to create block at 0x{:08X}, falling back to uncached interpreter" , start_pc);
1357+ SetCodeLUT (start_pc, g_recompiler_functions.interpret_block );
1358+ BacklinkBlocks (start_pc, g_recompiler_functions.interpret_block );
1359+ JITWriteProtect (true );
1360+ return ;
1361+ }
1362+
1363+ JITWriteProtect (false );
1364+ if (!CompileBlock (block))
13411365 {
13421366 ERROR_LOG (" Failed to compile block at 0x{:08X}, falling back to uncached interpreter" , start_pc);
13431367 SetCodeLUT (start_pc, g_recompiler_functions.interpret_block );
13441368 BacklinkBlocks (start_pc, g_recompiler_functions.interpret_block );
1345- MemMap::EndCodeWrite ( );
1369+ JITWriteProtect ( true );
13461370 return ;
13471371 }
13481372
13491373 SetCodeLUT (start_pc, block->host_code );
13501374 BacklinkBlocks (start_pc, block->host_code );
1351- MemMap::EndCodeWrite ( );
1375+ JITWriteProtect ( true );
13521376}
13531377
13541378void CPU::CodeCache::DiscardAndRecompileBlock (u32 start_pc)
@@ -1419,6 +1443,7 @@ void CPU::CodeCache::BacklinkBlocks(u32 pc, const void* dst)
14191443 {
14201444 DEBUG_LOG (" Backlinking {} with dst pc {:08X} to {}{}" , it->second , pc, dst,
14211445 (dst == g_recompiler_functions.compile_or_revalidate_block ) ? " [compiler]" : " " );
1446+ JITWriteProtect (false );
14221447 EmitJump (it->second , dst, true );
14231448 }
14241449}
@@ -1433,10 +1458,10 @@ void CPU::CodeCache::UnlinkBlockExits(Block* block)
14331458
14341459void CPU::CodeCache::ResetCodeBuffer ()
14351460{
1461+ JITWriteProtect (false );
1462+
14361463 if (s_locals.code_used > 0 || s_locals.far_code_used > 0 )
14371464 {
1438- MemMap::BeginCodeWrite ();
1439-
14401465 if (s_locals.code_used > 0 )
14411466 {
14421467 std::memset (s_locals.code_ptr , 0 , s_locals.code_used );
@@ -1448,8 +1473,6 @@ void CPU::CodeCache::ResetCodeBuffer()
14481473 std::memset (s_locals.far_code_ptr , 0 , s_locals.far_code_used );
14491474 MemMap::FlushInstructionCache (s_locals.far_code_ptr , s_locals.far_code_used );
14501475 }
1451-
1452- MemMap::EndCodeWrite ();
14531476 }
14541477
14551478#ifdef USE_CODE_BUFFER_SECTION
@@ -1468,6 +1491,20 @@ void CPU::CodeCache::ResetCodeBuffer()
14681491 s_locals.far_code_ptr = (far_code_size > 0 ) ? (static_cast <u8 *>(s_locals.code_ptr ) + s_locals.code_size ) : nullptr ;
14691492 s_locals.free_far_code_ptr = s_locals.far_code_ptr ;
14701493 s_locals.far_code_used = 0 ;
1494+
1495+ CompileASMFunctions ();
1496+ ResetCodeLUT ();
1497+ }
1498+
1499+ ALWAYS_INLINE_RELEASE void CPU::CodeCache::JITWriteProtect (bool enabled)
1500+ {
1501+ #ifdef NEEDS_JIT_WRITE_PROTECT
1502+ if (enabled == s_locals.jit_write_protect_enabled )
1503+ return ;
1504+
1505+ s_locals.jit_write_protect_enabled = enabled;
1506+ pthread_jit_write_protect_np (enabled);
1507+ #endif
14711508}
14721509
14731510u8 * CPU::CodeCache::GetFreeCodePointer ()
@@ -1546,7 +1583,7 @@ const void* CPU::CodeCache::GetInterpretUncachedBlockFunction()
15461583
15471584void CPU::CodeCache::CompileASMFunctions ()
15481585{
1549- MemMap::BeginCodeWrite ( );
1586+ JITWriteProtect ( false );
15501587
15511588#ifdef DUMP_CODE_SIZE_STATS
15521589 s_locals.total_instructions_compiled = 0 ;
@@ -1561,7 +1598,6 @@ void CPU::CodeCache::CompileASMFunctions()
15611598#endif
15621599
15631600 CommitCode (asm_size);
1564- MemMap::EndCodeWrite ();
15651601}
15661602
15671603bool CPU::CodeCache::CompileBlock (Block* block)
@@ -1708,7 +1744,7 @@ PageFaultHandler::HandlerResult CPU::CodeCache::HandleFastmemException(void* exc
17081744 static_cast <unsigned >(info.address_register ), static_cast <unsigned >(info.data_register ),
17091745 info.AccessSizeInBytes (), static_cast <unsigned >(info.is_signed ));
17101746
1711- MemMap::BeginCodeWrite ( );
1747+ JITWriteProtect ( false );
17121748
17131749 BackpatchLoadStore (exception_pc, info);
17141750
@@ -1726,7 +1762,7 @@ PageFaultHandler::HandlerResult CPU::CodeCache::HandleFastmemException(void* exc
17261762 block->compile_count = 1 ;
17271763 }
17281764
1729- MemMap::EndCodeWrite ( );
1765+ JITWriteProtect ( true );
17301766
17311767 // and store the pc in the faulting list, so that we don't emit another fastmem loadstore
17321768 s_locals.fastmem_faulting_pcs .insert (info.guest_pc );
0 commit comments