Skip to content

Commit 312e6b4

Browse files
jeremywclaude
andcommitted
new_dynarec: native Apple Silicon (darwin-arm64) support
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>
1 parent 6dca4c1 commit 312e6b4

4 files changed

Lines changed: 140 additions & 19 deletions

File tree

projects/unix/Makefile

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,7 @@ ifeq ($(OS), OSX)
246246
endif
247247
endif
248248
ifeq ($(CPU), ARM)
249-
# assembly compilation not yet supported on macOS with Apple Silicon
250-
NO_ASM = 1
251-
CFLAGS += -pipe -arch arm64 -mmacosx-version-min=10.16 -isysroot $(OSX_SDK_PATH)
249+
CFLAGS += -pipe -arch arm64 -mmacosx-version-min=11.0 -isysroot $(OSX_SDK_PATH)
252250
endif
253251
endif
254252
ifeq ($(OS), MINGW)

src/device/r4300/new_dynarec/arm64/assem_arm64.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,17 @@ static uintptr_t jump_table_symbols[] = {
261261
(intptr_t)breakpoint
262262
};
263263

264+
#if defined(__APPLE__)
265+
#include <libkern/OSCacheControl.h>
266+
#endif
267+
264268
static void cache_flush(char* start, char* end)
265269
{
266-
#ifndef WIN32
270+
#if defined(__APPLE__)
271+
// Reading ctr_el0 from EL0 traps (SIGILL) on Apple Silicon; use the
272+
// system-provided cache maintenance routine instead.
273+
sys_icache_invalidate(start, end - start);
274+
#elif !defined(WIN32)
267275
// Don't rely on GCC's __clear_cache implementation, as it caches
268276
// icache/dcache cache line sizes, that can vary between cores on
269277
// big.LITTLE architectures.
@@ -4639,6 +4647,7 @@ static void arch_init(void) {
46394647

46404648
// Trampolines for jumps >128MB
46414649
intptr_t *ptr,*ptr2,*ptr3;
4650+
jit_write_begin();
46424651
ptr=(intptr_t *)jump_table_symbols;
46434652
ptr2=(intptr_t *)((char *)base_addr+(1<<TARGET_SIZE_2)-JUMP_TABLE_SIZE);
46444653
ptr3=(intptr_t *)((char *)base_addr_rx+(1<<TARGET_SIZE_2)-JUMP_TABLE_SIZE);
@@ -4658,4 +4667,5 @@ static void arch_init(void) {
46584667
ptr2++;
46594668
ptr3+=2;
46604669
}
4670+
jit_write_end();
46614671
}

src/device/r4300/new_dynarec/arm64/linkage_arm64.S

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,47 @@
1818
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
1919
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020

21+
#ifdef __APPLE__
22+
23+
/* Mach-O: C symbols carry a leading underscore, .hidden/.type/.size are
24+
* ELF-only (visibility is .private_extern), and adrp page relocations are
25+
* spelled sym@PAGE/sym@PAGEOFF instead of sym/:lo12:sym. The Darwin arm64
26+
* assembler treats ';' as a comment, so multi-statement macros use the '%%'
27+
* statement separator instead.
28+
* Each function gets two labels: _name for linkage with C code and a plain
29+
* name for branches within this file. */
30+
31+
#define GLOBAL_FUNCTION(name) \
32+
.globl _##name %% \
33+
.private_extern _##name %% \
34+
_##name: name
35+
36+
#define LOCAL_FUNCTION(name) \
37+
name
38+
39+
#define GLOBAL_VARIABLE(name, size_) \
40+
.globl _##name %% \
41+
.private_extern _##name
42+
43+
#define PAGE(sym) sym@PAGE
44+
#define PAGEOFF(sym) sym@PAGEOFF
45+
46+
/* C symbols referenced from this file */
47+
#define g_dev _g_dev
48+
#define base_addr_rx _base_addr_rx
49+
#define get_addr_ht _get_addr_ht
50+
#define get_addr _get_addr
51+
#define verify_dirty _verify_dirty
52+
#define dynarec_gen_interrupt _dynarec_gen_interrupt
53+
#define cop1_unusable _cop1_unusable
54+
#define SYSCALL_new _SYSCALL_new
55+
#define ERET_new _ERET_new
56+
#define dynamic_linker _dynamic_linker
57+
#define dynamic_linker_ds _dynamic_linker_ds
58+
#define new_recompile_block _new_recompile_block
59+
60+
#else /* ELF */
61+
2162
#define GLOBAL_FUNCTION(name) \
2263
.globl name; \
2364
.hidden name; \
@@ -35,6 +76,11 @@
3576
.type name, %object; \
3677
.size name, size_
3778

79+
#define PAGE(sym) sym
80+
#define PAGEOFF(sym) :lo12:sym
81+
82+
#endif
83+
3884
.macro movl Wn, imm
3985
movz \Wn, (\imm >> 16) & 0xFFFF, lsl 16
4086
movk \Wn, \imm & 0xFFFF
@@ -171,10 +217,10 @@ GLOBAL_FUNCTION(verify_code):
171217
mov x21, x30 /* Save link register */
172218
bl verify_dirty
173219
tst x0, x0
174-
b.ne .D1
220+
b.ne 1f
175221
mov x30, x21 /* Restore link register */
176222
ret
177-
.D1:
223+
1:
178224
bl get_addr
179225
br x0
180226

@@ -190,9 +236,9 @@ GLOBAL_FUNCTION(cc_interrupt):
190236
tst w2, w2
191237
b.ne new_dyna_stop
192238
tst w1, w1
193-
b.ne .E1
239+
b.ne 1f
194240
ret
195-
.E1:
241+
1:
196242
ldr w0, [x29, #fp_pcaddr]
197243
bl get_addr_ht
198244
br x0
@@ -243,12 +289,12 @@ GLOBAL_FUNCTION(dyna_linker_ds):
243289
br x0
244290

245291
GLOBAL_FUNCTION(new_dyna_start):
246-
adrp x16, g_dev
247-
add x16, x16, :lo12:g_dev
292+
adrp x16, PAGE(g_dev)
293+
add x16, x16, PAGEOFF(g_dev)
248294
movl x1, (device_r4300_new_dynarec_hot_state_dynarec_local + saved_context)
249295
add x16, x16, x1
250-
adrp x1, base_addr_rx
251-
add x1, x1, :lo12:base_addr_rx
296+
adrp x1, PAGE(base_addr_rx)
297+
add x1, x1, PAGEOFF(base_addr_rx)
252298
mov w0, #0xa4000000
253299
stp x19,x20,[x16,#0]
254300
stp x21,x22,[x16,#16]

src/device/r4300/new_dynarec/new_dynarec.c

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,30 @@
5858
#include <sys/mman.h>
5959
#endif
6060

61+
#if defined(__APPLE__) && defined(__aarch64__)
62+
/* Apple Silicon enforces W^X: the MAP_JIT code cache is executable by
63+
* default and only writable while the calling thread has toggled itself
64+
* into write mode with pthread_jit_write_protect_np(). Exec mode is the
65+
* default; every code path that writes to the cache is bracketed with
66+
* jit_write_begin/jit_write_end. The depth counter makes the brackets
67+
* reentrant (e.g. dynamic_linker -> new_recompile_block). */
68+
#include <pthread.h>
69+
static int jit_write_depth;
70+
static void jit_write_begin(void)
71+
{
72+
if (jit_write_depth++ == 0)
73+
pthread_jit_write_protect_np(0);
74+
}
75+
static void jit_write_end(void)
76+
{
77+
if (--jit_write_depth == 0)
78+
pthread_jit_write_protect_np(1);
79+
}
80+
#else
81+
#define jit_write_begin() do {} while (0)
82+
#define jit_write_end() do {} while (0)
83+
#endif
84+
6185
#if defined(RECOMPILER_DEBUG) && !defined(RECOMP_DBG)
6286
void recomp_dbg_init(void);
6387
void recomp_dbg_cleanup(void);
@@ -2593,7 +2617,7 @@ static struct ll_entry *get_dirty(struct r4300_core* r4300,u_int vaddr,u_int fla
25932617
return NULL;
25942618
}
25952619

2596-
void *dynamic_linker(void * src, u_int vaddr)
2620+
static void *dynamic_linker_impl(void * src, u_int vaddr)
25972621
{
25982622
assert((vaddr&1)==0);
25992623
struct r4300_core* r4300 = &g_dev.r4300;
@@ -2643,7 +2667,7 @@ void *dynamic_linker(void * src, u_int vaddr)
26432667
}
26442668

26452669
int r=new_recompile_block(vaddr);
2646-
if(r==0) return dynamic_linker(src,vaddr);
2670+
if(r==0) return dynamic_linker_impl(src,vaddr);
26472671
// Execute in unmapped page, generate pagefault execption
26482672
assert(r4300->cp0.tlb.LUT_r[(vaddr&~1) >> 12] == 0);
26492673
assert((intptr_t)r4300->new_dynarec_hot_state.memory_map[(vaddr&~1) >> 12] < 0);
@@ -2652,7 +2676,15 @@ void *dynamic_linker(void * src, u_int vaddr)
26522676
return get_addr_ht(r4300->new_dynarec_hot_state.pcaddr);
26532677
}
26542678

2655-
void *dynamic_linker_ds(void * src, u_int vaddr)
2679+
void *dynamic_linker(void * src, u_int vaddr)
2680+
{
2681+
jit_write_begin();
2682+
void *ret = dynamic_linker_impl(src, vaddr);
2683+
jit_write_end();
2684+
return ret;
2685+
}
2686+
2687+
static void *dynamic_linker_ds_impl(void * src, u_int vaddr)
26562688
{
26572689
struct r4300_core* r4300 = &g_dev.r4300;
26582690
struct ll_entry *head;
@@ -2701,7 +2733,7 @@ void *dynamic_linker_ds(void * src, u_int vaddr)
27012733
}
27022734

27032735
int r=new_recompile_block((vaddr&0xFFFFFFF8)+1);
2704-
if(r==0) return dynamic_linker_ds(src,vaddr);
2736+
if(r==0) return dynamic_linker_ds_impl(src,vaddr);
27052737
// Execute in unmapped page, generate pagefault execption
27062738
assert(r4300->cp0.tlb.LUT_r[(vaddr&~1) >> 12] == 0);
27072739
assert((intptr_t)r4300->new_dynarec_hot_state.memory_map[(vaddr&~1) >> 12] < 0);
@@ -2710,6 +2742,14 @@ void *dynamic_linker_ds(void * src, u_int vaddr)
27102742
return get_addr_ht(r4300->new_dynarec_hot_state.pcaddr);
27112743
}
27122744

2745+
void *dynamic_linker_ds(void * src, u_int vaddr)
2746+
{
2747+
jit_write_begin();
2748+
void *ret = dynamic_linker_ds_impl(src, vaddr);
2749+
jit_write_end();
2750+
return ret;
2751+
}
2752+
27132753
// Get address from virtual address
27142754
// This is called from the recompiled JR/JALR instructions
27152755
void *get_addr(u_int vaddr)
@@ -2873,7 +2913,7 @@ static void invalidate_page(u_int page)
28732913
}
28742914
}
28752915

