new_dynarec: native Apple Silicon (darwin-arm64) support#1184
Open
jeremyw wants to merge 1 commit into
Open
Conversation
jeremyw
force-pushed
the
darwin-arm64-dynarec
branch
from
July 19, 2026 01:33
2caaf0d to
312e6b4
Compare
Enable the arm64 new_dynarec on macOS, previously hard-disabled with
NO_ASM=1. Three independent blockers, three fixes:
1. linkage_arm64.S only assembled with ELF toolchains. Add a Mach-O
variant of the symbol macros: leading-underscore C symbols,
.private_extern instead of .hidden (.type/.size dropped, ELF-only),
sym@PAGE/sym@PAGEOFF instead of :lo12: adrp relocations, and '%%'
statement separators because the Darwin arm64 assembler treats ';'
as a comment. Dot-prefixed local labels (ELF-only) become numbered
locals. ELF output is unchanged (verified by cross-assembling for
aarch64-linux before/after).
2. Apple Silicon enforces W^X. The code cache is now allocated with
MAP_JIT at a dynamic address (static memory cannot be made
executable on this platform), and every code path that writes to
the cache is bracketed with pthread_jit_write_protect_np() via
reentrant jit_write_begin/end helpers. The bracket points are the
five C entry points all cache writes funnel through
(new_recompile_block, dynamic_linker{,_ds}, invalidate_block,
invalidate_all_pages) plus the arch_init trampoline setup; the
assembly stubs need no changes because exec mode is the default.
3. cache_flush() reads ctr_el0, which traps (SIGILL) from EL0 on
Apple Silicon. Use the system-provided sys_icache_invalidate()
instead. This was the cause of the "continuous SIGILLs" reported
when compiling the dynarec on M-series machines.
Tested on an M-series Mac (macOS 26.5): two commercial ROMs run on
the dynarec under the stock mupen64plus 2.6.0 frontend/plugins, with
generated code confirmed executing from the MAP_JIT region.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jeremyw
force-pushed
the
darwin-arm64-dynarec
branch
from
July 19, 2026 01:33
312e6b4 to
3af70b7
Compare
jeremyw
marked this pull request as ready for review
July 19, 2026 17:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem: on Apple Silicon Macs, mupen64plus falls back to the cached interpreter — the arm64 dynarec has been hard-disabled on macOS (
NO_ASM = 1) since native arm64 support landed in #910. So the machines that could benefit most from the recompiler are the ones that don't get it. The dynarec itself is fine on arm64 (it works on Linux); the blockers were all macOS-specific.Prior art: #910 got the core building natively on Apple Silicon but had to disable the dynarec to do it. #831 (@MaddTheSane, 2021) tried to close the gap using gas-preprocessor to translate the ELF assembly syntax, but hit "continuous SIGILLs" at runtime and stalled. This PR picks that effort back up. It turns out no extra tooling is needed — clang's integrated assembler handles
linkage_arm64.Sfine once the syntax differences are handled with an#ifdef— and the SIGILLs had a specific, fixable cause (see below).Three separate problems needed solving:
Assembler syntax. I added an
#ifdef __APPLE__variant of the symbol macros inlinkage_arm64.S: leading underscores on C symbols,.private_externin place of.hidden/.type/.size, and@PAGE/@PAGEOFFinstead of:lo12:. One gotcha worth writing down: the Darwin arm64 assembler treats;as a comment character (the statement separator is%%), so multi-statement macros silently drop everything after the first statement. I suspect that's why earlier attempts ended up with undefined symbols. I cross-assembled for aarch64-linux to confirm the ELF output is unchanged.W^X. Apple Silicon refuses RWX mappings and won't execute unsigned shared memory, so neither
FIXED_CACHE_ADDRnorDOUBLE_CACHE_ADDRcan work — I tried both. Apple builds now useDYNAMIC_CACHE_ADDRwithMAP_JIT, plus small reentrantjit_write_begin/endhelpers that togglepthread_jit_write_protect_np()around the six C functions all cache writes go through (new_recompile_block,dynamic_linker{,_ds},invalidate_block,invalidate_all_pages,arch_init). The cache stays executable by default, so the asm stubs and the generated code itself didn't need any changes.Cache flush.
cache_flush()readsctr_el0, which traps from userspace on macOS. I believe this is the actual source of the "continuous SIGILLs" reported in #831, not the 16KB page size. Apple builds now callsys_icache_invalidate()instead.Tested on an M-series Mac (macOS 26.5) with a plain
make all: two commercial ROMs run under the stock 2.6.0 frontend and plugins with--emumode 2, and sampling the process shows it executing out of theMAP_JITregion, so it really is running recompiled code. The minimum macOS version for the arm64 build goes from 10.16 to 11.0, whichpthread_jit_write_protect_nprequires anyway.Everything is behind
__APPLE__/__aarch64__guards, so other platforms build exactly what they built before. Happy to adjust anything if a different approach is preferred.