@@ -49,7 +49,45 @@ impl PtrLen {
4949 } )
5050 }
5151
52- #[ cfg( all( not( target_os = "windows" ) , not( feature = "selinux-fix" ) ) ) ]
52+ /// macOS ARM64: Allocate JIT memory using mmap with MAP_JIT flag.
53+ ///
54+ /// PROBLEM: Without MAP_JIT, JIT execution on Apple Silicon fails ~44% of the time.
55+ /// Standard allocators don't set MAP_JIT, causing non-deterministic crashes when
56+ /// threads execute JIT-compiled code.
57+ ///
58+ /// SOLUTION: Use mmap directly with MAP_JIT (0x0800) to allocate memory that will
59+ /// become executable. This allows macOS to properly track the memory for W^X
60+ /// (Write XOR Execute) policy enforcement via pthread_jit_write_protect_np.
61+ #[ cfg( all( target_arch = "aarch64" , target_os = "macos" , not( feature = "selinux-fix" ) ) ) ]
62+ fn with_size ( size : usize ) -> io:: Result < Self > {
63+ assert_ne ! ( size, 0 ) ;
64+ let alloc_size = region:: page:: ceil ( size as * const ( ) ) as usize ;
65+
66+ const MAP_JIT : libc:: c_int = 0x0800 ;
67+
68+ let ptr = unsafe {
69+ libc:: mmap (
70+ ptr:: null_mut ( ) ,
71+ alloc_size,
72+ libc:: PROT_READ | libc:: PROT_WRITE ,
73+ libc:: MAP_PRIVATE | libc:: MAP_ANON | MAP_JIT ,
74+ -1 ,
75+ 0 ,
76+ )
77+ } ;
78+
79+ if ptr == libc:: MAP_FAILED {
80+ Err ( io:: Error :: last_os_error ( ) )
81+ } else {
82+ Ok ( Self {
83+ ptr : ptr as * mut u8 ,
84+ len : alloc_size,
85+ } )
86+ }
87+ }
88+
89+ /// Non-macOS ARM64: Use standard allocator
90+ #[ cfg( all( not( target_os = "windows" ) , not( feature = "selinux-fix" ) , not( all( target_arch = "aarch64" , target_os = "macos" ) ) ) ) ]
5391 fn with_size ( size : usize ) -> io:: Result < Self > {
5492 assert_ne ! ( size, 0 ) ;
5593 let page_size = region:: page:: size ( ) ;
@@ -95,7 +133,26 @@ impl PtrLen {
95133}
96134
97135// `MMapMut` from `cfg(feature = "selinux-fix")` already deallocates properly.
98- #[ cfg( all( not( target_os = "windows" ) , not( feature = "selinux-fix" ) ) ) ]
136+
137+ /// macOS ARM64: Deallocate MAP_JIT memory using munmap.
138+ ///
139+ /// Memory allocated with mmap+MAP_JIT must be freed with munmap, not the
140+ /// standard allocator. We also reset protection to RW before unmapping
141+ /// to avoid potential issues with protected memory.
142+ #[ cfg( all( target_arch = "aarch64" , target_os = "macos" , not( feature = "selinux-fix" ) ) ) ]
143+ impl Drop for PtrLen {
144+ fn drop ( & mut self ) {
145+ if !self . ptr . is_null ( ) {
146+ unsafe {
147+ let _ = region:: protect ( self . ptr , self . len , region:: Protection :: READ_WRITE ) ;
148+ libc:: munmap ( self . ptr as * mut libc:: c_void , self . len ) ;
149+ }
150+ }
151+ }
152+ }
153+
154+ /// Other Unix platforms: Use standard allocator dealloc.
155+ #[ cfg( all( not( target_os = "windows" ) , not( feature = "selinux-fix" ) , not( all( target_arch = "aarch64" , target_os = "macos" ) ) ) ) ]
99156impl Drop for PtrLen {
100157 fn drop ( & mut self ) {
101158 if !self . ptr . is_null ( ) {
@@ -178,6 +235,19 @@ impl Memory {
178235 // Flush any in-flight instructions from the pipeline
179236 wasmtime_jit_icache_coherence:: pipeline_flush_mt ( ) . expect ( "Failed pipeline flush" ) ;
180237
238+ // ARM64 macOS: Final memory barriers after all code regions are executable.
239+ //
240+ // PROBLEM: Without barriers, CPUs may execute stale icache entries, causing
241+ // crashes or wrong results. Apple Silicon has independent instruction caches
242+ // per core (P-cores and E-cores).
243+ //
244+ // SOLUTION: DSB SY ensures all cache ops complete; ISB SY flushes the pipeline.
245+ #[ cfg( all( target_arch = "aarch64" , target_os = "macos" ) ) ]
246+ unsafe {
247+ std:: arch:: asm!( "dsb sy" , options( nostack, preserves_flags) ) ;
248+ std:: arch:: asm!( "isb sy" , options( nostack, preserves_flags) ) ;
249+ }
250+
181251 self . already_protected = self . allocations . len ( ) ;
182252 Ok ( ( ) )
183253 }
0 commit comments