From 2417094e85d38bd56d38597e1a6122cb303b5534 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 20 May 2025 11:27:26 +0200 Subject: [PATCH 1/8] Allow building opcache statically Opcache is both a Zend extension and a PHP module. When Opcache is built statically, the engine will attempt to initialize it as a PHP module, but will not initialize the Zend extension. Here I make Opcache a two-way hybrid extension [1] so that initializing it as a PHP module also initializes the Zend extension. [1] https://www.phpinternalsbook.com/php7/extensions_design/zend_extensions.html#hybrid-extensions --- ext/opcache/ZendAccelerator.c | 30 ++++++++++++++++++++++- ext/opcache/ZendAccelerator.h | 5 ++++ ext/opcache/config.m4 | 3 --- ext/opcache/config.w32 | 2 +- ext/opcache/zend_accelerator_module.c | 34 +++++++++++++++++++++++---- ext/opcache/zend_accelerator_module.h | 9 ++++++- 6 files changed, 72 insertions(+), 11 deletions(-) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index a2b2d0fde8b42..9b7cbdc9f9077 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -25,6 +25,7 @@ #include "zend_extensions.h" #include "zend_compile.h" #include "ZendAccelerator.h" +#include "zend_modules.h" #include "zend_persist.h" #include "zend_shared_alloc.h" #include "zend_accelerator_module.h" @@ -100,7 +101,13 @@ typedef int gid_t; #include "zend_simd.h" +#ifdef COMPILE_DL_OPCACHE ZEND_EXTENSION(); +ZEND_EXT_API zend_extension zend_extension_entry; +#define opcache_extension_entry zend_extension_entry +#else +zend_extension opcache_extension_entry; +#endif #ifndef ZTS zend_accel_globals accel_globals; @@ -118,6 +125,7 @@ zend_accel_shared_globals *accel_shared_globals = NULL; #ifdef ZEND_WIN32 char accel_uname_id[32]; #endif +bool accel_starting = false; bool accel_startup_ok = false; static const char *zps_failure_reason = NULL; const char *zps_api_failure_reason = NULL; @@ -3154,8 +3162,21 @@ static void accel_move_code_to_huge_pages(void) # endif /* defined(MAP_HUGETLB) || defined(MADV_HUGEPAGE) */ #endif /* HAVE_HUGE_CODE_PAGES */ +void start_accel_extension(void) +{ + if (accel_starting) { + return; + } + + accel_starting = true; + + zend_register_extension(&opcache_extension_entry, NULL); +} + static int accel_startup(zend_extension *extension) { + accel_starting = true; + #ifdef ZTS accel_globals_id = ts_allocate_id(&accel_globals_id, sizeof(zend_accel_globals), (ts_allocate_ctor) accel_globals_ctor, (ts_allocate_dtor) accel_globals_dtor); #else @@ -3172,12 +3193,15 @@ static int accel_startup(zend_extension *extension) # endif #endif - if (start_accel_module() == FAILURE) { + zend_module_entry *module = start_accel_module(); + if (module == NULL) { accel_startup_ok = false; zend_error(E_WARNING, ACCELERATOR_PRODUCT_NAME ": module registration failed!"); return FAILURE; } + accel_register_ini_entries(module); + #ifdef ZEND_WIN32 if (UNEXPECTED(accel_gen_uname_id() == FAILURE)) { zps_startup_failure("Unable to get user name", NULL, accelerator_remove_cb); @@ -5037,7 +5061,11 @@ static void accel_activate(void) { } } +#ifdef COMPILE_DL_OPCACHE ZEND_EXT_API zend_extension zend_extension_entry = { +#else +zend_extension opcache_extension_entry = { +#endif ACCELERATOR_PRODUCT_NAME, /* name */ PHP_VERSION, /* version */ "Zend Technologies", /* author */ diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index 1bac9af9b8b7a..ff4ced45416ca 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -314,6 +314,7 @@ extern const char *zps_api_failure_reason; BEGIN_EXTERN_C() +void start_accel_extension(void); void accel_shutdown(void); ZEND_RINIT_FUNCTION(zend_accelerator); zend_result accel_post_deactivate(void); @@ -336,6 +337,10 @@ zend_string* ZEND_FASTCALL accel_new_interned_string(zend_string *str); uint32_t zend_accel_get_class_name_map_ptr(zend_string *type_name); +#ifndef COMPILE_DL_OPCACHE +extern zend_extension opcache_extension_entry; +#endif + END_EXTERN_C() /* memory write protection */ diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index 8f6d5ab711b28..fb791865daec6 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -26,9 +26,6 @@ PHP_ARG_WITH([capstone], [no]) if test "$PHP_OPCACHE" != "no"; then - dnl Always build as shared extension. - ext_shared=yes - AS_VAR_IF([PHP_HUGE_CODE_PAGES], [yes], [AC_DEFINE([HAVE_HUGE_CODE_PAGES], [1], [Define to 1 to enable copying PHP CODE pages into HUGE PAGES.])]) diff --git a/ext/opcache/config.w32 b/ext/opcache/config.w32 index fa89ca1f18a39..dc677305ce4c7 100644 --- a/ext/opcache/config.w32 +++ b/ext/opcache/config.w32 @@ -15,7 +15,7 @@ if (PHP_OPCACHE != "no") { zend_persist_calc.c \ zend_file_cache.c \ zend_shared_alloc.c \ - shared_alloc_win32.c", true, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); + shared_alloc_win32.c", PHP_OPCACHE_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); ADD_EXTENSION_DEP('opcache', 'date'); ADD_EXTENSION_DEP('opcache', 'hash'); diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 203a41d93b40a..a5d83d764f95c 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -25,6 +25,8 @@ #include "ZendAccelerator.h" #include "zend_API.h" #include "zend_closures.h" +#include "zend_extensions.h" +#include "zend_modules.h" #include "zend_shared_alloc.h" #include "zend_accelerator_blacklist.h" #include "php_ini.h" @@ -55,6 +57,9 @@ )) #define TOKENTOSTR(X) #X +static zend_module_entry *accel_module_loaded = NULL; +zend_module_entry opcache_module_entry; + static zif_handler orig_file_exists = NULL; static zif_handler orig_is_file = NULL; static zif_handler orig_is_readable = NULL; @@ -405,13 +410,19 @@ static ZEND_NAMED_FUNCTION(accel_is_readable) static ZEND_MINIT_FUNCTION(zend_accelerator) { - (void)type; /* keep the compiler happy */ + accel_module_loaded = zend_hash_str_find_ptr_lc(&module_registry, + opcache_module_entry.name, strlen(opcache_module_entry.name)); - REGISTER_INI_ENTRIES(); + start_accel_extension(); return SUCCESS; } +void accel_register_ini_entries(zend_module_entry *module) +{ + zend_register_ini_entries_ex(ini_entries, module->module_number, module->type); +} + void zend_accel_override_file_functions(void) { zend_function *old_function; @@ -554,7 +565,7 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS) DISPLAY_INI_ENTRIES(); } -static zend_module_entry accel_module_entry = { +zend_module_entry opcache_module_entry = { STANDARD_MODULE_HEADER, ACCELERATOR_PRODUCT_NAME, ext_functions, @@ -569,9 +580,22 @@ static zend_module_entry accel_module_entry = { STANDARD_MODULE_PROPERTIES_EX }; -int start_accel_module(void) +zend_module_entry *start_accel_module(void) { - return zend_startup_module(&accel_module_entry); + if (accel_module_loaded) { + return accel_module_loaded; + } + + accel_module_loaded = zend_register_internal_module(&opcache_module_entry); + if (!accel_module_loaded) { + return NULL; + } + + if (zend_startup_module_ex(accel_module_loaded) == FAILURE) { + return NULL; + } + + return accel_module_loaded; } /* {{{ Get the scripts which are accelerated by ZendAccelerator */ diff --git a/ext/opcache/zend_accelerator_module.h b/ext/opcache/zend_accelerator_module.h index 656336eeba762..ce0069299f5eb 100644 --- a/ext/opcache/zend_accelerator_module.h +++ b/ext/opcache/zend_accelerator_module.h @@ -22,7 +22,14 @@ #ifndef ZEND_ACCELERATOR_MODULE_H #define ZEND_ACCELERATOR_MODULE_H -int start_accel_module(void); +#include "Zend/zend_modules.h" + +#define phpext_opcache_ptr &opcache_module_entry +extern zend_module_entry opcache_module_entry; + +zend_module_entry *start_accel_module(void); + +void accel_register_ini_entries(zend_module_entry *module); void zend_accel_override_file_functions(void); From 3ffefcc01b997bbf03a15311ddb79e50882987f8 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 26 May 2025 11:47:41 +0200 Subject: [PATCH 2/8] Always enable opcache --- ext/opcache/config.m4 | 252 ++++++++++++++++++++---------------------- 1 file changed, 122 insertions(+), 130 deletions(-) diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index fb791865daec6..25303a65d1ba4 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -1,9 +1,3 @@ -PHP_ARG_ENABLE([opcache], - [whether to enable Zend OPcache support], - [AS_HELP_STRING([--disable-opcache], - [Disable Zend OPcache support])], - [yes]) - PHP_ARG_ENABLE([huge-code-pages], [whether to enable copying PHP CODE pages into HUGE PAGES], [AS_HELP_STRING([--disable-huge-code-pages], @@ -25,86 +19,85 @@ PHP_ARG_WITH([capstone], [no], [no]) -if test "$PHP_OPCACHE" != "no"; then - AS_VAR_IF([PHP_HUGE_CODE_PAGES], [yes], - [AC_DEFINE([HAVE_HUGE_CODE_PAGES], [1], - [Define to 1 to enable copying PHP CODE pages into HUGE PAGES.])]) - - AS_VAR_IF([PHP_OPCACHE_JIT], [yes], [ - AS_CASE([$host_cpu], - [[i[34567]86*|x86*|aarch64|amd64]], [], - [ - AC_MSG_WARN([JIT not supported by host architecture]) - 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]) +AS_VAR_IF([PHP_HUGE_CODE_PAGES], [yes], + [AC_DEFINE([HAVE_HUGE_CODE_PAGES], [1], + [Define to 1 to enable copying PHP CODE pages into HUGE PAGES.])]) + +AS_VAR_IF([PHP_OPCACHE_JIT], [yes], [ + AS_CASE([$host_cpu], + [[i[34567]86*|x86*|aarch64|amd64]], [], + [ + AC_MSG_WARN([JIT not supported by host architecture]) PHP_OPCACHE_JIT=no - fi - ]) + ]) - AS_VAR_IF([PHP_OPCACHE_JIT], [yes], [ - AC_DEFINE([HAVE_JIT], [1], [Define to 1 to enable JIT.]) - ZEND_JIT_SRC=m4_normalize([" - jit/ir/ir_cfg.c - jit/ir/ir_check.c - jit/ir/ir_dump.c - jit/ir/ir_emit.c - jit/ir/ir_gcm.c - jit/ir/ir_gdb.c - jit/ir/ir_patch.c - jit/ir/ir_perf.c - jit/ir/ir_ra.c - jit/ir/ir_save.c - jit/ir/ir_sccp.c - jit/ir/ir_strtab.c - jit/ir/ir.c - jit/zend_jit_vm_helpers.c - jit/zend_jit.c - "]) - - dnl Find out which ABI we are using. - AS_CASE([$host_alias], - [x86_64-*-darwin*], [ - IR_TARGET=IR_TARGET_X64 - DASM_FLAGS="-D X64APPLE=1 -D X64=1" - DASM_ARCH="x86" - ], - [*x86_64*|amd64-*-freebsd*], [ - IR_TARGET=IR_TARGET_X64 - DASM_FLAGS="-D X64=1" - DASM_ARCH="x86" - ], - [[i[34567]86*|x86*]], [ - IR_TARGET=IR_TARGET_X86 - DASM_ARCH="x86" - ], - [aarch64*], [ - IR_TARGET=IR_TARGET_AARCH64 - DASM_ARCH="aarch64" - ]) - - AS_VAR_IF([PHP_CAPSTONE], [yes], - [PKG_CHECK_MODULES([CAPSTONE], [capstone >= 3.0.0], [ - AC_DEFINE([HAVE_CAPSTONE], [1], [Define to 1 if Capstone is available.]) - PHP_EVAL_LIBLINE([$CAPSTONE_LIBS], [OPCACHE_SHARED_LIBADD]) - PHP_EVAL_INCLINE([$CAPSTONE_CFLAGS]) - ZEND_JIT_SRC="$ZEND_JIT_SRC jit/ir/ir_disasm.c" - ])]) - - PHP_SUBST([IR_TARGET]) - PHP_SUBST([DASM_FLAGS]) - PHP_SUBST([DASM_ARCH]) - - JIT_CFLAGS="-I@ext_builddir@/jit/ir -D$IR_TARGET -DIR_PHP" - AS_VAR_IF([ZEND_DEBUG], [yes], [JIT_CFLAGS="$JIT_CFLAGS -DIR_DEBUG"]) - ]) + 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], [ + AC_DEFINE([HAVE_JIT], [1], [Define to 1 to enable JIT.]) + ZEND_JIT_SRC=m4_normalize([" + jit/ir/ir_cfg.c + jit/ir/ir_check.c + jit/ir/ir_dump.c + jit/ir/ir_emit.c + jit/ir/ir_gcm.c + jit/ir/ir_gdb.c + jit/ir/ir_patch.c + jit/ir/ir_perf.c + jit/ir/ir_ra.c + jit/ir/ir_save.c + jit/ir/ir_sccp.c + jit/ir/ir_strtab.c + jit/ir/ir.c + jit/zend_jit_vm_helpers.c + jit/zend_jit.c + "]) + + dnl Find out which ABI we are using. + AS_CASE([$host_alias], + [x86_64-*-darwin*], [ + IR_TARGET=IR_TARGET_X64 + DASM_FLAGS="-D X64APPLE=1 -D X64=1" + DASM_ARCH="x86" + ], + [*x86_64*|amd64-*-freebsd*], [ + IR_TARGET=IR_TARGET_X64 + DASM_FLAGS="-D X64=1" + DASM_ARCH="x86" + ], + [[i[34567]86*|x86*]], [ + IR_TARGET=IR_TARGET_X86 + DASM_ARCH="x86" + ], + [aarch64*], [ + IR_TARGET=IR_TARGET_AARCH64 + DASM_ARCH="aarch64" + ]) + + AS_VAR_IF([PHP_CAPSTONE], [yes], + [PKG_CHECK_MODULES([CAPSTONE], [capstone >= 3.0.0], [ + AC_DEFINE([HAVE_CAPSTONE], [1], [Define to 1 if Capstone is available.]) + PHP_EVAL_LIBLINE([$CAPSTONE_LIBS], [OPCACHE_SHARED_LIBADD]) + PHP_EVAL_INCLINE([$CAPSTONE_CFLAGS]) + ZEND_JIT_SRC="$ZEND_JIT_SRC jit/ir/ir_disasm.c" + ])]) + + PHP_SUBST([IR_TARGET]) + PHP_SUBST([DASM_FLAGS]) + PHP_SUBST([DASM_ARCH]) - AC_CHECK_FUNCS([mprotect shm_create_largepage]) + JIT_CFLAGS="-I@ext_builddir@/jit/ir -D$IR_TARGET -DIR_PHP" + AS_VAR_IF([ZEND_DEBUG], [yes], [JIT_CFLAGS="$JIT_CFLAGS -DIR_DEBUG"]) +]) - AC_CACHE_CHECK([for sysvipc shared memory support], [php_cv_shm_ipc], - [AC_RUN_IFELSE([AC_LANG_SOURCE([ +AC_CHECK_FUNCS([mprotect shm_create_largepage]) + +AC_CACHE_CHECK([for sysvipc shared memory support], [php_cv_shm_ipc], + [AC_RUN_IFELSE([AC_LANG_SOURCE([ #include #include #include @@ -306,56 +299,55 @@ int main(void) { } return 0; }]])], - [php_cv_shm_mmap_posix=yes], - [php_cv_shm_mmap_posix=no], - [php_cv_shm_mmap_posix=no]) - ]) + [php_cv_shm_mmap_posix=yes], + [php_cv_shm_mmap_posix=no], + [php_cv_shm_mmap_posix=no]) ]) - LIBS=$LIBS_save +]) +LIBS=$LIBS_save + +AS_VAR_IF([php_cv_shm_mmap_posix], [yes], [ + AC_DEFINE([HAVE_SHM_MMAP_POSIX], [1], + [Define to 1 if you have the POSIX mmap() SHM support.]) + AS_CASE([$ac_cv_search_shm_open], ["none required"|no], [], + [PHP_EVAL_LIBLINE([$ac_cv_search_shm_open], [OPCACHE_SHARED_LIBADD])]) +]) + +PHP_NEW_EXTENSION([opcache], m4_normalize([ + shared_alloc_mmap.c + shared_alloc_posix.c + shared_alloc_shm.c + zend_accelerator_blacklist.c + zend_accelerator_debug.c + zend_accelerator_hash.c + zend_accelerator_module.c + zend_accelerator_util_funcs.c + zend_file_cache.c + zend_persist_calc.c + zend_persist.c + zend_shared_alloc.c + ZendAccelerator.c + $ZEND_JIT_SRC + ]), + [$ext_shared],, + [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 $JIT_CFLAGS],, + [yes]) - AS_VAR_IF([php_cv_shm_mmap_posix], [yes], [ - AC_DEFINE([HAVE_SHM_MMAP_POSIX], [1], - [Define to 1 if you have the POSIX mmap() SHM support.]) - AS_CASE([$ac_cv_search_shm_open], ["none required"|no], [], - [PHP_EVAL_LIBLINE([$ac_cv_search_shm_open], [OPCACHE_SHARED_LIBADD])]) - ]) +PHP_ADD_EXTENSION_DEP(opcache, date) +PHP_ADD_EXTENSION_DEP(opcache, pcre) - PHP_NEW_EXTENSION([opcache], m4_normalize([ - shared_alloc_mmap.c - shared_alloc_posix.c - shared_alloc_shm.c - zend_accelerator_blacklist.c - zend_accelerator_debug.c - zend_accelerator_hash.c - zend_accelerator_module.c - zend_accelerator_util_funcs.c - zend_file_cache.c - zend_persist_calc.c - zend_persist.c - zend_shared_alloc.c - ZendAccelerator.c - $ZEND_JIT_SRC - ]), - [$ext_shared],, - [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 $JIT_CFLAGS],, - [yes]) - - PHP_ADD_EXTENSION_DEP(opcache, date) - PHP_ADD_EXTENSION_DEP(opcache, pcre) - - if test "$php_cv_shm_ipc" != "yes" && test "$php_cv_shm_mmap_posix" != "yes" && test "$php_cv_shm_mmap_anon" != "yes"; then - AC_MSG_FAILURE(m4_text_wrap([ - No supported shared memory caching support was found when configuring - opcache. - ])) - fi +if test "$php_cv_shm_ipc" != "yes" && test "$php_cv_shm_mmap_posix" != "yes" && test "$php_cv_shm_mmap_anon" != "yes"; then + AC_MSG_FAILURE(m4_text_wrap([ + No supported shared memory caching support was found when configuring + opcache. + ])) +fi - AS_VAR_IF([PHP_OPCACHE_JIT], [yes], [ - PHP_ADD_BUILD_DIR([ - $ext_builddir/jit - $ext_builddir/jit/ir - ]) - PHP_ADD_MAKEFILE_FRAGMENT([$ext_srcdir/jit/Makefile.frag]) +AS_VAR_IF([PHP_OPCACHE_JIT], [yes], [ + PHP_ADD_BUILD_DIR([ + $ext_builddir/jit + $ext_builddir/jit/ir ]) - PHP_SUBST([OPCACHE_SHARED_LIBADD]) -fi + PHP_ADD_MAKEFILE_FRAGMENT([$ext_srcdir/jit/Makefile.frag]) +]) +PHP_SUBST([OPCACHE_SHARED_LIBADD]) From ba04798bb9fc20cb93b1a50c0c496f15b13109dd Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 21 May 2025 15:32:55 +0200 Subject: [PATCH 3/8] Fix "TLS transition from R_X86_64_TLSGD to R_X86_64_GOTTPOFF against `_tsrm_ls_cache' at 0x12fc3 in section `.text' failed" In order to get information about the _tsrm_ls_cache TLS variable, we emit ASM code with TLS-related relocations [1]. These relocations, once adjusted by the linker, give us the offset or address of the variable's TLS descriptor. Specifically, we use relocations meant for use in the "General Dynamic" model described in [1]. When building Opcache statically in a binary, the linker will attempt to optimize the General Dynamic code sequence to a more efficient one. Unfortunately, linkers will break as we don't use the exact code sequence they are expecting. Here I use a different approach to get information about the TLS variable: * Emit the exact code sequence expected by linker, so that linking works * Extract the information we want by inspecting the ASM code. If the linker did something we didn't expect, we fallback to a safer (but slower) mechanism. [1] https://www.akkadia.org/drepper/tls.pdf --- ext/opcache/jit/zend_jit_ir.c | 650 +++++++++++++++++++++++++++++----- 1 file changed, 556 insertions(+), 94 deletions(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 1fdb1b9b3af91..91942cf7e2f6a 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -334,6 +334,8 @@ static int zend_jit_assign_to_variable(zend_jit_ctx *jit, zend_jit_addr ref_addr, bool check_exception); +static ir_ref jit_CONST_FUNC(zend_jit_ctx *jit, uintptr_t addr, uint16_t flags); + typedef struct _zend_jit_stub { const char *name; int (*stub)(zend_jit_ctx *jit); @@ -463,6 +465,10 @@ static const char* zend_reg_name(int8_t reg) /* IR helpers */ #ifdef ZTS +static void * ZEND_FASTCALL zend_jit_get_tsrm_ls_cache(void) { + return _tsrm_ls_cache; +} + static ir_ref jit_TLS(zend_jit_ctx *jit) { ZEND_ASSERT(jit->ctx.control); @@ -482,9 +488,13 @@ static ir_ref jit_TLS(zend_jit_ctx *jit) ref = insn->op1; } } - jit->tls = ir_TLS( - tsrm_ls_cache_tcb_offset ? tsrm_ls_cache_tcb_offset : tsrm_tls_index, - tsrm_ls_cache_tcb_offset ? IR_NULL : tsrm_tls_offset); + if (tsrm_ls_cache_tcb_offset == 0 && tsrm_tls_index == -1 && tsrm_tls_offset == -1) { + jit->tls = ir_CALL(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_get_tsrm_ls_cache)); + } else { + jit->tls = ir_TLS( + tsrm_ls_cache_tcb_offset ? tsrm_ls_cache_tcb_offset : tsrm_tls_index, + tsrm_ls_cache_tcb_offset ? IR_NULL : tsrm_tls_offset); + } return jit->tls; } #endif @@ -3113,6 +3123,7 @@ static void zend_jit_setup_disasm(void) REGISTER_HELPER(zend_fcall_interrupt); #ifndef ZTS + REGISTER_DATA(EG(current_execute_data)); REGISTER_DATA(EG(exception)); REGISTER_DATA(EG(opline_before_exception)); @@ -3127,6 +3138,8 @@ static void zend_jit_setup_disasm(void) REGISTER_DATA(EG(symbol_table)); REGISTER_DATA(CG(map_ptr_base)); +#else /* ZTS */ + REGISTER_HELPER(zend_jit_get_tsrm_ls_cache); #endif #endif } @@ -3293,6 +3306,535 @@ static void zend_jit_setup_unwinder(void) } #endif +#if defined(ZTS) +# if defined(IR_TARGET_AARCH64) + +/* https://developer.arm.com/documentation/ddi0602/2025-03/Base-Instructions/ADRP--Form-PC-relative-address-to-4KB-page- */ +# define AARCH64_ADRP_IMM_MASK 0x60ffffe0 /* bits 30-29, 23-5 */ +# define AARCH64_LDR_UNSIGNED_IMM_MASK 0x003ffc00 /* bits 21-10 */ +# define AARCH64_ADD_IMM_MASK 0x003ffc00 /* bits 21-10 */ +# define AARCH64_MOVZ_IMM_MASK 0x001fffe0 /* bits 20-5 */ +# define AARCH64_MOVZ_HW_MASK 0x00600000 /* bits 22-21 */ +# define AARCH64_MOVK_IMM_MASK 0x001fffe0 /* bits 20-5 */ +# define AARCH64_MOVK_HW_MASK 0x00600000 /* bits 22-21 */ +# define AARCH64_NOP 0xd503201f + +# ifdef __MUSL__ + +# define DTV_OFFSET -8 +# define DTV_INDEX_GAP 0 + +typedef struct _dtv_pointer_t { + uintptr_t val; +} dtv_pointer_t; + +typedef struct TLSDescriptor { + size_t index; + size_t offset; +} TLSDescriptor; + +# elif defined(__FreeBSD__) + +# define DTV_OFFSET 0 +/* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/22ca6db50f4e6bd75a141f57cf953d8de6531a06/lib/libc/gen/tls.c#L88) */ +# define DTV_INDEX_GAP 1 + +typedef struct _dtv_pointer_t { + uintptr_t val; +} dtv_pointer_t; + +/* https://github.com/freebsd/freebsd-src/blob/c52ca7dd09066648b1cc40f758289404d68ab886/libexec/rtld-elf/aarch64/reloc.c#L180-L184 */ +typedef struct TLSDescriptor { + void* thunk; + int index; + size_t offset; +} TLSDescriptor; + +# else /* Glibc */ + +# define DTV_OFFSET 0 +# define DTV_INDEX_GAP 0 + +typedef struct _dtv_pointer_t { + uintptr_t val; + uintptr_t _; +} dtv_pointer_t; + +typedef struct TLSDescriptor { + size_t index; + size_t offset; +} TLSDescriptor; + +# endif + +ZEND_ATTRIBUTE_UNUSED static zend_result zend_jit_resolve_tsrm_ls_cache_offsets(void) { + void *addr; + uint32_t *insn; + void *thread_pointer; + + __asm__ __volatile__( + /* Load thread pointer address */ + "mrs %0, tpidr_el0\n" + /* Load next instruction address */ + "adr %1, .+4\n\t" + /* General Dynamic code sequence as expected by linkers */ + "adrp x0, :tlsdesc:_tsrm_ls_cache\n" + "ldr x1, [x0, #:tlsdesc_lo12:_tsrm_ls_cache]\n" + "add x0, x0, :tlsdesc_lo12:_tsrm_ls_cache\n" + ".tlsdesccall _tsrm_ls_cache\n" + "blr x1\n" + "mrs x8, tpidr_el0\n" + "add %2, x8, x0\n" + : "=r" (thread_pointer), "=r" (insn), "=r" (addr) + : + : "x0", "x1", "x8"); + + ZEND_ASSERT(addr == &_tsrm_ls_cache); + + /* Check if the general dynamic code was relaxed by the linker */ + + // adrp x0, #any + if ((insn[0] & ~AARCH64_ADRP_IMM_MASK) != 0x90000000) { + zend_accel_error(ACCEL_LOG_DEBUG, "adrp insn does not match: 0x%08x\n", insn[0]); + goto code_changed; + } + + // ldr x1, [x0, #any] + if ((insn[1] & ~AARCH64_LDR_UNSIGNED_IMM_MASK) != 0xf9400001) { + zend_accel_error(ACCEL_LOG_DEBUG, "ldr insn does not match: 0x%08x\n", insn[1]); + goto code_changed; + } + + // add x0, x0, any + if ((insn[2] & ~AARCH64_ADD_IMM_MASK) != 0x91000000) { + zend_accel_error(ACCEL_LOG_DEBUG, "add insn does not match: 0x%08x\n", insn[2]); + goto code_changed; + } + + /* Code is intact, we can extract immediate values */ + + uint64_t adrp_imm = (uint64_t)( ((insn[0] & 0x00ffffe0) >> 3) | ((insn[0] & 0x60000000) >> 29) ) << 12; + uint64_t add_imm = (uint64_t)(insn[2] & AARCH64_ADD_IMM_MASK) >> 10; + uint64_t pc = (uint64_t)insn; + uintptr_t **where = (uintptr_t**)((pc & ~(4096-1)) + adrp_imm + add_imm); + + /* See https://github.com/ARM-software/abi-aa/blob/2a70c42d62e9c3eb5887fa50b71257f20daca6f9/aaelf64/aaelf64.rst + * section "Relocations for thread-local storage". + * The first entry holds a pointer to the variable's TLS descriptor resolver + * function and the second entry holds a platform-specific offset or + * pointer. */ + TLSDescriptor *tlsdesc = (TLSDescriptor*)(where[1]); + + if ((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer == (uintptr_t)tlsdesc) { + zend_accel_error(ACCEL_LOG_DEBUG, "static tls at offset %p from thread pointer (inferred from tlsdesc)\n", tlsdesc); + tsrm_ls_cache_tcb_offset = (uintptr_t)tlsdesc; + return SUCCESS; + } + + /* We've got the TLS descriptor. Double check: */ + +# if ZEND_DEBUG + dtv_pointer_t *dtv = *(dtv_pointer_t**)((uintptr_t)thread_pointer + DTV_OFFSET); + addr = (void*) (dtv[tlsdesc->index + DTV_INDEX_GAP].val + tlsdesc->offset); + + ZEND_ASSERT(addr == &_tsrm_ls_cache); +# endif + + zend_accel_error(ACCEL_LOG_DEBUG, "dynamic tls module idx %zu offset %zu (inferred from code)\n", (size_t)tlsdesc->index, tlsdesc->offset); + + tsrm_tls_index = (tlsdesc->index + DTV_INDEX_GAP) * sizeof(dtv_pointer_t); + tsrm_tls_offset = tlsdesc->offset; + + return SUCCESS; + +code_changed: + + /* Code was changed by the linker. Check if we recognize the updated code */ + + // movz x0, #0, lsl #16 + if ((insn[0] & ~AARCH64_MOVZ_IMM_MASK) != 0xd2a00000) { + zend_accel_error(ACCEL_LOG_DEBUG, "movz insn does not match: 0x%08x\n", insn[0]); + return FAILURE; + } + + // movk x0, #0x10 + if ((insn[1] & ~AARCH64_MOVK_IMM_MASK) != 0xf2800000) { + zend_accel_error(ACCEL_LOG_DEBUG, "movk insn does not match: 0x%08x\n", insn[1]); + return FAILURE; + } + + // nop + for (int i = 0; i < 2; i++) { + if (insn[2+i] != 0xd503201f) { + zend_accel_error(ACCEL_LOG_DEBUG, "nop(%d) insn does not match: 0x%08x\n", i, insn[2+i]); + return FAILURE; + } + } + + /* Extract immediate values */ + + uint64_t movz_imm = (insn[0] & AARCH64_MOVZ_IMM_MASK) >> 5; + uint64_t movz_shift = (((insn[0] & AARCH64_MOVZ_HW_MASK) >> 21) << 4); + uint64_t mozk_imm = (insn[1] & AARCH64_MOVK_IMM_MASK) >> 5; + uint64_t offset = (movz_imm << movz_shift) | mozk_imm; + + if ((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer == offset) { + zend_accel_error(ACCEL_LOG_DEBUG, "static tls at offset %" PRIx64 " from thread pointer (inferred from code)\n", offset); + tsrm_ls_cache_tcb_offset = offset; + return SUCCESS; + } + + return FAILURE; +} + +# elif defined(__GNUC__) && defined(__i386__) + +# ifdef __MUSL__ + +# define DTV_OFFSET 4 +# define DTV_INDEX_GAP 0 + +typedef struct _dtv_pointer_t { + uintptr_t val; +} dtv_pointer_t; + +typedef struct TLSDescriptor { + size_t index; + size_t offset; +} TLSDescriptor; + +# elif defined(__FreeBSD__) + +# define DTV_OFFSET 4 +# define DTV_INDEX_GAP 1 + +typedef struct _dtv_pointer_t { + uintptr_t val; +} dtv_pointer_t; + +/* https://github.com/freebsd/freebsd-src/blob/6b94546a7ea2dc593f5765bd5465a8b7bb80c325/libexec/rtld-elf/i386/rtld_machdep.h#L65 */ +typedef struct TLSDescriptor { + unsigned long index; + unsigned long offset; +} TLSDescriptor; + +# else + +# define DTV_OFFSET 4 +# define DTV_INDEX_GAP 0 + +typedef struct _dtv_pointer_t { + uintptr_t val; + uintptr_t _; +} dtv_pointer_t; + +typedef struct TLSDescriptor { + size_t index; + size_t offset; +} TLSDescriptor; + +# endif + +ZEND_ATTRIBUTE_UNUSED static zend_result zend_jit_resolve_tsrm_ls_cache_offsets(void) { +# if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__MUSL__) + size_t ret; + + asm ("leal _tsrm_ls_cache@ntpoff,%0\n" + : "=a" (ret)); + tsrm_ls_cache_tcb_offset = ret; + + return SUCCESS; + +# else + + void *t_addr; + unsigned char *code; + void *thread_pointer; + + __asm__ __volatile__( + /* Load next instruction address */ + "call 1f\n" + ".subsection 1\n" + "1:\n" + "movl (%%esp), %%ebx\n" + "movl %%ebx, %%esi\n" + "ret\n" + ".previous\n" + /* General Dynamic code sequence as expected by linkers */ + "addl $_GLOBAL_OFFSET_TABLE_, %%ebx\n" + "leal _tsrm_ls_cache@TLSGD(,%%ebx,1), %%eax\n" + "call ___tls_get_addr@PLT\n" + /* Load thread pointer address */ + "movl %%gs:0, %%ebx\n" + : "=a" (t_addr), "=S" (code), "=b" (thread_pointer) + ); + + ZEND_ASSERT(t_addr == &_tsrm_ls_cache); + + /* Check if the general dynamic code was relaxed by the linker */ + + // addl any,%ebx + if (code[0] != 0x81 || code[1] != 0xc3) { + uint64_t bytes; + memcpy(&bytes, &code[0], 8); + zend_accel_error(ACCEL_LOG_DEBUG, "addl insn does not match: 0x%16" PRIx64 "\n", bytes); + goto code_changed; + } + + // leal any(,%ebx,1),%eax + if (code[6] != 0x8d || code[7] != 0x04 || code[8] != 0x1d) { + uint64_t bytes; + memcpy(&bytes, &code[6], 8); + zend_accel_error(ACCEL_LOG_DEBUG, "leal insn does not match: 0x%16" PRIx64 "\n", bytes); + goto code_changed; + } + + // call any + if (code[13] != 0xe8) { + uint64_t bytes; + memcpy(&bytes, &code[13], 8); + zend_accel_error(ACCEL_LOG_DEBUG, "call insn does not match: 0x%16" PRIx64 "\n", bytes); + goto code_changed; + } + + /* Code is intact, we can extract immediate values */ + + uint32_t addl_imm = ((uint32_t)code[5] << 24) + | ((uint32_t)code[4] << 16) + | ((uint32_t)code[3] << 8) + | ((uint32_t)code[2]); + uint32_t leal_imm = ((uint32_t)code[12] << 24) + | ((uint32_t)code[11] << 16) + | ((uint32_t)code[10] << 8) + | ((uint32_t)code[9]); + + TLSDescriptor *tlsdesc = (TLSDescriptor*)(leal_imm + addl_imm + (uintptr_t)code); + + /* We've got the TLS descriptor. Double check: */ + +# if ZEND_DEBUG + dtv_pointer_t *dtv = *(dtv_pointer_t**)((uintptr_t)thread_pointer + DTV_OFFSET); + void *addr = (void*) (dtv[tlsdesc->index + DTV_INDEX_GAP].val + tlsdesc->offset); + + ZEND_ASSERT((void*)addr == &_tsrm_ls_cache); +# endif + + zend_accel_error(ACCEL_LOG_DEBUG, "dynamic tls module idx %zu offset %zu (inferred from code)\n", (size_t)tlsdesc->index, (size_t)tlsdesc->offset); + + tsrm_tls_index = tlsdesc->index * 8; + tsrm_tls_offset = tlsdesc->offset; + + return SUCCESS; + +code_changed: + + /* Code was changed by the linker. Check if we recognize the updated code */ + + /* + * 81 c3 98 2d 00 00 addl $0x2d98,%ebx + * 65 a1 00 00 00 00 movl %gs:0x0,%eax + * 81 e8 04 00 00 00 subl $0x4,%eax + */ + + // movl %gs:0x0,%eax + if (memcmp(&code[6], "\x65\xa1\x00\x00\x00\x00", 6) != 0) { + uint64_t bytes; + memcpy(&bytes, &code[6], 8); + zend_accel_error(ACCEL_LOG_DEBUG, "movl insn does not match: 0x%16" PRIx64 "\n", bytes); + return FAILURE; + } + + // subl $any,%eax + if (code[12] != 0x81 || code[13] != 0xe8) { + uint64_t bytes; + memcpy(&bytes, &code[6], 8); + zend_accel_error(ACCEL_LOG_DEBUG, "subl insn does not match: 0x%16" PRIx64 "\n", bytes); + return FAILURE; + } + + /* Extract immediate values */ + + uint32_t offset = -(((uint32_t)code[17] << 24) + | ((uint32_t)code[16] << 16) + | ((uint32_t)code[15] << 8) + | ((uint32_t)code[14])); + + if ((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer == offset) { + zend_accel_error(ACCEL_LOG_DEBUG, "static tls at offset %" PRIx32 " from thread pointer (inferred from code)\n", offset); + tsrm_ls_cache_tcb_offset = offset; + return SUCCESS; + } + + zend_accel_error(ACCEL_LOG_DEBUG, "static tls offset does not match: 0x%08" PRIx32 " (expected 0x%08" PRIx32 ")\n", offset, (uint32_t)((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer)); + + return FAILURE; + +# endif +} +# elif defined(__GNUC__) && defined(__x86_64__) +# ifdef __MUSL__ + +# define DTV_OFFSET 8 +# define DTV_INDEX_GAP 0 + +typedef struct _dtv_pointer_t { + uintptr_t val; +} dtv_pointer_t; + +typedef struct TLSDescriptor { + size_t index; + size_t offset; +} TLSDescriptor; + +# elif defined(__FreeBSD__) + +# define DTV_OFFSET 4 +# define DTV_INDEX_GAP 1 + +typedef struct _dtv_pointer_t { + uintptr_t val; +} dtv_pointer_t; + +/* https://github.com/freebsd/freebsd-src/blob/6b94546a7ea2dc593f5765bd5465a8b7bb80c325/libexec/rtld-elf/amd64/rtld_machdep.h#L65 */ +typedef struct TLSDescriptor { + unsigned long index; + unsigned long offset; +} TLSDescriptor; + +# else + +# define DTV_OFFSET 8 +# define DTV_INDEX_GAP 0 + +typedef struct _dtv_pointer_t { + uintptr_t val; + uintptr_t _; +} dtv_pointer_t; + +typedef struct TLSDescriptor { + size_t index; + size_t offset; +} TLSDescriptor; + +# endif + +ZEND_ATTRIBUTE_UNUSED static zend_result zend_jit_resolve_tsrm_ls_cache_offsets(void) { +# if defined(__has_attribute) && __has_attribute(tls_model) && !defined(__FreeBSD__) && \ + !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__MUSL__) + size_t ret; + + asm ("movq _tsrm_ls_cache@gottpoff(%%rip),%0" + : "=r" (ret)); + tsrm_ls_cache_tcb_offset = ret; + + return SUCCESS; +# else + + void *addr; + unsigned char *code; + void *thread_pointer; + + __asm__ __volatile__( + /* Load next instruction address */ + "leaq (%%rip), %%rbx\n" + /* General Dynamic code sequence as expected by linkers */ + ".byte 0x66\n" + "leaq _tsrm_ls_cache@tlsgd(%%rip), %%rdi\n" + ".word 0x6666\n" + "rex64\n" + "call __tls_get_addr\n" + /* Load thread pointer address */ + "movq %%fs:0, %%rsi\n" + : "=a" (addr), "=b" (code), "=S" (thread_pointer) + ); + + ZEND_ASSERT(addr == &_tsrm_ls_cache); + + /* Check if the general dynamic code was relaxed by the linker */ + + // data16 leaq any(%rip),%rdi + if (code[0] != 0x66 || code[1] != 0x48 || code[2] != 0x8d || code[3] != 0x3d) { + uint64_t bytes; + memcpy(&bytes, &code[0], 8); + zend_accel_error(ACCEL_LOG_DEBUG, "leaq insn does not match: 0x%016" PRIx64 "\n", bytes); + goto code_changed; + } + + // data16 data16 rex.W call any + if (code[8] != 0x66 || code[9] != 0x66 || code[10] != 0x48 || code[11] != 0xe8) { + uint64_t bytes; + memcpy(&bytes, &code[8], 8); + zend_accel_error(ACCEL_LOG_DEBUG, "call insn does not match: 0x%016" PRIx64 "\n", bytes); + goto code_changed; + } + + /* Code is intact, we can extract immediate values */ + + uintptr_t leaq_imm = (uintptr_t)(int32_t)((uint32_t)code[7] << 24) + | ((uint32_t)code[6] << 16) + | ((uint32_t)code[5] << 8) + | ((uint32_t)code[4]); + + TLSDescriptor *tlsdesc = (TLSDescriptor*)(leaq_imm + (uintptr_t)code + 8 /* leaq */); + + /* We've got the TLS descriptor. Double check: */ + +# if ZEND_DEBUG + dtv_pointer_t *dtv = *(dtv_pointer_t**)((uintptr_t)thread_pointer + DTV_OFFSET); + addr = (void*) (dtv[tlsdesc->index + DTV_INDEX_GAP].val + tlsdesc->offset); + + ZEND_ASSERT((void*)addr == &_tsrm_ls_cache); +# endif + + zend_accel_error(ACCEL_LOG_DEBUG, "dynamic tls module idx %zu offset %zu (infered from code)\n", (size_t)tlsdesc->index, (size_t)tlsdesc->offset); + + return SUCCESS; + +code_changed: + + /* Code was changed by the linker. Check if we recognize the updated code */ + + /* + * 64 48 8b 04 25 00 00 00 00 movq %fs:0x0,%rax + * 48 8d 80 f8 ff ff ff leaq -0x8(%rax),%rax + */ + + // movq %fs:0x0,%rax + if (memcmp(&code[0], "\x64\x48\x8b\x04\x25\x00\x00\x00\x00", 9) != 0) { + uint64_t bytes; + memcpy(&bytes, &code[0], 8); + zend_accel_error(ACCEL_LOG_DEBUG, "movq insn does not match: 0x%016" PRIx64 "\n", bytes); + return FAILURE; + } + + // leaq any(%rax),$rax + if (code[9] != 0x48 || code[10] != 0x8d || code[11] != 0x80) { + uint64_t bytes; + memcpy(&bytes, &code[10], 8); + zend_accel_error(ACCEL_LOG_DEBUG, "leaq insn does not match: 0x%016" PRIx64 "\n", bytes); + return FAILURE; + } + + /* Extract immediate values */ + + uintptr_t offset = (uintptr_t)(int32_t)(((uint32_t)code[15] << 24) + | ((uint32_t)code[14] << 16) + | ((uint32_t)code[13] << 8) + | ((uint32_t)code[12])); + + if ((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer == offset) { + tsrm_ls_cache_tcb_offset = offset; + zend_accel_error(ACCEL_LOG_DEBUG, "static tls at offset 0x%" PRIxPTR " from thread pointer (inferred from code)\n", offset); + return SUCCESS; + } + + zend_accel_error(ACCEL_LOG_DEBUG, "static tls offset does not match: 0x%016" PRIxPTR " (expected 0x%016" PRIxPTR ")\n", offset, ((uintptr_t)&_tsrm_ls_cache - (uintptr_t)thread_pointer)); + + return FAILURE; +# endif +} +# endif +#endif /* ZTS */ static void zend_jit_setup(bool reattached) { @@ -3313,40 +3855,16 @@ static void zend_jit_setup(bool reattached) # endif #endif #ifdef ZTS + #if defined(IR_TARGET_AARCH64) tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset(); -# ifdef __FreeBSD__ +# if defined(__FreeBSD__) || defined(__MUSL__) if (tsrm_ls_cache_tcb_offset == 0) { - TLSDescriptor **where; - - __asm__( - "adrp %0, :tlsdesc:_tsrm_ls_cache\n" - "add %0, %0, :tlsdesc_lo12:_tsrm_ls_cache\n" - : "=r" (where)); - /* See https://github.com/ARM-software/abi-aa/blob/2a70c42d62e9c3eb5887fa50b71257f20daca6f9/aaelf64/aaelf64.rst - * section "Relocations for thread-local storage". - * The first entry holds a pointer to the variable's TLS descriptor resolver function and the second entry holds - * a platform-specific offset or pointer. */ - TLSDescriptor *tlsdesc = where[1]; - - tsrm_tls_offset = tlsdesc->offset; - /* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/22ca6db50f4e6bd75a141f57cf953d8de6531a06/lib/libc/gen/tls.c#L88) */ - tsrm_tls_index = (tlsdesc->index + 1) * 8; - } -# elif defined(__MUSL__) - if (tsrm_ls_cache_tcb_offset == 0) { - size_t **where; - - __asm__( - "adrp %0, :tlsdesc:_tsrm_ls_cache\n" - "add %0, %0, :tlsdesc_lo12:_tsrm_ls_cache\n" - : "=r" (where)); - /* See https://github.com/ARM-software/abi-aa/blob/2a70c42d62e9c3eb5887fa50b71257f20daca6f9/aaelf64/aaelf64.rst */ - size_t *tlsdesc = where[1]; - - tsrm_tls_offset = tlsdesc[1]; - tsrm_tls_index = tlsdesc[0] * 8; + if (zend_jit_resolve_tsrm_ls_cache_offsets() == FAILURE) { + tsrm_tls_offset = -1; + tsrm_tls_index = -1; + } } # else ZEND_ASSERT(tsrm_ls_cache_tcb_offset != 0); @@ -3407,69 +3925,13 @@ static void zend_jit_setup(bool reattached) tsrm_tls_offset = ti[2]; tsrm_tls_index = ti[1] * 8; } -# elif defined(__GNUC__) && defined(__x86_64__) +# elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset(); if (tsrm_ls_cache_tcb_offset == 0) { -#if defined(__has_attribute) && __has_attribute(tls_model) && !defined(__FreeBSD__) && \ - !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__MUSL__) - size_t ret; - - asm ("movq _tsrm_ls_cache@gottpoff(%%rip),%0" - : "=r" (ret)); - tsrm_ls_cache_tcb_offset = ret; -#elif defined(__MUSL__) - size_t *ti; - - __asm__( - "leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n" - : "=a" (ti)); - tsrm_tls_offset = ti[1]; - tsrm_tls_index = ti[0] * 8; -#elif defined(__FreeBSD__) - size_t *ti; - - __asm__( - "leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n" - : "=a" (ti)); - tsrm_tls_offset = ti[1]; - /* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/bf56e8b9c8639ac4447d223b83cdc128107cc3cd/libexec/rtld-elf/rtld.c#L5260) */ - tsrm_tls_index = (ti[0] + 1) * 8; -#else - size_t *ti; - - __asm__( - "leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n" - : "=a" (ti)); - tsrm_tls_offset = ti[1]; - tsrm_tls_index = ti[0] * 16; -#endif - } -# elif defined(__GNUC__) && defined(__i386__) - tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset(); - if (tsrm_ls_cache_tcb_offset == 0) { -#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__MUSL__) - size_t ret; - - asm ("leal _tsrm_ls_cache@ntpoff,%0\n" - : "=a" (ret)); - tsrm_ls_cache_tcb_offset = ret; -#else - size_t *ti, _ebx, _ecx, _edx; - - __asm__( - "call 1f\n" - ".subsection 1\n" - "1:\tmovl (%%esp), %%ebx\n\t" - "ret\n" - ".previous\n\t" - "addl $_GLOBAL_OFFSET_TABLE_, %%ebx\n\t" - "leal _tsrm_ls_cache@tlsldm(%%ebx), %0\n\t" - "call ___tls_get_addr@plt\n\t" - "leal _tsrm_ls_cache@tlsldm(%%ebx), %0\n" - : "=a" (ti), "=&b" (_ebx), "=&c" (_ecx), "=&d" (_edx)); - tsrm_tls_offset = ti[1]; - tsrm_tls_index = ti[0] * 8; -#endif + if (zend_jit_resolve_tsrm_ls_cache_offsets() == FAILURE) { + tsrm_tls_index = -1; + tsrm_tls_offset = -1; + } } # endif #endif From 8489edac3bad894b2a78d16e3c40fc7b435cecfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 26 May 2025 12:50:17 +0200 Subject: [PATCH 4/8] Remove `zend_extension=opcache` --- .circleci/config.yml | 1 - .github/actions/freebsd/action.yml | 1 - .github/workflows/nightly.yml | 18 ------------------ .github/workflows/push.yml | 4 ---- ext/opcache/tests/gh18417.phpt | 1 - php.ini-development | 2 -- php.ini-production | 2 -- sapi/cli/tests/bug80092.phpt | 1 - 8 files changed, 30 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0ce68b36d9013..cf6a863c235cf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -168,7 +168,6 @@ jobs: no_output_timeout: 30m command: | sapi/cli/php run-tests.php \ - -d zend_extension=opcache.so \ -d opcache.enable_cli=1 \ -d opcache.jit_buffer_size=64M \ -d opcache.jit=tracing \ diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml index d375a51a6d173..525280f5f800c 100644 --- a/.github/actions/freebsd/action.yml +++ b/.github/actions/freebsd/action.yml @@ -101,4 +101,3 @@ runs: --show-diff \ --show-slow 1000 \ --set-timeout 120 \ - -d zend_extension=opcache.so diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1b1532af7f799..27ea1cedaafaa 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -126,7 +126,6 @@ jobs: jitType: tracing runTestsParameters: >- --asan -x - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Notify Slack if: failure() @@ -240,14 +239,12 @@ jobs: jitType: tracing runTestsParameters: >- ${{ matrix.run_tests_parameters }} - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Test OpCache uses: ./.github/actions/test-linux with: runTestsParameters: >- ${{ matrix.run_tests_parameters }} - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Test Function JIT # ASAN frequently timeouts. Each test run takes ~90 minutes, we can @@ -258,7 +255,6 @@ jobs: jitType: function runTestsParameters: >- ${{ matrix.run_tests_parameters }} - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files @@ -332,14 +328,12 @@ jobs: jitType: tracing runTestsParameters: >- ${{ matrix.run_tests_parameters }} - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Test OpCache uses: ./.github/actions/test-linux with: runTestsParameters: >- ${{ matrix.run_tests_parameters }} - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Test Function JIT uses: ./.github/actions/test-linux @@ -347,7 +341,6 @@ jobs: jitType: function runTestsParameters: >- ${{ matrix.run_tests_parameters }} - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Notify Slack if: failure() @@ -392,13 +385,11 @@ jobs: with: jitType: tracing runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Test OpCache uses: ./.github/actions/test-macos with: runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Test Function JIT if: matrix.os != '14' || !matrix.zts @@ -406,7 +397,6 @@ jobs: with: jitType: function runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files @@ -468,7 +458,6 @@ jobs: with: jitType: tracing runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 - uses: codecov/codecov-action@v4 if: ${{ !cancelled() }} @@ -522,7 +511,6 @@ jobs: - name: Enable Opcache run: | echo memory_limit=-1 >> /etc/php.d/opcache.ini - echo zend_extension=opcache.so > /etc/php.d/opcache.ini echo opcache.enable_cli=1 >> /etc/php.d/opcache.ini echo opcache.enable=1 >> /etc/php.d/opcache.ini echo opcache.protect_memory=1 >> /etc/php.d/opcache.ini @@ -713,21 +701,18 @@ jobs: uses: ./.github/actions/test-linux with: runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 --file-cache-prime - name: Test File Cache (prime shm, use shm) uses: ./.github/actions/test-linux with: runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 --file-cache-use - name: Test File Cache (prime shm, use file) uses: ./.github/actions/test-linux with: runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 --file-cache-use -d opcache.file_cache_only=1 @@ -735,7 +720,6 @@ jobs: uses: ./.github/actions/test-linux with: runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 --file-cache-prime -d opcache.file_cache_only=1 @@ -743,7 +727,6 @@ jobs: uses: ./.github/actions/test-linux with: runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 --file-cache-use -d opcache.file_cache_only=1 @@ -837,7 +820,6 @@ jobs: with: runTestsParameters: >- --msan - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 2f82179b90ec6..6b0a46dc5e710 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -130,7 +130,6 @@ jobs: with: jitType: tracing runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 ${{ matrix.asan && '--asan -x' || '' }} - name: Verify generated files are up to date @@ -190,7 +189,6 @@ jobs: with: jitType: tracing runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 MACOS_DEBUG_NTS: if: github.repository == 'php/php-src' || github.event_name == 'pull_request' @@ -229,7 +227,6 @@ jobs: with: jitType: tracing runTestsParameters: >- - -d zend_extension=opcache.so -d opcache.enable_cli=1 - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files @@ -319,7 +316,6 @@ jobs: sudo mkdir -p /etc/php.d sudo chmod 777 /etc/php.d echo mysqli.default_socket=/var/run/mysqld/mysqld.sock > /etc/php.d/mysqli.ini - echo zend_extension=opcache.so >> /etc/php.d/opcache.ini echo opcache.enable=1 >> /etc/php.d/opcache.ini echo opcache.enable_cli=1 >> /etc/php.d/opcache.ini - name: Setup diff --git a/ext/opcache/tests/gh18417.phpt b/ext/opcache/tests/gh18417.phpt index e7b9790b9e661..f4f430827b3a4 100644 --- a/ext/opcache/tests/gh18417.phpt +++ b/ext/opcache/tests/gh18417.phpt @@ -23,7 +23,6 @@ $proc = proc_open([ PHP_BINARY, "-n", "-d", "extension_dir=$extension_dir", - "-d", "zend_extension=opcache", "-d", "opcache.memory_consumption=$new_memory_consumption", "-d", "opcache.enable=1", "-d", "opcache.enable_cli=1", diff --git a/php.ini-development b/php.ini-development index 162fb3f25c19c..8c58a86eee4b6 100644 --- a/php.ini-development +++ b/php.ini-development @@ -959,8 +959,6 @@ default_socket_timeout = 60 ;extension=xsl ;extension=zip -;zend_extension=opcache - ;;;;;;;;;;;;;;;;;;; ; Module Settings ; ;;;;;;;;;;;;;;;;;;; diff --git a/php.ini-production b/php.ini-production index 042d246943d81..4798a6a563938 100644 --- a/php.ini-production +++ b/php.ini-production @@ -961,8 +961,6 @@ default_socket_timeout = 60 ;extension=xsl ;extension=zip -;zend_extension=opcache - ;;;;;;;;;;;;;;;;;;; ; Module Settings ; ;;;;;;;;;;;;;;;;;;; diff --git a/sapi/cli/tests/bug80092.phpt b/sapi/cli/tests/bug80092.phpt index 350b46b3f57a6..27c02f8b3e1d7 100644 --- a/sapi/cli/tests/bug80092.phpt +++ b/sapi/cli/tests/bug80092.phpt @@ -17,7 +17,6 @@ if (!file_exists($extDir . '/opcache.so')) { $cmd = [ PHP_BINARY, '-dextension_dir=' . ini_get('extension_dir'), - '-dzend_extension=opcache.so', '-dopcache.enable=1', '-dopcache.enable_cli=1', '-dopcache.preload=' . __DIR__ . '/preload.inc', From 4f7f977c93d43444447276f02bc2f83ec193b3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 26 May 2025 12:54:15 +0200 Subject: [PATCH 5/8] Remove `--enable-opcache` --- .circleci/config.yml | 1 - .github/workflows/push.yml | 1 - docs/release-process.md | 8 ++++---- ext/opcache/jit/Dockerfile.arm64.example | 2 +- ext/opcache/jit/README.md | 2 +- sapi/fuzzer/README.md | 4 ++-- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cf6a863c235cf..f5dddad093158 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -90,7 +90,6 @@ jobs: --prefix=/usr \ --enable-phpdbg \ --enable-fpm \ - --enable-opcache \ --with-pdo-mysql=mysqlnd \ --with-mysqli=mysqlnd \ --with-pgsql \ diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 6b0a46dc5e710..38be0086fc8df 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -296,7 +296,6 @@ jobs: ./configure \ --disable-debug \ --enable-mbstring \ - --enable-opcache \ --enable-option-checking=fatal \ --enable-sockets \ --enable-werror \ diff --git a/docs/release-process.md b/docs/release-process.md index 5d603a22f078e..4351942900fc5 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -209,7 +209,7 @@ slightly different steps. We'll call attention where the steps differ. # With ZTS make distclean || \ ./buildconf --force \ - && ./configure --enable-zts --disable-all --enable-debug --enable-opcache --enable-opcache-jit \ + && ./configure --enable-zts --disable-all --enable-debug --enable-opcache-jit \ && make -j$(nproc) \ && make test TEST_PHP_ARGS="-q -j$(nproc)" \ || ./sapi/cli/php -v @@ -217,7 +217,7 @@ slightly different steps. We'll call attention where the steps differ. # Without ZTS make distclean || \ ./buildconf --force \ - && ./configure --disable-all --enable-debug --enable-opcache --enable-opcache-jit \ + && ./configure --disable-all --enable-debug --enable-opcache-jit \ && make -j$(nproc) \ && make test TEST_PHP_ARGS="-q -j$(nproc)" \ || ./sapi/cli/php -v @@ -528,7 +528,7 @@ slightly different steps. We'll call attention where the steps differ. # With ZTS make distclean || \ ./buildconf --force \ - && ./configure --enable-zts --disable-all --enable-debug --enable-opcache --enable-opcache-jit \ + && ./configure --enable-zts --disable-all --enable-debug --enable-opcache-jit \ && make -j$(nproc) \ && make test TEST_PHP_ARGS="-q -j$(nproc)" \ || ./sapi/cli/php -v @@ -536,7 +536,7 @@ slightly different steps. We'll call attention where the steps differ. # Without ZTS make distclean || \ ./buildconf --force \ - && ./configure --disable-all --enable-debug --enable-opcache --enable-opcache-jit \ + && ./configure --disable-all --enable-debug --enable-opcache-jit \ && make -j$(nproc) \ && make test TEST_PHP_ARGS="-q -j$(nproc)" \ || ./sapi/cli/php -v diff --git a/ext/opcache/jit/Dockerfile.arm64.example b/ext/opcache/jit/Dockerfile.arm64.example index e7b6a03b2db89..6665f4ab81b75 100644 --- a/ext/opcache/jit/Dockerfile.arm64.example +++ b/ext/opcache/jit/Dockerfile.arm64.example @@ -12,4 +12,4 @@ ADD . /php-src/ WORKDIR /php-src RUN ./buildconf # Compile a minimal debug build. --enable-debug adds runtime assertions and is slower than regular builds. -RUN ./configure --enable-debug --disable-all --enable-opcache && make clean && make -j$(nproc) +RUN ./configure --enable-debug --disable-all && make clean && make -j$(nproc) diff --git a/ext/opcache/jit/README.md b/ext/opcache/jit/README.md index 6ec58378acc7e..c87c625e845bb 100644 --- a/ext/opcache/jit/README.md +++ b/ext/opcache/jit/README.md @@ -76,7 +76,7 @@ export LDFLAGS=-L/usr/lib/i386-linux-gnu export CFLAGS='-m32' export CXXFLAGS='-m32' export PKG_CONFIG=/usr/bin/i686-linux-gnu-pkg-config -./configure --disable-all --enable-opcache --build=i686-pc-linux-gnu +./configure --disable-all --build=i686-pc-linux-gnu make -j$(nproc) ``` diff --git a/sapi/fuzzer/README.md b/sapi/fuzzer/README.md index b4bb2bbe4573f..42e1eb7fa3040 100644 --- a/sapi/fuzzer/README.md +++ b/sapi/fuzzer/README.md @@ -31,8 +31,8 @@ When running `make` it creates these binaries in `sapi/fuzzer/`: * `php-fuzz-mbstring`: Fuzzing `mb_convert_encoding()` (requires `--enable-mbstring`) * `php-fuzz-mbregex`: Fuzzing `mb_ereg[i]()` (requires --enable-mbstring) * `php-fuzz-execute`: Fuzzing the executor -* `php-fuzz-function-jit`: Fuzzing the function JIT (requires --enable-opcache) -* `php-fuzz-tracing-jit`: Fuzzing the tracing JIT (requires --enable-opcache) +* `php-fuzz-function-jit`: Fuzzing the function JIT +* `php-fuzz-tracing-jit`: Fuzzing the tracing JIT Some fuzzers have a seed corpus in `sapi/fuzzer/corpus`. You can use it as follows: From 91adbe5fa986e47424cfb2adf48cf98bc93e67c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 26 May 2025 14:29:16 +0200 Subject: [PATCH 6/8] Fix expectations in version string tests --- sapi/cgi/tests/001.phpt | 3 +-- sapi/cli/tests/001.phpt | 3 +-- sapi/fpm/tests/main-version.phpt | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/sapi/cgi/tests/001.phpt b/sapi/cgi/tests/001.phpt index c4b539aa83bc6..0527993e8c164 100644 --- a/sapi/cgi/tests/001.phpt +++ b/sapi/cgi/tests/001.phpt @@ -17,6 +17,5 @@ echo "Done\n"; --EXPECTF-- string(%d) "PHP %s (cgi%s (built: %s Copyright (c) The PHP Group -%AZend Engine v%s, Copyright (c) Zend Technologies -" +%AZend Engine v%s, Copyright (c) Zend Technologies%A" Done diff --git a/sapi/cli/tests/001.phpt b/sapi/cli/tests/001.phpt index e13ab8def300f..fc1ae71fa134a 100644 --- a/sapi/cli/tests/001.phpt +++ b/sapi/cli/tests/001.phpt @@ -14,6 +14,5 @@ echo "Done\n"; --EXPECTF-- string(%d) "PHP %s (cli) (built: %s)%s Copyright (c) The PHP Group -%AZend Engine v%s, Copyright (c) Zend Technologies -" +%AZend Engine v%s, Copyright (c) Zend Technologies%A" Done diff --git a/sapi/fpm/tests/main-version.phpt b/sapi/fpm/tests/main-version.phpt index 5ae83562d070b..d03372846ede0 100644 --- a/sapi/fpm/tests/main-version.phpt +++ b/sapi/fpm/tests/main-version.phpt @@ -16,6 +16,5 @@ echo "Done\n"; --EXPECTF-- string(%d) "PHP %s (fpm%s (built: %s Copyright (c) The PHP Group -%AZend Engine v%s, Copyright (c) Zend Technologies -" +%AZend Engine v%s, Copyright (c) Zend Technologies%A" Done From f9e17db8ad10c59bc781340bc4a8e064f0ceda3b Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 26 May 2025 15:02:21 +0200 Subject: [PATCH 7/8] Fix opcache startup after a shutdown SAPIs such as phpdbg may run multiple startup-shutdown cycles in the same process. --- ext/opcache/ZendAccelerator.c | 2 ++ ext/opcache/zend_accelerator_module.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 9b7cbdc9f9077..ce0c1a441bb6f 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3485,6 +3485,8 @@ void accel_shutdown(void) zend_ini_entry *ini_entry; bool _file_cache_only = false; + accel_starting = false; + #ifdef HAVE_JIT zend_jit_shutdown(); #endif diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index a5d83d764f95c..576c601ee48bb 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -453,6 +453,8 @@ static ZEND_MSHUTDOWN_FUNCTION(zend_accelerator) UNREGISTER_INI_ENTRIES(); accel_shutdown(); + accel_module_loaded = false; + return SUCCESS; } From ad16a7553aa7a6b4141ef692c16d4c51e98c28c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 26 May 2025 16:05:10 +0200 Subject: [PATCH 8/8] fixup! Remove `zend_extension=opcache` --- .github/scripts/windows/test_task.bat | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/scripts/windows/test_task.bat b/.github/scripts/windows/test_task.bat index 43e7763e70294..d97a5dad922b8 100644 --- a/.github/scripts/windows/test_task.bat +++ b/.github/scripts/windows/test_task.bat @@ -128,7 +128,6 @@ mkdir %PHP_BUILD_DIR%\test_file_cache rem generate php.ini echo extension_dir=%PHP_BUILD_DIR% > %PHP_BUILD_DIR%\php.ini echo opcache.file_cache=%PHP_BUILD_DIR%\test_file_cache >> %PHP_BUILD_DIR%\php.ini -if "%OPCACHE%" equ "1" echo zend_extension=php_opcache.dll >> %PHP_BUILD_DIR%\php.ini rem work-around for some spawned PHP processes requiring OpenSSL and sockets echo extension=php_openssl.dll >> %PHP_BUILD_DIR%\php.ini echo extension=php_sockets.dll >> %PHP_BUILD_DIR%\php.ini