diff --git a/NEWS b/NEWS index 6b1e857a8708..5bb78b98c4e7 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,9 @@ PHP NEWS . Fixed bug GH-22668 (Heap buffer over-read when a column value exceeds the driver-reported display size). (iliaal) +- Opcache: + . Re-enable JIT for ZTS builds on Apple Silicon. (realFlowControl) + - PDO_ODBC: . Fixed bug GH-22667 (Heap buffer over-read when a column value exceeds the driver-reported display size). (iliaal) diff --git a/UPGRADING b/UPGRADING index 57a1f6a0ebad..26e50b215f77 100644 --- a/UPGRADING +++ b/UPGRADING @@ -486,6 +486,9 @@ PHP 8.6 UPGRADE NOTES - mysqli . Added new constant MYSQLI_OPT_COMPRESS. +- Opcache + . JIT is now supported for ZTS builds on Apple Silicon. + ======================================== 10. New Global Constants ======================================== diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index cedc21c376a4..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,15 +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); + 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 switch (zend_shared_alloc_startup(shm_size, jit_size)) { -#else - switch (zend_shared_alloc_startup(shm_size, 0)) { -#endif case ALLOC_SUCCESS: if (zend_accel_init_shm() == FAILURE) { accel_startup_ok = false; @@ -3334,10 +3336,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 { - zend_jit_startup(ZSMMG(reserved), jit_size, reattached); +# 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_buffer_size, reattached); +# endif zend_jit_startup_ok = true; } } diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index 3798499a4511..2a1ac4e332fc 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -31,10 +31,9 @@ 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_IF([test "$host_vendor" = "apple" && test "$host_cpu" = "aarch64" && test "$PHP_THREAD_SAFETY" = "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()])])]) ]) 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..6f550f0cf36c 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -40,8 +40,10 @@ #include "jit/zend_jit_internal.h" -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT +#include #include +#include #endif #ifdef ZTS @@ -77,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; @@ -3517,17 +3516,14 @@ int zend_jit_script(zend_script *script) void zend_jit_unprotect(void) { -#ifdef HAVE_MPROTECT +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT + pthread_jit_write_protect_np(0); +#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 - if (zend_write_protect) { - pthread_jit_write_protect_np(0); - } -#endif +# ifdef ZTS opts |= PROT_EXEC; -#endif +# endif if (mprotect(dasm_buf, dasm_size, opts) != 0) { fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno)); } @@ -3535,11 +3531,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); @@ -3552,13 +3548,10 @@ void zend_jit_unprotect(void) void zend_jit_protect(void) { -#ifdef HAVE_MPROTECT +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT + 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)); } @@ -3780,20 +3773,36 @@ 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 - zend_write_protect = pthread_jit_write_protect_supported_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); + } + 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"); + } #endif dasm_buf = buf; dasm_size = size; dasm_ptr = dasm_end = (void*)(((char*)dasm_buf) + size - sizeof(*dasm_ptr) * 2); -#ifdef HAVE_MPROTECT -#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP - if (zend_write_protect) { - pthread_jit_write_protect_np(1); - } -#endif +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT + pthread_jit_write_protect_np(1); +#elif defined(HAVE_MPROTECT) 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)); @@ -3874,6 +3883,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..c4080d86bc6d 100644 --- a/ext/opcache/jit/zend_jit.h +++ b/ext/opcache/jit/zend_jit.h @@ -17,6 +17,13 @@ #ifndef HAVE_JIT_H #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 + #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)