2876-
void invalidate_block(u_int block)
2916+
static void invalidate_block_impl(u_int block)
28772917
{
28782918
u_int page;
28792919
page=block^0x80000;
@@ -2950,11 +2990,19 @@ void invalidate_block(u_int block)
29502990
#endif
29512991
}
29522992

2993+
void invalidate_block(u_int block)
2994+
{
2995+
jit_write_begin();
2996+
invalidate_block_impl(block);
2997+
jit_write_end();
2998+
}
2999+
29533000
// This is called when loading a save state.
29543001
// Anything could have changed, so invalidate everything.
29553002
static void invalidate_all_pages(void)
29563003
{
29573004
u_int page;
3005+
jit_write_begin();
29583006
for(page=0;page<4096;page++)
29593007
invalidate_page(page);
29603008
for(page=0;page<1048576;page++)
@@ -2967,6 +3015,7 @@ static void invalidate_all_pages(void)
29673015
#if NEW_DYNAREC >= NEW_DYNAREC_ARM
29683016
cache_flush((char *)base_addr_rx,(char *)base_addr_rx+(1<<TARGET_SIZE_2));
29693017
#endif
3018+
jit_write_end();
29703019
#ifdef USE_MINI_HT
29713020
memset(g_dev.r4300.new_dynarec_hot_state.mini_ht,-1,sizeof(g_dev.r4300.new_dynarec_hot_state.mini_ht));
29723021
#endif
@@ -8713,7 +8762,13 @@ void new_dynarec_init(void)
87138762
#define DOUBLE_CACHE_ADDR 3 // Put the dynarec cache at random address with RW address != RX address
87148763

87158764
// Default to fixed cache address
8765+
#if defined(__APPLE__)
8766+
// Apple Silicon requires a fresh MAP_JIT mapping for the code cache
8767+
// (static memory cannot be made executable), so the cache address is dynamic.
8768+
#define CACHE_ADDR DYNAMIC_CACHE_ADDR
8769+
#else
87168770
#define CACHE_ADDR FIXED_CACHE_ADDR
8771+
#endif
87178772

87188773
#if CACHE_ADDR==DOUBLE_CACHE_ADDR
87198774
#include <unistd.h>
@@ -8744,7 +8799,11 @@ void new_dynarec_init(void)
87448799
#else /*DYNAMIC_CACHE_ADDR*/
87458800
base_addr = mmap (NULL, 1<<TARGET_SIZE_2,
87468801
PROT_READ | PROT_WRITE | PROT_EXEC,
8747-
MAP_PRIVATE | MAP_ANONYMOUS,
8802+
MAP_PRIVATE | MAP_ANONYMOUS
8803+
#if defined(__APPLE__) && defined(__aarch64__)
8804+
| MAP_JIT
8805+
#endif
8806+
,
87488807
-1, 0);
87498808
base_addr_rx = base_addr;
87508809
#endif
@@ -8830,7 +8889,7 @@ void new_dynarec_cleanup(void)
88308889
#endif
88318890
}
88328891

8833-
int new_recompile_block(int addr)
8892+
static int new_recompile_block_impl(int addr)
88348893
{
88358894
#if defined(RECOMPILER_DEBUG) && !defined(RECOMP_DBG)
88368895
recomp_dbg_block(addr);
@@ -11962,3 +12021,11 @@ int new_recompile_block(int addr)
1196212021
}
1196312022
return 0;
1196412023
}
12024+
12025+
int new_recompile_block(int addr)
12026+
{
12027+
jit_write_begin();
12028+
int ret = new_recompile_block_impl(addr);
12029+
jit_write_end();
12030+
return ret;
12031+
}

0 commit comments

Comments
 (0)