From 3abebf3e31bd0b02a82e4c4aaeb40463ab055272 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 13 Oct 2025 12:57:16 +0200 Subject: [PATCH] Fix JIT TLS on MacOS The dynamic loader, starting around version 1284, patches the thunk emitted for thread local variables by the compiler, so that its format changes from struct Thunk { void *func; size_t module; size_t offset; } to struct Thunk_v2 { void *func; uint32_t module; uint32_t offset; // other fields } which has the same size, but not the same layout. This is mentionned in https://github.com/apple-oss-distributions/dyld/blob/9307719dd8dc9b385daa412b03cfceb897b2b398/libdyld/ThreadLocalVariables.h#L90 As a result, access to thread specific variables in JIT is broken. Fix by using the new layout when the new dynamic loader is in use. Closes GH-20121 --- NEWS | 2 ++ ext/opcache/jit/zend_jit.c | 4 ++++ ext/opcache/jit/zend_jit_x86.dasc | 40 +++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 8412c6c2bf4..d8efbffb9c9 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,8 @@ PHP NEWS - Opcache: . Fixed bug GH-20081 (access to uninitialized vars in preload_load()). (Arnaud) + . Fixed bug GH-20121 (JIT broken in ZTS builds on MacOS 15). + (Arnaud, Shivam Mathur) - Phar: . Fix memory leak of argument in webPhar. (nielsdos) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 147d0c0b112..b58b515a623 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -51,6 +51,10 @@ #include #endif +#if defined(__APPLE__) && defined(__x86_64__) +# include +#endif + #ifdef ZTS int jit_globals_id; #else diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index 7061f6b2b73..358154cc4d1 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -2826,6 +2826,25 @@ static zend_never_inline void zend_jit_set_sp_adj_vm(void) } #endif +#if defined(__APPLE__) && defined(__x86_64__) +/* Thunk format used since dydl 1284 (approx. MacOS 15) + * https://github.com/apple-oss-distributions/dyld/blob/9307719dd8dc9b385daa412b03cfceb897b2b398/libdyld/ThreadLocalVariables.h#L146 */ +struct TLV_Thunkv2 +{ + void* func; + uint32_t key; + uint32_t offset; +}; + +/* Thunk format used in earlier versions */ +struct TLV_Thunkv1 +{ + void* func; + size_t key; + size_t offset; +}; +#endif + static int zend_jit_setup(void) { if (!zend_cpu_supports_sse2()) { @@ -2889,12 +2908,25 @@ static int zend_jit_setup(void) # elif defined(__APPLE__) && defined(__x86_64__) tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset(); if (tsrm_ls_cache_tcb_offset == 0) { - size_t *ti; + struct TLV_Thunkv2 *thunk; __asm__( "leaq __tsrm_ls_cache(%%rip),%0" - : "=r" (ti)); - tsrm_tls_offset = ti[2]; - tsrm_tls_index = ti[1] * 8; + : "=r" (thunk)); + + /* Detect dyld 1284: With dyld 1284, thunk->func will be _tlv_get_addr. + * Unfortunately this symbol is private, but we can find it + * as _tlv_bootstrap+8: https://github.com/apple-oss-distributions/dyld/blob/9307719dd8dc9b385daa412b03cfceb897b2b398/libdyld/threadLocalHelpers.s#L54 + * In earlier versions, thunk->func will be tlv_get_addr, which is not + * _tlv_bootstrap+8. + */ + if (thunk->func == (void*)((char*)_tlv_bootstrap + 8)) { + tsrm_tls_offset = thunk->offset; + tsrm_tls_index = (size_t)thunk->key * 8; + } else { + struct TLV_Thunkv1 *thunkv1 = (struct TLV_Thunkv1*) thunk; + tsrm_tls_offset = thunkv1->offset; + tsrm_tls_index = thunkv1->key * 8; + } } # elif defined(__GNUC__) && defined(__x86_64__) tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset();