Skip to content

Conversation

@darmie
Copy link

@darmie darmie commented Nov 24, 2025

Title

fix(jit): Add MAP_JIT support for 100% stable JIT execution on ARM64 macOS

Related Issue: #12076

Summary

This PR fixes non-deterministic JIT execution failures on Apple Silicon by implementing proper memory allocation and W^X mode handling required by the platform.

Before: ~56% success rate
After: 100% success rate (verified over 50+ consecutive runs)

Problem

JIT-compiled code on ARM64 macOS fails non-deterministically in multi-threaded scenarios. Symptoms include:

Failure Mode Frequency
SIGBUS on JIT function calls ~30%
Silent incorrect results ~10%
Segmentation fault ~4%

Root Cause

Apple Silicon enforces W^X (Write XOR Execute) at the hardware level:

  1. Memory must be allocated with MAP_JIT flag so the kernel can track it for W^X enforcement
  2. Threads must switch to execute mode via pthread_jit_write_protect_np(1) before calling JIT code

The current implementation:

  • Uses standard allocator (no MAP_JIT)
  • Doesn't call pthread_jit_write_protect_np()

Solution

cranelift/jit/src/memory/system.rs

Added an ARM64 macOS-specific PtrLen::with_size() implementation that uses mmap with the MAP_JIT flag (0x0800) instead of the standard allocator. This allows macOS to properly track the memory for W^X policy enforcement.

Also added a corresponding Drop implementation that uses munmap to deallocate the memory, since memory allocated with mmap cannot be freed with the standard allocator.

cranelift/jit/src/memory/mod.rs

After making memory executable in set_readable_and_executable(), added a call to pthread_jit_write_protect_np(1) to switch the current thread to execute mode. This is required by Apple's W^X enforcement - threads must explicitly opt into execute mode before running JIT code.

Testing

Test Script

#!/bin/bash
passed=0
failed=0

for i in {1..50}; do
    result=$(timeout 120 ./target/release/jit_test 2>&1)
    if echo "$result" | grep -q "All tests passed"; then
        echo "Run $i: PASSED"
        passed=$((passed+1))
    else
        echo "Run $i: FAILED"
        failed=$((failed+1))
    fi
done

echo ""
echo "=== RESULTS ==="
echo "Passed: $passed/50, Failed: $failed/50"
echo "Success rate: $((passed * 100 / 50))%"

Results

Tested with the Rayzor compiler's stdlib e2e test suite (50+ JIT-compiled runtime functions, multi-threaded):

Configuration Success Rate
Before fix (standard allocator) ~56% (28/50)
After fix (MAP_JIT + pthread_jit_write_protect_np) 100% (50/50)

Note: Simple standalone tests may not reliably reproduce this issue. The failure is non-deterministic and depends on timing, memory layout, and CPU core scheduling (P-core vs E-core).

Platform Impact

  • ARM64 macOS: Fixed (was broken)
  • x86_64 macOS: No change (not affected)
  • Linux (all arch): Adds Linux x86_64 mmap hint to keep JIT code within 2GB of
    runtime symbols for PC-relative relocations
  • Windows: No change (not affected)

All changes are gated behind #[cfg(all(target_arch = "aarch64", target_os = "macos"))].

Related Issues

@darmie darmie requested a review from a team as a code owner November 24, 2025 13:16
@darmie darmie requested review from abrown and removed request for a team November 24, 2025 13:16
@darmie darmie requested a review from a team as a code owner November 24, 2025 14:16
- Use mmap with MAP_JIT flag for JIT memory allocation
- Call pthread_jit_write_protect_np(1) to switch to execute mode
- Add sys_icache_invalidate for proper icache coherence
- Custom Drop using munmap for MAP_JIT memory

Fixes non-deterministic JIT execution failures on Apple Silicon.
@darmie darmie requested a review from a team as a code owner November 24, 2025 14:58
@darmie darmie requested review from bjorn3 and fitzgen and removed request for a team November 24, 2025 14:58
- Use libc::MAP_JIT instead of custom constant
- Add pthread_jit_write_protect_np(0) after mmap to enable write mode
- Change doc comments to regular comments for implementation details
- Add TODO comment about AArch64 icache reliance on mprotect
@darmie darmie requested a review from bjorn3 November 24, 2025 16:21
@github-actions github-actions bot added the cranelift Issues related to the Cranelift code generator label Nov 24, 2025
@alexcrichton
Copy link
Member

@bjorn3 are you willing to take on review here as an owner of the cranelift-jit crate? I've written up my thoughts here on how this would all affect Wasmtime but this doesn't actually touch wasmtime except for the jit-icache-coherence crate, so my comment there is only tangentially applicable.

fn pthread_jit_write_protect_np(enabled: libc::c_int);
}
unsafe {
pthread_jit_write_protect_np(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to scope those pthread_jit_write_protect_np calls to the actual section that does the writes. That would be more secure.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bjorn3 Done!

Address bjorn3 and cfallin's review feedback:

1. Drop icache changes: PR bytecodealliance#12133 already implements proper AArch64
   icache coherence upstream. Remove our sys_icache_invalidate addition
   from libc.rs - it will be picked up on rebase.

2. Scope pthread_jit_write_protect_np calls: Move the write-mode enable
   (pthread_jit_write_protect_np(0)) from allocate_readexec() into
   with_size() right after mmap. Write mode
   is only enabled when new MAP_JIT pages are actually allocated, not
   on every bump-pointer allocation request.

3. MAP_JIT only for executable pages: Add executable bool parameter to
   with_size() and Memory. MAP_JIT flag is only passed for code pages
   (executable=true), not for readonly or writable data pages.

Also adds Linux x86_64 mmap hint to keep JIT code within 2GB of
runtime symbols for PC-relative relocations.
@darmie darmie requested a review from bjorn3 January 27, 2026 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cranelift Issues related to the Cranelift code generator

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cranelift: Non-deterministic JIT execution failures on ARM64 macOS

4 participants