From 807e29d347297683e26c649f136fa61c5161cbbc Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Mon, 13 Jul 2026 09:08:03 +0200 Subject: [PATCH 1/7] re-enable JIT for ZTS builds on Apple Silicon --- .github/workflows/test-suite.yml | 3 +- ext/opcache/ZendAccelerator.c | 15 +++++++- ext/opcache/config.m4 | 4 -- ext/opcache/jit/zend_jit.c | 62 ++++++++++++++++++++++++++++-- ext/opcache/jit/zend_jit.h | 4 ++ ext/opcache/tests/jit/gh13400.phpt | 55 ++++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 12 deletions(-) create mode 100644 ext/opcache/tests/jit/gh13400.phpt diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index e269ed336408..a1b43e49964b 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -345,7 +345,6 @@ jobs: if: ${{ inputs.all_variations }} uses: ./.github/actions/test-macos - name: Test Tracing JIT - if: ${{ matrix.arch == 'X64' || !matrix.zts }} uses: ./.github/actions/test-macos with: enableOpcache: true @@ -356,7 +355,7 @@ jobs: with: enableOpcache: true - name: Test Function JIT - if: ${{ inputs.all_variations && (matrix.arch == 'X64' || !matrix.zts) }} + if: ${{ inputs.all_variations }} uses: ./.github/actions/test-macos with: enableOpcache: true diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index cedc21c376a4..bf0608bbf5a9 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3274,10 +3274,16 @@ static zend_result accel_post_startup(void) } jit_size = JIT_G(buffer_size); jit_size = ZEND_MM_ALIGNED_SIZE_EX(jit_size, page_size); +#ifndef ZEND_JIT_USE_APPLE_MAP_JIT shm_size += jit_size; +#endif } +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT + switch (zend_shared_alloc_startup(shm_size, 0)) { +#else switch (zend_shared_alloc_startup(shm_size, jit_size)) { +#endif #else switch (zend_shared_alloc_startup(shm_size, 0)) { #endif @@ -3334,10 +3340,15 @@ static zend_result accel_post_startup(void) if (JIT_G(buffer_size) == 0) { JIT_G(enabled) = false; JIT_G(on) = false; - } else if (!ZSMMG(reserved)) { - zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: could not use reserved buffer!"); } else { +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT + zend_jit_startup(NULL, jit_size, reattached); +#else + if (!ZSMMG(reserved)) { + zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: could not use reserved buffer!"); + } zend_jit_startup(ZSMMG(reserved), jit_size, reattached); +#endif zend_jit_startup_ok = true; } } diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index 3798499a4511..922621315113 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -31,10 +31,6 @@ AS_VAR_IF([PHP_OPCACHE_JIT], [yes], [ PHP_OPCACHE_JIT=no ]) - if test "$host_vendor" = "apple" && test "$host_cpu" = "aarch64" && test "$PHP_THREAD_SAFETY" = "yes"; then - AC_MSG_WARN([JIT not supported on Apple Silicon with ZTS]) - PHP_OPCACHE_JIT=no - fi ]) AS_VAR_IF([PHP_OPCACHE_JIT], [yes], [ diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index fbbfab6b243c..a52a920c65a5 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -43,6 +43,10 @@ #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP #include #endif +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT +#include +#include +#endif #ifdef ZTS int jit_globals_id; @@ -831,7 +835,11 @@ ZEND_EXT_API void zend_jit_status(zval *ret) add_assoc_long(&stats, "opt_level", JIT_G(opt_level)); add_assoc_long(&stats, "opt_flags", JIT_G(opt_flags)); if (dasm_buf) { +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT + add_assoc_long(&stats, "buffer_size", dasm_size); +#else add_assoc_long(&stats, "buffer_size", (char*)dasm_end - (char*)dasm_buf); +#endif add_assoc_long(&stats, "buffer_free", (char*)dasm_end - (char*)*dasm_ptr); } else { add_assoc_long(&stats, "buffer_size", 0); @@ -3517,7 +3525,11 @@ int zend_jit_script(zend_script *script) void zend_jit_unprotect(void) { -#ifdef HAVE_MPROTECT +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT +#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP + pthread_jit_write_protect_np(0); +#endif +#elif defined(HAVE_MPROTECT) if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { int opts = PROT_READ | PROT_WRITE; #ifdef ZTS @@ -3552,7 +3564,11 @@ void zend_jit_unprotect(void) void zend_jit_protect(void) { -#ifdef HAVE_MPROTECT +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT +#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP + pthread_jit_write_protect_np(1); +#endif +#elif defined(HAVE_MPROTECT) if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP if (zend_write_protect) { @@ -3780,7 +3796,35 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) zend_jit_interrupt_op = zend_get_interrupt_op(); zend_jit_profile_counter_rid = zend_get_op_array_extension_handle(ACCELERATOR_PRODUCT_NAME); -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT + buf = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, 0); + if (buf == MAP_FAILED) { + int error = errno; + zend_accel_error_noreturn(ACCEL_LOG_FATAL, + "Unable to allocate %zu bytes for JIT buffer using MAP_JIT: %s (%d)", + size, strerror(error), error); + } + if (minherit(buf, size, VM_INHERIT_SHARE) != 0) { + int error = errno; + munmap(buf, size); + zend_accel_error_noreturn(ACCEL_LOG_FATAL, + "Unable to share JIT buffer across fork using minherit(): %s (%d)", + strerror(error), error); + } +#ifndef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP + munmap(buf, size); + zend_accel_error_noreturn(ACCEL_LOG_FATAL, + "Apple Silicon ZTS JIT requires pthread_jit_write_protect_np() support"); +#else + zend_write_protect = pthread_jit_write_protect_supported_np(); + if (!zend_write_protect) { + munmap(buf, size); + zend_accel_error_noreturn(ACCEL_LOG_FATAL, + "Apple Silicon ZTS JIT requires pthread_jit_write_protect_np() support"); + } +#endif +#elif defined(HAVE_PTHREAD_JIT_WRITE_PROTECT_NP) zend_write_protect = pthread_jit_write_protect_supported_np(); #endif @@ -3788,7 +3832,11 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) dasm_size = size; dasm_ptr = dasm_end = (void*)(((char*)dasm_buf) + size - sizeof(*dasm_ptr) * 2); -#ifdef HAVE_MPROTECT +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT +#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP + pthread_jit_write_protect_np(1); +#endif +#elif defined(HAVE_MPROTECT) #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP if (zend_write_protect) { pthread_jit_write_protect_np(1); @@ -3874,6 +3922,12 @@ void zend_jit_shutdown(void) zend_jit_trace_free_caches(&jit_globals); #endif +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT + if (dasm_buf != NULL) { + munmap(dasm_buf, dasm_size); + } +#endif + /* Reset global pointers to prevent use-after-free in `zend_jit_status()` * after gracefully restarting Apache with mod_php, see: * https://github.com/php/php-src/pull/19212 */ diff --git a/ext/opcache/jit/zend_jit.h b/ext/opcache/jit/zend_jit.h index 2671ddd23e2d..96a9c9d769ff 100644 --- a/ext/opcache/jit/zend_jit.h +++ b/ext/opcache/jit/zend_jit.h @@ -17,6 +17,10 @@ #ifndef HAVE_JIT_H #define HAVE_JIT_H +#if defined(__APPLE__) && defined(__aarch64__) && defined(ZTS) +# define ZEND_JIT_USE_APPLE_MAP_JIT 1 +#endif + #if defined(__x86_64__) || defined(i386) || defined(ZEND_WIN32) # define ZEND_JIT_TARGET_X86 1 # define ZEND_JIT_TARGET_ARM64 0 diff --git a/ext/opcache/tests/jit/gh13400.phpt b/ext/opcache/tests/jit/gh13400.phpt new file mode 100644 index 000000000000..db3d7b755761 --- /dev/null +++ b/ext/opcache/tests/jit/gh13400.phpt @@ -0,0 +1,55 @@ +--TEST-- +GH-13400: JIT code generated after fork is visible to the parent +--DESCRIPTION-- +OPcache metadata and generated JIT code are shared by forked workers, so the +JIT mapping and its allocation pointer must remain shared after fork. This test +records the available JIT space before forking and makes only the child call a +function often enough to generate code. The parent then verifies that it sees +the reduced free space and can execute the generated function. + +Without shared inheritance, a private JIT mapping becomes copy-on-write in the +child. The parent's free-space check would print bool(false), while shared +OPcache metadata could point the parent at code bytes that exist only in the +child and cause a crash instead of printing int(42). A startup-only test would +not detect this failure in fork-based SAPIs such as FPM or Apache. +--EXTENSIONS-- +opcache +pcntl +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit=tracing +opcache.jit_buffer_size=16M +opcache.jit_hot_func=1 +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +int(42) From f7021f364374ee8d4a661240328007945f227766 Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Mon, 13 Jul 2026 15:24:29 +0200 Subject: [PATCH 2/7] Indent nested JIT preprocessor directives --- ext/opcache/jit/zend_jit.c | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index a52a920c65a5..d6a69bc21926 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -3526,20 +3526,20 @@ int zend_jit_script(zend_script *script) void zend_jit_unprotect(void) { #ifdef ZEND_JIT_USE_APPLE_MAP_JIT -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP +# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP pthread_jit_write_protect_np(0); -#endif +# endif #elif defined(HAVE_MPROTECT) if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { int opts = PROT_READ | PROT_WRITE; -#ifdef ZTS -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP +# ifdef ZTS +# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP if (zend_write_protect) { pthread_jit_write_protect_np(0); } -#endif +# endif opts |= PROT_EXEC; -#endif +# endif if (mprotect(dasm_buf, dasm_size, opts) != 0) { fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); } @@ -3547,11 +3547,11 @@ void zend_jit_unprotect(void) #elif defined(_WIN32) if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { DWORD old, new; -#ifdef ZTS +# ifdef ZTS new = PAGE_EXECUTE_READWRITE; -#else +# else new = PAGE_READWRITE; -#endif +# endif if (!VirtualProtect(dasm_buf, dasm_size, new, &old)) { DWORD err = GetLastError(); char *msg = php_win32_error_to_msg(err); @@ -3565,16 +3565,16 @@ void zend_jit_unprotect(void) void zend_jit_protect(void) { #ifdef ZEND_JIT_USE_APPLE_MAP_JIT -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP +# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP pthread_jit_write_protect_np(1); -#endif +# endif #elif defined(HAVE_MPROTECT) if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP +# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP if (zend_write_protect) { pthread_jit_write_protect_np(1); } -#endif +# endif if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_EXEC) != 0) { fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); } @@ -3812,18 +3812,18 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) "Unable to share JIT buffer across fork using minherit(): %s (%d)", strerror(error), error); } -#ifndef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP +# ifndef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP munmap(buf, size); zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Apple Silicon ZTS JIT requires pthread_jit_write_protect_np() support"); -#else +# else zend_write_protect = pthread_jit_write_protect_supported_np(); if (!zend_write_protect) { munmap(buf, size); zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Apple Silicon ZTS JIT requires pthread_jit_write_protect_np() support"); } -#endif +# endif #elif defined(HAVE_PTHREAD_JIT_WRITE_PROTECT_NP) zend_write_protect = pthread_jit_write_protect_supported_np(); #endif @@ -3833,15 +3833,15 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) dasm_ptr = dasm_end = (void*)(((char*)dasm_buf) + size - sizeof(*dasm_ptr) * 2); #ifdef ZEND_JIT_USE_APPLE_MAP_JIT -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP +# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP pthread_jit_write_protect_np(1); -#endif +# endif #elif defined(HAVE_MPROTECT) -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP +# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP if (zend_write_protect) { pthread_jit_write_protect_np(1); } -#endif +# endif if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) { if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); From e883ad704c809133f9a48988c31f9a1e767e5b0e Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Mon, 13 Jul 2026 15:25:53 +0200 Subject: [PATCH 3/7] Simplify JIT shared memory sizing --- ext/opcache/ZendAccelerator.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index bf0608bbf5a9..ac6d6787a6d6 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3259,8 +3259,9 @@ static zend_result accel_post_startup(void) file_cache_only = ZCG(accel_directives).file_cache_only; if (!file_cache_only) { size_t shm_size = ZCG(accel_directives).memory_consumption; -#ifdef HAVE_JIT size_t jit_size = 0; +#ifdef HAVE_JIT + size_t jit_buffer_size = 0; bool reattached = false; if (JIT_G(enabled) && JIT_G(buffer_size) @@ -3272,21 +3273,16 @@ static zend_result accel_post_startup(void) zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Failure to initialize shared memory structures - can't get page size."); abort(); } - jit_size = JIT_G(buffer_size); - jit_size = ZEND_MM_ALIGNED_SIZE_EX(jit_size, page_size); -#ifndef ZEND_JIT_USE_APPLE_MAP_JIT + jit_buffer_size = JIT_G(buffer_size); + jit_buffer_size = ZEND_MM_ALIGNED_SIZE_EX(jit_buffer_size, page_size); +# ifndef ZEND_JIT_USE_APPLE_MAP_JIT + jit_size = jit_buffer_size; shm_size += jit_size; -#endif +# endif } +#endif -#ifdef ZEND_JIT_USE_APPLE_MAP_JIT - switch (zend_shared_alloc_startup(shm_size, 0)) { -#else switch (zend_shared_alloc_startup(shm_size, jit_size)) { -#endif -#else - switch (zend_shared_alloc_startup(shm_size, 0)) { -#endif case ALLOC_SUCCESS: if (zend_accel_init_shm() == FAILURE) { accel_startup_ok = false; @@ -3341,14 +3337,14 @@ static zend_result accel_post_startup(void) JIT_G(enabled) = false; JIT_G(on) = false; } else { -#ifdef ZEND_JIT_USE_APPLE_MAP_JIT - zend_jit_startup(NULL, jit_size, reattached); -#else +# ifdef ZEND_JIT_USE_APPLE_MAP_JIT + zend_jit_startup(NULL, jit_buffer_size, reattached); +# else if (!ZSMMG(reserved)) { zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: could not use reserved buffer!"); } - zend_jit_startup(ZSMMG(reserved), jit_size, reattached); -#endif + zend_jit_startup(ZSMMG(reserved), jit_buffer_size, reattached); +# endif zend_jit_startup_ok = true; } } From 8f51da2a2a4c0298c7e61b850d8167ff71bfd9c5 Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Tue, 14 Jul 2026 18:04:44 +0200 Subject: [PATCH 4/7] Require pthread JIT write protection on Apple Silicon ZTS --- ext/opcache/config.m4 | 3 +++ ext/opcache/jit/zend_jit.c | 12 ------------ ext/opcache/jit/zend_jit.h | 3 +++ 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index 922621315113..aa3a67acb3c7 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -31,6 +31,9 @@ AS_VAR_IF([PHP_OPCACHE_JIT], [yes], [ PHP_OPCACHE_JIT=no ]) + AS_IF([test "$host_vendor" = "apple" && test "$host_cpu" = "aarch64" && test "$PHP_THREAD_SAFETY" = "yes"], + [AS_VAR_IF([ac_cv_func_pthread_jit_write_protect_np], [yes], [], + [AC_MSG_ERROR([OPcache JIT on Apple Silicon with ZTS requires pthread_jit_write_protect_np()])])]) ]) AS_VAR_IF([PHP_OPCACHE_JIT], [yes], [ diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index d6a69bc21926..5defaf27adb5 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -3526,9 +3526,7 @@ int zend_jit_script(zend_script *script) void zend_jit_unprotect(void) { #ifdef ZEND_JIT_USE_APPLE_MAP_JIT -# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP pthread_jit_write_protect_np(0); -# endif #elif defined(HAVE_MPROTECT) if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { int opts = PROT_READ | PROT_WRITE; @@ -3565,9 +3563,7 @@ void zend_jit_unprotect(void) void zend_jit_protect(void) { #ifdef ZEND_JIT_USE_APPLE_MAP_JIT -# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP pthread_jit_write_protect_np(1); -# endif #elif defined(HAVE_MPROTECT) if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { # ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP @@ -3812,18 +3808,12 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) "Unable to share JIT buffer across fork using minherit(): %s (%d)", strerror(error), error); } -# ifndef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP - munmap(buf, size); - zend_accel_error_noreturn(ACCEL_LOG_FATAL, - "Apple Silicon ZTS JIT requires pthread_jit_write_protect_np() support"); -# else zend_write_protect = pthread_jit_write_protect_supported_np(); if (!zend_write_protect) { munmap(buf, size); zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Apple Silicon ZTS JIT requires pthread_jit_write_protect_np() support"); } -# endif #elif defined(HAVE_PTHREAD_JIT_WRITE_PROTECT_NP) zend_write_protect = pthread_jit_write_protect_supported_np(); #endif @@ -3833,9 +3823,7 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) dasm_ptr = dasm_end = (void*)(((char*)dasm_buf) + size - sizeof(*dasm_ptr) * 2); #ifdef ZEND_JIT_USE_APPLE_MAP_JIT -# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP pthread_jit_write_protect_np(1); -# endif #elif defined(HAVE_MPROTECT) # ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP if (zend_write_protect) { diff --git a/ext/opcache/jit/zend_jit.h b/ext/opcache/jit/zend_jit.h index 96a9c9d769ff..c4080d86bc6d 100644 --- a/ext/opcache/jit/zend_jit.h +++ b/ext/opcache/jit/zend_jit.h @@ -18,6 +18,9 @@ #define HAVE_JIT_H #if defined(__APPLE__) && defined(__aarch64__) && defined(ZTS) +# ifndef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP +# error "Apple Silicon ZTS JIT requires pthread_jit_write_protect_np()" +# endif # define ZEND_JIT_USE_APPLE_MAP_JIT 1 #endif From 20fc5c682953422104cd71c8b9bf4724b2482ce3 Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Wed, 15 Jul 2026 06:49:15 +0200 Subject: [PATCH 5/7] Scope pthread JIT protection to MAP_JIT --- ext/opcache/jit/zend_jit.c | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 5defaf27adb5..91a52a37bd5e 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -40,11 +40,9 @@ #include "jit/zend_jit_internal.h" -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP -#include -#endif #ifdef ZEND_JIT_USE_APPLE_MAP_JIT #include +#include #include #endif @@ -81,9 +79,6 @@ int16_t zend_jit_hot_counters[ZEND_HOT_COUNTERS_COUNT]; const zend_op *zend_jit_halt_op = NULL; const zend_op *zend_jit_interrupt_op = NULL; -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP -static int zend_write_protect = 1; -#endif static void *dasm_buf = NULL; static void *dasm_end = NULL; @@ -3531,11 +3526,6 @@ void zend_jit_unprotect(void) if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { int opts = PROT_READ | PROT_WRITE; # ifdef ZTS -# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP - if (zend_write_protect) { - pthread_jit_write_protect_np(0); - } -# endif opts |= PROT_EXEC; # endif if (mprotect(dasm_buf, dasm_size, opts) != 0) { @@ -3566,11 +3556,6 @@ void zend_jit_protect(void) pthread_jit_write_protect_np(1); #elif defined(HAVE_MPROTECT) if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) { -# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP - if (zend_write_protect) { - pthread_jit_write_protect_np(1); - } -# endif if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_EXEC) != 0) { fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); } @@ -3808,14 +3793,11 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) "Unable to share JIT buffer across fork using minherit(): %s (%d)", strerror(error), error); } - zend_write_protect = pthread_jit_write_protect_supported_np(); - if (!zend_write_protect) { + if (!pthread_jit_write_protect_supported_np()) { munmap(buf, size); zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Apple Silicon ZTS JIT requires pthread_jit_write_protect_np() support"); } -#elif defined(HAVE_PTHREAD_JIT_WRITE_PROTECT_NP) - zend_write_protect = pthread_jit_write_protect_supported_np(); #endif dasm_buf = buf; @@ -3825,11 +3807,6 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) #ifdef ZEND_JIT_USE_APPLE_MAP_JIT pthread_jit_write_protect_np(1); #elif defined(HAVE_MPROTECT) -# ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP - if (zend_write_protect) { - pthread_jit_write_protect_np(1); - } -# endif if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) { if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); From c626ddfc79ed7ffef66f03e4a979bd91673b6a1e Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Wed, 15 Jul 2026 07:10:42 +0200 Subject: [PATCH 6/7] Check pthread JIT support in extension builds --- ext/opcache/config.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index aa3a67acb3c7..2a1ac4e332fc 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -32,7 +32,7 @@ AS_VAR_IF([PHP_OPCACHE_JIT], [yes], [ ]) AS_IF([test "$host_vendor" = "apple" && test "$host_cpu" = "aarch64" && test "$PHP_THREAD_SAFETY" = "yes"], - [AS_VAR_IF([ac_cv_func_pthread_jit_write_protect_np], [yes], [], + [AC_CHECK_FUNC([pthread_jit_write_protect_np], [], [AC_MSG_ERROR([OPcache JIT on Apple Silicon with ZTS requires pthread_jit_write_protect_np()])])]) ]) From 0ddd5da9007d627b5de2e61c4fbe6a1500d6d18e Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Wed, 15 Jul 2026 10:42:48 +0200 Subject: [PATCH 7/7] Preserve JIT buffer size reporting --- ext/opcache/jit/zend_jit.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 91a52a37bd5e..6f550f0cf36c 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -830,11 +830,7 @@ ZEND_EXT_API void zend_jit_status(zval *ret) add_assoc_long(&stats, "opt_level", JIT_G(opt_level)); add_assoc_long(&stats, "opt_flags", JIT_G(opt_flags)); if (dasm_buf) { -#ifdef ZEND_JIT_USE_APPLE_MAP_JIT - add_assoc_long(&stats, "buffer_size", dasm_size); -#else add_assoc_long(&stats, "buffer_size", (char*)dasm_end - (char*)dasm_buf); -#endif add_assoc_long(&stats, "buffer_free", (char*)dasm_end - (char*)*dasm_ptr); } else { add_assoc_long(&stats, "buffer_size", 0);