Enable DBT JIT on Apple Silicon

The DBT design (docs/DBT-PORTABILITY.md) anticipated Apple AArch64 as a
target sharing AAPCS64 with Linux, with only the JIT memory model
differing — and dbt_jit_mem.h already abstracted that difference
(MAP_JIT, pthread_jit_write_protect_np, sys_icache_invalidate). The two
remaining gaps were: (1) configure.ac explicitly errored on
darwin+aarch64, and (2) every code-write region had a matching
jit_write_end (via dbt_flush_code) but no jit_write_begin to flip JIT
pages out of execute-only mode under Apple's strict W^X.

- mux/configure.ac (and the corresponding hunk hand-applied to
  mux/configure to avoid an autoconf 2.71->2.73 rewrite of the whole
  generated file): drop the AArch64-darwin error and fall through to
  dbt_a64_sysv.

- mux/modules/engine/dbt.cpp: bracket every leaf code-write region with
  jit_write_begin(). Specifically, dbt_init's trampoline emit, both
  branches of dbt_reset (blob NOP-pad and full-reset trampoline emit),
  the per-iteration translate inside dbt_pretranslate (per-iter to
  avoid nesting under that function's recursion), the translate paths
  in dbt_run and dbt_resume, and the backpatch_jmp loop in
  dbt_resolve_chains. The conditional flush in resolve_chains becomes
  unconditional so begin/end pairs stay balanced when no patches
  resolved. Also drop the now-redundant final dbt_flush_code at the
  end of dbt_pretranslate (each iteration self-flushes).

On non-Apple platforms jit_write_begin compiles to a no-op, so this is
zero-cost everywhere except Apple Silicon, where it adds one per-thread
write-protect toggle per write region (cheap MSR write).

Verified: make install with --enable-jit on aarch64-apple-darwin selects
DBT_BACKEND=dbt_a64_sysv, builds dbt.eo + dbt_a64_sysv.eo + engine.so
cleanly, and netmux dlopens engine.so without unresolved symbols.
Actual JIT execution under load (smoke tests) still TODO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stephen Dennis 2026-05-02 12:52:30 -06:00
parent b5bdf36684
commit 2a1776e276
3 changed files with 22 additions and 16 deletions

5
mux/configure vendored
View file

@ -6712,10 +6712,7 @@ printf %s "checking DBT backend for host platform... " >&6; }
DBT_BACKEND="dbt_x64_sysv" ;;
esac ;;
aarch64|arm64)
case "$host_os" in
darwin*) as_fn_error $? "JIT not yet implemented for AArch64 macOS (Apple W^X backend needed)" "$LINENO" 5 ;;
*) DBT_BACKEND="dbt_a64_sysv" ;;
esac ;;
DBT_BACKEND="dbt_a64_sysv" ;;
riscv64)
as_fn_error $? "JIT not yet implemented for native RV64 (backend needed)" "$LINENO" 5 ;;
*)

View file

@ -242,10 +242,7 @@ if test "x$jit_enabled" = "xyes"; then
DBT_BACKEND="dbt_x64_sysv" ;;
esac ;;
aarch64|arm64)
case "$host_os" in
darwin*) AC_MSG_ERROR([JIT not yet implemented for AArch64 macOS (Apple W^X backend needed)]) ;;
*) DBT_BACKEND="dbt_a64_sysv" ;;
esac ;;
DBT_BACKEND="dbt_a64_sysv" ;;
riscv64)
AC_MSG_ERROR([JIT not yet implemented for native RV64 (backend needed)]) ;;
*)

View file

@ -192,6 +192,7 @@ int dbt_init(dbt_state_t *dbt, uint8_t *memory, size_t memory_size,
}
// Emit trampoline at the start of the code buffer.
jit_write_begin();
dbt_backend_emit_trampoline(dbt);
dbt_flush_code(dbt, 0);
@ -225,6 +226,7 @@ void dbt_reset(dbt_state_t *dbt, uint8_t *memory, size_t memory_size,
// program code. On x86-64, each NOP is 1 byte (0x90).
// On AArch64, NOPs are 4 bytes so N is rounded up to a
// multiple of 4.
jit_write_begin();
{
static int pad = -1;
if (pad < 0) {
@ -262,6 +264,7 @@ void dbt_reset(dbt_state_t *dbt, uint8_t *memory, size_t memory_size,
dbt->patches.clear();
dbt->pending_patch_targets.clear();
dbt->code_used = 0;
jit_write_begin();
dbt_backend_emit_trampoline(dbt);
dbt->num_intrinsics = 0;
memset(dbt->intrinsics, 0, sizeof(dbt->intrinsics));
@ -409,16 +412,20 @@ void dbt_pretranslate(dbt_state_t *dbt, uint64_t guest_pc) {
}
// Translate if not already cached.
// Brackets are per-iteration so dbt_pretranslate's recursion
// (function-call discovery) doesn't nest write-mode toggles.
if (!dbt_cache_lookup(dbt, pc)) {
jit_write_begin();
uint8_t *code = dbt_backend_translate_block(dbt, pc);
if (!code) continue;
if (!code) {
dbt_flush_code(dbt, dbt->code_used);
continue;
}
dbt_cache_insert(dbt, pc, code);
dbt_backpatch_chains(dbt, pc, code);
dbt_flush_code(dbt, static_cast<uint32_t>(code - dbt->code_buf));
}
}
// Flush I-cache for all code generated during blob pretranslation.
dbt_flush_code(dbt, 0);
}
void dbt_resolve_chains(dbt_state_t *dbt) {
@ -431,6 +438,7 @@ void dbt_resolve_chains(dbt_state_t *dbt) {
uint32_t resolved = 0;
uint32_t already_ok = 0;
uint32_t unresolvable = 0;
jit_write_begin();
for (size_t i = 0; i < dbt->patches.size(); i++) {
uint64_t target = dbt->patches[i].target_pc;
if (target == 0) continue;
@ -465,10 +473,10 @@ void dbt_resolve_chains(dbt_state_t *dbt) {
resolved, already_ok, unresolvable,
static_cast<unsigned>(dbt->patches.size()));
// Flush I-cache for any backpatched JMP targets.
if (resolved > 0) {
dbt_flush_code(dbt, 0);
}
// Flush I-cache for any backpatched JMP targets, and balance the
// jit_write_begin above (always required so write protection is
// restored on Apple Silicon even when no patches resolved).
dbt_flush_code(dbt, resolved > 0 ? 0 : dbt->code_used);
}
int dbt_run(dbt_state_t *dbt, uint64_t entry_pc, uint64_t stack_top) {
@ -530,8 +538,10 @@ int dbt_run(dbt_state_t *dbt, uint64_t entry_pc, uint64_t stack_top) {
static_cast<unsigned long long>(pc));
}
} else {
jit_write_begin();
code = dbt_backend_translate_block(dbt, pc);
if (!code) {
dbt_flush_code(dbt, dbt->code_used);
dbt->dispatch_count = dispatch_count;
return -1; // code buffer full
}
@ -613,8 +623,10 @@ int dbt_resume(dbt_state_t *dbt, uint64_t entry_pc) {
static_cast<unsigned long long>(pc));
}
} else {
jit_write_begin();
code = dbt_backend_translate_block(dbt, pc);
if (!code) {
dbt_flush_code(dbt, dbt->code_used);
dbt->dispatch_count = dispatch_count;
return -1; // code buffer full
}