From 730ef63cd586fe0bb99aa75668bb30db99bdf210 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 04:59:49 +0000 Subject: [PATCH 1/4] xtajit64: batch Unicorn context transfers --- .../provider_tests/run_unixlib_concurrency.sh | 1 + dlls/xtajit64/unixlib.c | 80 ++++++++++++------- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/dlls/xtajit64/provider_tests/run_unixlib_concurrency.sh b/dlls/xtajit64/provider_tests/run_unixlib_concurrency.sh index 21c4a67326a5..957cf019ff30 100755 --- a/dlls/xtajit64/provider_tests/run_unixlib_concurrency.sh +++ b/dlls/xtajit64/provider_tests/run_unixlib_concurrency.sh @@ -69,6 +69,7 @@ fi native_imports=$(/usr/bin/nm -u "$native_provider") || exit 1 for symbol in uc_open uc_emu_start uc_hook_add uc_mem_map_ptr \ uc_context_alloc uc_context_save uc_context_restore uc_context_free \ + uc_reg_write_batch uc_reg_read_batch \ uc_emu_stop_at_instruction_boundary uc_enable_shared_memory_atomics \ uc_set_shared_memory_atomic_callback; do if ! grep -Eq "(^|[[:space:]])_?${symbol}$" <<<"$native_imports"; then diff --git a/dlls/xtajit64/unixlib.c b/dlls/xtajit64/unixlib.c index 0b72fab02346..d86da7910d7a 100644 --- a/dlls/xtajit64/unixlib.c +++ b/dlls/xtajit64/unixlib.c @@ -616,23 +616,47 @@ static atomic_int test_check_context_read_lock; static atomic_int test_context_read_lock_violation; #endif -static const int integer_regs[] = +enum +{ + XTAJIT64_CONTEXT_INTEGER_REG_COUNT = 18, + XTAJIT64_CONTEXT_XMM_REG_COUNT = 16, +}; + +static const int context_write_regs[] = { UC_X86_REG_RAX, UC_X86_REG_RBX, UC_X86_REG_RCX, UC_X86_REG_RDX, UC_X86_REG_RSI, UC_X86_REG_RDI, UC_X86_REG_RBP, UC_X86_REG_RSP, UC_X86_REG_R8, UC_X86_REG_R9, UC_X86_REG_R10, UC_X86_REG_R11, UC_X86_REG_R12, UC_X86_REG_R13, UC_X86_REG_R14, UC_X86_REG_R15, - UC_X86_REG_RIP, UC_X86_REG_EFLAGS, + UC_X86_REG_RIP, UC_X86_REG_EFLAGS, UC_X86_REG_GS_BASE, UC_X86_REG_MXCSR, + UC_X86_REG_XMM0, UC_X86_REG_XMM1, UC_X86_REG_XMM2, UC_X86_REG_XMM3, + UC_X86_REG_XMM4, UC_X86_REG_XMM5, UC_X86_REG_XMM6, UC_X86_REG_XMM7, + UC_X86_REG_XMM8, UC_X86_REG_XMM9, UC_X86_REG_XMM10, UC_X86_REG_XMM11, + UC_X86_REG_XMM12, UC_X86_REG_XMM13, UC_X86_REG_XMM14, UC_X86_REG_XMM15, }; -static const int xmm_regs[] = +static const int context_read_regs[] = { + UC_X86_REG_RAX, UC_X86_REG_RBX, UC_X86_REG_RCX, UC_X86_REG_RDX, + UC_X86_REG_RSI, UC_X86_REG_RDI, UC_X86_REG_RBP, UC_X86_REG_RSP, + UC_X86_REG_R8, UC_X86_REG_R9, UC_X86_REG_R10, UC_X86_REG_R11, + UC_X86_REG_R12, UC_X86_REG_R13, UC_X86_REG_R14, UC_X86_REG_R15, + UC_X86_REG_RIP, UC_X86_REG_EFLAGS, UC_X86_REG_MXCSR, UC_X86_REG_XMM0, UC_X86_REG_XMM1, UC_X86_REG_XMM2, UC_X86_REG_XMM3, UC_X86_REG_XMM4, UC_X86_REG_XMM5, UC_X86_REG_XMM6, UC_X86_REG_XMM7, UC_X86_REG_XMM8, UC_X86_REG_XMM9, UC_X86_REG_XMM10, UC_X86_REG_XMM11, UC_X86_REG_XMM12, UC_X86_REG_XMM13, UC_X86_REG_XMM14, UC_X86_REG_XMM15, }; +C_ASSERT( offsetof(struct xtajit64_x64_context, mxcsr) == + XTAJIT64_CONTEXT_INTEGER_REG_COUNT * sizeof(UINT64) ); +C_ASSERT( ARRAY_SIZE(((struct xtajit64_x64_context *)0)->xmm) == + XTAJIT64_CONTEXT_XMM_REG_COUNT ); +C_ASSERT( ARRAY_SIZE(context_write_regs) == + XTAJIT64_CONTEXT_INTEGER_REG_COUNT + XTAJIT64_CONTEXT_XMM_REG_COUNT + 2 ); +C_ASSERT( ARRAY_SIZE(context_read_regs) == + XTAJIT64_CONTEXT_INTEGER_REG_COUNT + XTAJIT64_CONTEXT_XMM_REG_COUNT + 1 ); + static uint64_t align_down( uint64_t value ) { return value & ~(uint64_t)(XTAJIT64_GUEST_PAGE_SIZE - 1); @@ -1892,32 +1916,31 @@ static uc_err write_context( struct thread_engine *engine, const struct xtajit64_x64_context *context, uint64_t gs_base ) { - const UINT64 *values = &context->rax; - uc_err err; - unsigned int i; + const UINT64 *integer_values = &context->rax; + void *values[ARRAY_SIZE(context_write_regs)]; + unsigned int i, index = 0; #ifdef XTAJIT64_UNIXLIB_TEST atomic_fetch_add_explicit( &test_context_write_count, 1, memory_order_relaxed ); #endif - for (i = 0; i < ARRAY_SIZE(integer_regs); ++i) - if ((err = uc_reg_write( engine->uc, integer_regs[i], &values[i] )) != UC_ERR_OK) - return err; - if ((err = uc_reg_write( engine->uc, UC_X86_REG_GS_BASE, &gs_base )) != UC_ERR_OK) - return err; - if ((err = uc_reg_write( engine->uc, UC_X86_REG_MXCSR, &context->mxcsr )) != UC_ERR_OK) - return err; - for (i = 0; i < ARRAY_SIZE(xmm_regs); ++i) - if ((err = uc_reg_write( engine->uc, xmm_regs[i], context->xmm[i] )) != UC_ERR_OK) - return err; - return UC_ERR_OK; + /* One batch keeps Unicorn's JIT-state and register-dispatch setup outside + * the 36-register loop instead of crossing the dylib boundary 36 times. */ + for (i = 0; i < XTAJIT64_CONTEXT_INTEGER_REG_COUNT; ++i) + values[index++] = (void *)&integer_values[i]; + values[index++] = &gs_base; + values[index++] = (void *)&context->mxcsr; + for (i = 0; i < XTAJIT64_CONTEXT_XMM_REG_COUNT; ++i) + values[index++] = (void *)context->xmm[i]; + return uc_reg_write_batch( engine->uc, context_write_regs, values, + (int)ARRAY_SIZE(context_write_regs) ); } static uc_err read_context( struct thread_engine *engine, struct xtajit64_x64_context *context ) { - UINT64 *values = &context->rax; - uc_err err; - unsigned int i; + UINT64 *integer_values = &context->rax; + void *values[ARRAY_SIZE(context_read_regs)]; + unsigned int i, index = 0; #ifdef XTAJIT64_UNIXLIB_TEST atomic_fetch_add_explicit( &test_context_read_count, 1, memory_order_relaxed ); @@ -1936,15 +1959,14 @@ static uc_err read_context( struct thread_engine *engine, memory_order_release ); } #endif - for (i = 0; i < ARRAY_SIZE(integer_regs); ++i) - if ((err = uc_reg_read( engine->uc, integer_regs[i], &values[i] )) != UC_ERR_OK) - return err; - if ((err = uc_reg_read( engine->uc, UC_X86_REG_MXCSR, &context->mxcsr )) != UC_ERR_OK) - return err; - for (i = 0; i < ARRAY_SIZE(xmm_regs); ++i) - if ((err = uc_reg_read( engine->uc, xmm_regs[i], context->xmm[i] )) != UC_ERR_OK) - return err; - return UC_ERR_OK; + /* Export the 35 normalized registers through one Unicorn API boundary. */ + for (i = 0; i < XTAJIT64_CONTEXT_INTEGER_REG_COUNT; ++i) + values[index++] = &integer_values[i]; + values[index++] = &context->mxcsr; + for (i = 0; i < XTAJIT64_CONTEXT_XMM_REG_COUNT; ++i) + values[index++] = context->xmm[i]; + return uc_reg_read_batch( engine->uc, context_read_regs, values, + (int)ARRAY_SIZE(context_read_regs) ); } static uc_err prepare_x64_syscall_engine( struct thread_engine *engine, From 7cd8af8bb8cc498b0fc17316bb47fcfd52ee502f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 04:59:49 +0000 Subject: [PATCH 2/4] test: add Apple Silicon context-transfer benchmark --- .../provider_tests/apple_silicon_hotpaths.c | 316 ++++++++++++++++++ .../check_apple_silicon_hotpaths.py | 63 ++++ .../run_apple_silicon_hotpaths.sh | 72 ++++ 3 files changed, 451 insertions(+) create mode 100644 dlls/xtajit64/provider_tests/apple_silicon_hotpaths.c create mode 100755 dlls/xtajit64/provider_tests/check_apple_silicon_hotpaths.py create mode 100755 dlls/xtajit64/provider_tests/run_apple_silicon_hotpaths.sh diff --git a/dlls/xtajit64/provider_tests/apple_silicon_hotpaths.c b/dlls/xtajit64/provider_tests/apple_silicon_hotpaths.c new file mode 100644 index 000000000000..150d3ff59ab0 --- /dev/null +++ b/dlls/xtajit64/provider_tests/apple_silicon_hotpaths.c @@ -0,0 +1,316 @@ +/* + * Apple Silicon nanosecond benchmark for the xtajit64 Unicorn + * context-transfer API boundary. + * + * The benchmark first verifies scalar and batch register-transfer semantics, + * then reports the median cost of a full write/read boundary. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define ROUNDS 9 +#define CONTEXT_ITERATIONS 12000u + +struct x64_context +{ + uint64_t rax, rbx, rcx, rdx; + uint64_t rsi, rdi, rbp, rsp; + uint64_t r8, r9, r10, r11; + uint64_t r12, r13, r14, r15; + uint64_t rip, eflags; + uint32_t mxcsr; + uint32_t reserved; + uint64_t xmm[16][2]; +}; + +static const int integer_regs[] = +{ + UC_X86_REG_RAX, UC_X86_REG_RBX, UC_X86_REG_RCX, UC_X86_REG_RDX, + UC_X86_REG_RSI, UC_X86_REG_RDI, UC_X86_REG_RBP, UC_X86_REG_RSP, + UC_X86_REG_R8, UC_X86_REG_R9, UC_X86_REG_R10, UC_X86_REG_R11, + UC_X86_REG_R12, UC_X86_REG_R13, UC_X86_REG_R14, UC_X86_REG_R15, + UC_X86_REG_RIP, UC_X86_REG_EFLAGS, +}; + +static const int xmm_regs[] = +{ + UC_X86_REG_XMM0, UC_X86_REG_XMM1, UC_X86_REG_XMM2, UC_X86_REG_XMM3, + UC_X86_REG_XMM4, UC_X86_REG_XMM5, UC_X86_REG_XMM6, UC_X86_REG_XMM7, + UC_X86_REG_XMM8, UC_X86_REG_XMM9, UC_X86_REG_XMM10, UC_X86_REG_XMM11, + UC_X86_REG_XMM12, UC_X86_REG_XMM13, UC_X86_REG_XMM14, UC_X86_REG_XMM15, +}; + +static const int context_write_regs[] = +{ + UC_X86_REG_RAX, UC_X86_REG_RBX, UC_X86_REG_RCX, UC_X86_REG_RDX, + UC_X86_REG_RSI, UC_X86_REG_RDI, UC_X86_REG_RBP, UC_X86_REG_RSP, + UC_X86_REG_R8, UC_X86_REG_R9, UC_X86_REG_R10, UC_X86_REG_R11, + UC_X86_REG_R12, UC_X86_REG_R13, UC_X86_REG_R14, UC_X86_REG_R15, + UC_X86_REG_RIP, UC_X86_REG_EFLAGS, UC_X86_REG_GS_BASE, UC_X86_REG_MXCSR, + UC_X86_REG_XMM0, UC_X86_REG_XMM1, UC_X86_REG_XMM2, UC_X86_REG_XMM3, + UC_X86_REG_XMM4, UC_X86_REG_XMM5, UC_X86_REG_XMM6, UC_X86_REG_XMM7, + UC_X86_REG_XMM8, UC_X86_REG_XMM9, UC_X86_REG_XMM10, UC_X86_REG_XMM11, + UC_X86_REG_XMM12, UC_X86_REG_XMM13, UC_X86_REG_XMM14, UC_X86_REG_XMM15, +}; + +static const int context_read_regs[] = +{ + UC_X86_REG_RAX, UC_X86_REG_RBX, UC_X86_REG_RCX, UC_X86_REG_RDX, + UC_X86_REG_RSI, UC_X86_REG_RDI, UC_X86_REG_RBP, UC_X86_REG_RSP, + UC_X86_REG_R8, UC_X86_REG_R9, UC_X86_REG_R10, UC_X86_REG_R11, + UC_X86_REG_R12, UC_X86_REG_R13, UC_X86_REG_R14, UC_X86_REG_R15, + UC_X86_REG_RIP, UC_X86_REG_EFLAGS, UC_X86_REG_MXCSR, + UC_X86_REG_XMM0, UC_X86_REG_XMM1, UC_X86_REG_XMM2, UC_X86_REG_XMM3, + UC_X86_REG_XMM4, UC_X86_REG_XMM5, UC_X86_REG_XMM6, UC_X86_REG_XMM7, + UC_X86_REG_XMM8, UC_X86_REG_XMM9, UC_X86_REG_XMM10, UC_X86_REG_XMM11, + UC_X86_REG_XMM12, UC_X86_REG_XMM13, UC_X86_REG_XMM14, UC_X86_REG_XMM15, +}; + +static volatile uint64_t benchmark_sink; +static mach_timebase_info_data_t timebase; + +static void die_unicorn(const char *operation, uc_err error) +{ + fprintf(stderr, "%s failed: %s\n", operation, uc_strerror(error)); + exit(1); +} + +static double elapsed_ns(uint64_t begin, uint64_t end) +{ + return (double)(end - begin) * timebase.numer / timebase.denom; +} + +static int compare_double(const void *left, const void *right) +{ + double a = *(const double *)left; + double b = *(const double *)right; + return (a > b) - (a < b); +} + +static double median(double values[ROUNDS]) +{ + qsort(values, ROUNDS, sizeof(values[0]), compare_double); + return values[ROUNDS / 2]; +} + +static uc_err scalar_write(uc_engine *uc, const struct x64_context *context, + uint64_t gs_base) +{ + const uint64_t *values = &context->rax; + uc_err error; + size_t i; + + for (i = 0; i < ARRAY_SIZE(integer_regs); ++i) + if ((error = uc_reg_write(uc, integer_regs[i], &values[i])) != UC_ERR_OK) + return error; + if ((error = uc_reg_write(uc, UC_X86_REG_GS_BASE, &gs_base)) != UC_ERR_OK) + return error; + if ((error = uc_reg_write(uc, UC_X86_REG_MXCSR, &context->mxcsr)) != UC_ERR_OK) + return error; + for (i = 0; i < ARRAY_SIZE(xmm_regs); ++i) + if ((error = uc_reg_write(uc, xmm_regs[i], context->xmm[i])) != UC_ERR_OK) + return error; + return UC_ERR_OK; +} + +static uc_err scalar_read(uc_engine *uc, struct x64_context *context) +{ + uint64_t *values = &context->rax; + uc_err error; + size_t i; + + for (i = 0; i < ARRAY_SIZE(integer_regs); ++i) + if ((error = uc_reg_read(uc, integer_regs[i], &values[i])) != UC_ERR_OK) + return error; + if ((error = uc_reg_read(uc, UC_X86_REG_MXCSR, &context->mxcsr)) != UC_ERR_OK) + return error; + for (i = 0; i < ARRAY_SIZE(xmm_regs); ++i) + if ((error = uc_reg_read(uc, xmm_regs[i], context->xmm[i])) != UC_ERR_OK) + return error; + return UC_ERR_OK; +} + +static uc_err batch_write(uc_engine *uc, const struct x64_context *context, + uint64_t gs_base) +{ + const uint64_t *integer_values = &context->rax; + void *values[ARRAY_SIZE(context_write_regs)]; + size_t i, index = 0; + + for (i = 0; i < ARRAY_SIZE(integer_regs); ++i) + values[index++] = (void *)&integer_values[i]; + values[index++] = &gs_base; + values[index++] = (void *)&context->mxcsr; + for (i = 0; i < ARRAY_SIZE(xmm_regs); ++i) + values[index++] = (void *)context->xmm[i]; + return uc_reg_write_batch(uc, context_write_regs, values, + (int)ARRAY_SIZE(context_write_regs)); +} + +static uc_err batch_read(uc_engine *uc, struct x64_context *context) +{ + uint64_t *integer_values = &context->rax; + void *values[ARRAY_SIZE(context_read_regs)]; + size_t i, index = 0; + + for (i = 0; i < ARRAY_SIZE(integer_regs); ++i) + values[index++] = &integer_values[i]; + values[index++] = &context->mxcsr; + for (i = 0; i < ARRAY_SIZE(xmm_regs); ++i) + values[index++] = context->xmm[i]; + return uc_reg_read_batch(uc, context_read_regs, values, + (int)ARRAY_SIZE(context_read_regs)); +} + +static void initialize_context(struct x64_context *context) +{ + uint64_t *values = &context->rax; + size_t i, j; + + memset(context, 0, sizeof(*context)); + for (i = 0; i < ARRAY_SIZE(integer_regs); ++i) + values[i] = UINT64_C(0x100000001) * (i + 1); + context->rsp = UINT64_C(0x0000000100004000); + context->rip = UINT64_C(0x0000000100001000); + context->eflags = 0x202; + context->mxcsr = 0x1f80; + for (i = 0; i < ARRAY_SIZE(context->xmm); ++i) + for (j = 0; j < ARRAY_SIZE(context->xmm[i]); ++j) + context->xmm[i][j] = + UINT64_C(0x9e3779b97f4a7c15) * (i * 2 + j + 1); +} + +static void validate_context_transfer(uc_engine *uc, + const struct x64_context *input, + uint64_t gs_base) +{ + struct x64_context scalar_state, batch_state; + uint64_t observed_gs = 0; + uc_err error; + + memset(&scalar_state, 0, sizeof(scalar_state)); + memset(&batch_state, 0, sizeof(batch_state)); + if ((error = scalar_write(uc, input, gs_base)) != UC_ERR_OK) + die_unicorn("scalar context write", error); + if ((error = batch_read(uc, &scalar_state)) != UC_ERR_OK) + die_unicorn("batch context read", error); + if ((error = batch_write(uc, input, gs_base)) != UC_ERR_OK) + die_unicorn("batch context write", error); + if ((error = scalar_read(uc, &batch_state)) != UC_ERR_OK) + die_unicorn("scalar context read", error); + if (memcmp(&scalar_state, &batch_state, sizeof(scalar_state))) + { + fprintf(stderr, "scalar and batch context state differ\n"); + exit(1); + } + if ((error = uc_reg_read(uc, UC_X86_REG_GS_BASE, &observed_gs)) != UC_ERR_OK) + die_unicorn("GS base read", error); + if (observed_gs != gs_base) + { + fprintf(stderr, "GS base mismatch: %#llx != %#llx\n", + (unsigned long long)observed_gs, + (unsigned long long)gs_base); + exit(1); + } +} + +static double benchmark_context(uc_engine *uc, + const struct x64_context *input, + uint64_t gs_base, bool batch) +{ + struct x64_context output; + double samples[ROUNDS]; + unsigned int round; + + for (round = 0; round < ROUNDS; ++round) + { + uint64_t begin = mach_continuous_time(); + unsigned int i; + uc_err error = UC_ERR_OK; + + for (i = 0; i < CONTEXT_ITERATIONS; ++i) + { + if (batch) + { + if ((error = batch_write(uc, input, gs_base)) != UC_ERR_OK || + (error = batch_read(uc, &output)) != UC_ERR_OK) + break; + } + else + { + if ((error = scalar_write(uc, input, gs_base)) != UC_ERR_OK || + (error = scalar_read(uc, &output)) != UC_ERR_OK) + break; + } + } + if (error != UC_ERR_OK) die_unicorn("context benchmark", error); + samples[round] = + elapsed_ns(begin, mach_continuous_time()) / CONTEXT_ITERATIONS; + benchmark_sink ^= output.rax ^ output.xmm[15][1]; + } + return median(samples); +} + +static void print_cpu_model(void) +{ + char model[256]; + size_t size = sizeof(model); + + if (sysctlbyname("machdep.cpu.brand_string", model, &size, NULL, 0) == 0) + printf("APPLE_SILICON_CPU model=%s\n", model); +} + +int main(void) +{ + struct x64_context context; + uint64_t gs_base = UINT64_C(0x0000000101000000); + double scalar_ns, batch_ns; + uc_engine *uc = NULL; + uc_err error; + size_t i; + + if (mach_timebase_info(&timebase) != KERN_SUCCESS) + { + fprintf(stderr, "mach_timebase_info failed\n"); + return 1; + } + print_cpu_model(); + + if ((error = uc_open(UC_ARCH_X86, UC_MODE_64, &uc)) != UC_ERR_OK) + die_unicorn("uc_open", error); + initialize_context(&context); + validate_context_transfer(uc, &context, gs_base); + for (i = 0; i < 100; ++i) + { + if ((error = scalar_write(uc, &context, gs_base)) != UC_ERR_OK || + (error = scalar_read(uc, &context)) != UC_ERR_OK || + (error = batch_write(uc, &context, gs_base)) != UC_ERR_OK || + (error = batch_read(uc, &context)) != UC_ERR_OK) + die_unicorn("context warmup", error); + } + scalar_ns = benchmark_context(uc, &context, gs_base, false); + batch_ns = benchmark_context(uc, &context, gs_base, true); + uc_close(uc); + + printf("APPLE_SILICON_HOTPATH context_scalar_ns=%.3f " + "context_batch_ns=%.3f speedup=%.3fx\n", + scalar_ns, batch_ns, scalar_ns / batch_ns); + printf("APPLE_SILICON_HOTPATH api_calls_per_boundary=71->2 " + "removed=69\n"); + + if (!(batch_ns < scalar_ns)) + { + fprintf(stderr, "batch context transfer did not beat scalar calls\n"); + return 1; + } + return benchmark_sink == UINT64_MAX ? 1 : 0; +} diff --git a/dlls/xtajit64/provider_tests/check_apple_silicon_hotpaths.py b/dlls/xtajit64/provider_tests/check_apple_silicon_hotpaths.py new file mode 100755 index 000000000000..a2a73f89b892 --- /dev/null +++ b/dlls/xtajit64/provider_tests/check_apple_silicon_hotpaths.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +"""Static contracts for the xtajit64 Unicorn context-transfer hot path.""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path +from typing import NoReturn + + +def fail(message: str) -> NoReturn: + raise SystemExit(f"xtajit64 context-transfer contract failed: {message}") + + +def function_body(source: str, name: str) -> str: + match = re.search( + rf"(?m)^static\s+(?:inline\s+)?[^\n(]+\b{name}\s*\([^;]*?\)\s*\{{", + source, + re.MULTILINE | re.DOTALL, + ) + if not match: + fail(f"cannot find {name}()") + start = match.end() - 1 + depth = 0 + for index in range(start, len(source)): + if source[index] == "{": + depth += 1 + elif source[index] == "}": + depth -= 1 + if depth == 0: + return source[start : index + 1] + fail(f"unterminated {name}()") + + +def main() -> int: + if len(sys.argv) != 2: + print(f"usage: {sys.argv[0]} UNIXLIB_C", file=sys.stderr) + return 2 + + source = Path(sys.argv[1]).read_text(encoding="utf-8") + writer = function_body(source, "write_context") + reader = function_body(source, "read_context") + + if writer.count("uc_reg_write_batch") != 1 or "uc_reg_write(" in writer: + fail("write_context() is not a single batch API boundary") + if reader.count("uc_reg_read_batch") != 1 or "uc_reg_read(" in reader: + fail("read_context() is not a single batch API boundary") + if "context_write_regs" not in writer or "context_read_regs" not in reader: + fail("context transfer does not use closed register layouts") + if "XTAJIT64_CONTEXT_INTEGER_REG_COUNT = 18" not in source: + fail("integer register count contract is missing") + if "XTAJIT64_CONTEXT_XMM_REG_COUNT = 16" not in source: + fail("XMM register count contract is missing") + if source.count("C_ASSERT( ARRAY_SIZE(context_") != 2: + fail("register layout compile-time assertions are missing") + + print("xtajit64 Unicorn context-transfer contracts passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/dlls/xtajit64/provider_tests/run_apple_silicon_hotpaths.sh b/dlls/xtajit64/provider_tests/run_apple_silicon_hotpaths.sh new file mode 100755 index 000000000000..0a3a6cd32b0b --- /dev/null +++ b/dlls/xtajit64/provider_tests/run_apple_silicon_hotpaths.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# Build pinned Unicorn and benchmark xtajit64 hot paths on native Apple Silicon. + +set -euo pipefail + +if [[ $# -gt 1 ]]; then + echo "usage: $0 [WORK_DIR]" >&2 + exit 2 +fi +if [[ $(/usr/bin/uname -s) != Darwin || $(/usr/bin/uname -m) != arm64 ]]; then + echo "this benchmark requires a native Apple Silicon runner" >&2 + exit 2 +fi +if translated=$(/usr/sbin/sysctl -in sysctl.proc_translated 2>/dev/null); then + [[ $translated != 1 ]] || { + echo "this benchmark must not run through Rosetta" >&2 + exit 2 + } +fi + +source_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && /bin/pwd -P) +work_dir=${1:-} +cleanup=0 +if [[ -z $work_dir ]]; then + work_dir=$(mktemp -d "${TMPDIR:-/tmp}/xtajit64-hotpaths.XXXXXX") + cleanup=1 +else + mkdir -p "$work_dir" + work_dir=$(cd "$work_dir" && /bin/pwd -P) +fi +if [[ $cleanup -eq 1 ]]; then trap 'rm -rf -- "$work_dir"' EXIT; fi + +unicorn_source="$work_dir/unicorn" +unicorn_build="$work_dir/unicorn-build" +benchmark="$work_dir/apple_silicon_hotpaths" +revision=8028ec436f2d9376525352dd38ed9ed6b9f6be10 + +/usr/bin/git init -q "$unicorn_source" +/usr/bin/git -C "$unicorn_source" remote add origin \ + https://github.com/unicorn-engine/unicorn.git +/usr/bin/git -C "$unicorn_source" fetch -q --depth=1 origin "$revision" +/usr/bin/git -C "$unicorn_source" checkout -q --detach FETCH_HEAD + +cmake_bin=$(command -v cmake) +[[ -x $cmake_bin ]] || { echo "cmake is required" >&2; exit 2; } + +ARCHFLAGS="-arch arm64" "$cmake_bin" -S "$unicorn_source" -B "$unicorn_build" \ + -DCMAKE_BUILD_TYPE=Release \ + -DUNICORN_ARCH=x86 \ + -DUNICORN_BUILD_TESTS=OFF \ + -DUNICORN_BUILD_SAMPLES=OFF \ + -DCMAKE_OSX_ARCHITECTURES=arm64 +"$cmake_bin" --build "$unicorn_build" --parallel 3 + +library=$(find "$unicorn_build" -maxdepth 3 \ + \( -name 'libunicorn.dylib' -o -name 'libunicorn.2.dylib' \) \ + -type f -print -quit) +[[ -n $library ]] || { + echo "pinned Unicorn dylib was not produced" >&2 + exit 1 +} +library_dir=$(cd "$(dirname "$library")" && /bin/pwd -P) + +/usr/bin/clang -O3 -DNDEBUG -Wall -Wextra -Werror \ + -Wno-cast-qual \ + -I"$unicorn_source/include" \ + "$source_dir/dlls/xtajit64/provider_tests/apple_silicon_hotpaths.c" \ + -L"$library_dir" -Wl,-rpath,"$library_dir" -lunicorn \ + -o "$benchmark" + +/usr/bin/file "$benchmark" +/usr/bin/arch -arm64 "$benchmark" From 0c8a418b0d003edced92d11bb54893c7a639f417 Mon Sep 17 00:00:00 2001 From: Jungwuk Ryu Date: Thu, 27 Aug 2026 14:16:39 +0900 Subject: [PATCH 3/4] ci: benchmark xtajit64 context batching on Apple Silicon --- .../xtajit64-apple-silicon-hotpaths.yml | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/xtajit64-apple-silicon-hotpaths.yml diff --git a/.github/workflows/xtajit64-apple-silicon-hotpaths.yml b/.github/workflows/xtajit64-apple-silicon-hotpaths.yml new file mode 100644 index 000000000000..6ef61a20bbb6 --- /dev/null +++ b/.github/workflows/xtajit64-apple-silicon-hotpaths.yml @@ -0,0 +1,63 @@ +name: xtajit64 Apple Silicon hot paths + +on: + pull_request: + paths: + - ".github/workflows/xtajit64-apple-silicon-hotpaths.yml" + - "dlls/xtajit64/unixlib.c" + - "dlls/xtajit64/provider_tests/**" + push: + branches: + - perf/apple-silicon-context-batching + paths: + - ".github/workflows/xtajit64-apple-silicon-hotpaths.yml" + - "dlls/xtajit64/unixlib.c" + - "dlls/xtajit64/provider_tests/**" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: xtajit64-apple-silicon-hotpaths-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + context-transfer: + name: Native arm64 context transfer + runs-on: macos-26 + timeout-minutes: 30 + + steps: + - name: Check out source + uses: actions/checkout@v4 + + - name: Validate source contracts + shell: bash + run: | + set -euo pipefail + python3 dlls/xtajit64/provider_tests/check_apple_silicon_hotpaths.py \ + dlls/xtajit64/unixlib.c + python3 -m py_compile \ + dlls/xtajit64/provider_tests/check_apple_silicon_hotpaths.py + bash -n dlls/xtajit64/provider_tests/run_apple_silicon_hotpaths.sh + bash -n dlls/xtajit64/provider_tests/run_unixlib_concurrency.sh + git diff --check + + - name: Benchmark pinned Unicorn on native Apple Silicon + shell: bash + run: | + set -euo pipefail + set -o pipefail + dlls/xtajit64/provider_tests/run_apple_silicon_hotpaths.sh \ + "$RUNNER_TEMP/xtajit64-hotpaths" | tee "$RUNNER_TEMP/xtajit64-hotpaths.log" + grep -E '^(APPLE_SILICON_CPU|APPLE_SILICON_HOTPATH)' \ + "$RUNNER_TEMP/xtajit64-hotpaths.log" + { + echo "## xtajit64 Apple Silicon context-transfer benchmark" + echo + echo '```text' + grep -E '^(APPLE_SILICON_CPU|APPLE_SILICON_HOTPATH)' \ + "$RUNNER_TEMP/xtajit64-hotpaths.log" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" From 743f2c8312fff52e06de5d4c34a43557adaea0a2 Mon Sep 17 00:00:00 2001 From: Jungwuk Ryu Date: Thu, 27 Aug 2026 15:59:30 +0900 Subject: [PATCH 4/4] ci: benchmark syscall register batching on Apple Silicon --- ...xtajit64-apple-silicon-syscall-hotpath.yml | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/xtajit64-apple-silicon-syscall-hotpath.yml diff --git a/.github/workflows/xtajit64-apple-silicon-syscall-hotpath.yml b/.github/workflows/xtajit64-apple-silicon-syscall-hotpath.yml new file mode 100644 index 000000000000..6407d38de0c0 --- /dev/null +++ b/.github/workflows/xtajit64-apple-silicon-syscall-hotpath.yml @@ -0,0 +1,64 @@ +name: xtajit64 Apple Silicon syscall hot path + +on: + pull_request: + paths: + - ".github/workflows/xtajit64-apple-silicon-syscall-hotpath.yml" + - "dlls/xtajit64/**" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: xtajit64-apple-silicon-syscall-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + syscall-register-transfer: + runs-on: macos-26 + timeout-minutes: 30 + steps: + - name: Check out source + uses: actions/checkout@v7 + + - name: Verify native Apple Silicon runner + shell: bash + run: | + set -euo pipefail + test "$(uname -s)" = Darwin + test "$(uname -m)" = arm64 + if translated=$(/usr/sbin/sysctl -in sysctl.proc_translated 2>/dev/null); then + test "$translated" != 1 + fi + + - name: Verify syscall batching contracts + shell: bash + run: | + set -euo pipefail + python3 dlls/xtajit64/provider_tests/check_syscall_register_batching.py \ + dlls/xtajit64/unixlib.c + python3 -m py_compile \ + dlls/xtajit64/provider_tests/check_syscall_register_batching.py + bash -n \ + dlls/xtajit64/provider_tests/run_apple_silicon_hotpaths.sh \ + dlls/xtajit64/provider_tests/run_syscall_register_hotpath.sh + + - name: Build Unicorn and benchmark syscall preparation + shell: bash + run: | + set -euo pipefail + set -o pipefail + workspace="$RUNNER_TEMP/xtajit64-apple-silicon-syscall" + dlls/xtajit64/provider_tests/run_apple_silicon_hotpaths.sh \ + "$workspace" | tee "$RUNNER_TEMP/context-hotpaths.log" + dlls/xtajit64/provider_tests/run_syscall_register_hotpath.sh \ + "$workspace" | tee "$RUNNER_TEMP/syscall-hotpaths.log" + { + echo "## xtajit64 Apple Silicon syscall register benchmark" + echo + echo '```text' + grep -E '^(APPLE_SILICON_CPU|APPLE_SILICON_HOTPATH)' \ + "$RUNNER_TEMP/syscall-hotpaths.log" + echo '```' + } >> "$GITHUB_STEP_SUMMARY"