From aa36e75018c3b561deb51c088bbd09274afa940e Mon Sep 17 00:00:00 2001 From: henderkes Date: Tue, 21 Apr 2026 14:48:58 +0700 Subject: [PATCH 1/4] enable tailcall vm with clang >= 19 on windows (aarch64, x86-64) --- Zend/zend_vm_gen.php | 3 +-- Zend/zend_vm_opcodes.h | 2 +- ext/opcache/jit/zend_jit_ir.c | 22 ++++++++++++++++++++++ win32/build/confutils.js | 4 ++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index cef44695be85..7ca4d0154c88 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -2497,8 +2497,7 @@ function gen_vm_opcodes_header( $str .= "# define ZEND_VM_KIND\t\tZEND_VM_KIND_HYBRID\n"; } if ($GLOBALS["vm_kind_name"][ZEND_VM_GEN_KIND] === "ZEND_VM_KIND_HYBRID" || $GLOBALS["vm_kind_name"][ZEND_VM_GEN_KIND] === "ZEND_VM_KIND_CALL") { - $str .= "#elif defined(HAVE_MUSTTAIL) && defined(HAVE_PRESERVE_NONE) && (defined(__x86_64__) || defined(__aarch64__))\n"; - $str .= "# define ZEND_VM_KIND\t\tZEND_VM_KIND_TAILCALL\n"; + $str .= "#elif defined(HAVE_MUSTTAIL) && defined(HAVE_PRESERVE_NONE) && (defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64))\n"; $str .= "# define ZEND_VM_KIND\t\tZEND_VM_KIND_TAILCALL\n"; $str .= "#else\n"; $str .= "# define ZEND_VM_KIND\t\tZEND_VM_KIND_CALL\n"; } else { diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h index b63177e2421e..d7bd233ebb81 100644 --- a/Zend/zend_vm_opcodes.h +++ b/Zend/zend_vm_opcodes.h @@ -41,7 +41,7 @@ static const char *const zend_vm_kind_name[] = { /* HYBRID requires support for computed GOTO and global register variables*/ #elif (defined(__GNUC__) && defined(HAVE_GCC_GLOBAL_REGS)) # define ZEND_VM_KIND ZEND_VM_KIND_HYBRID -#elif defined(HAVE_MUSTTAIL) && defined(HAVE_PRESERVE_NONE) && (defined(__x86_64__) || defined(__aarch64__)) +#elif defined(HAVE_MUSTTAIL) && defined(HAVE_PRESERVE_NONE) && (defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64)) # define ZEND_VM_KIND ZEND_VM_KIND_TAILCALL #else # define ZEND_VM_KIND ZEND_VM_KIND_CALL diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index c4b88b74f64d..e802d213aff1 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -3314,6 +3314,27 @@ static PRUNTIME_FUNCTION zend_jit_unwind_callback(DWORD64 pc, PVOID context) static void zend_jit_setup_unwinder(void) { +#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL + /* TAILCALL VM: fixed_save_regset=0, no registers pushed in prologue. + * fixed_stack_frame_size=40, fixed_call_stack_size=48 (16+IR_SHADOW_ARGS). + * Prologue is: sub rsp, 0x58 (88 bytes = 40+48). */ + static const unsigned char uw_data[] = { + 0x01, // Version=1, Flags=0 + 0x04, // Size of prolog (sub rsp,imm8 = 4 bytes: 48 83 ec 58) + 0x01, // Count of unwind codes + 0x00, // Frame Register=none + 0x04, 0xa2, // offset 4: UWOP_ALLOC_SMALL info=10, alloc=(10+1)*8=88 + 0x00, 0x00, // padding + }; + /* Exit call variant: base 88 + 304 (shadow+GP+FP+padding) = 392 (0x188) */ + static const unsigned char uw_data_exitcall[] = { + 0x01, // Version=1, Flags=0 + 0x07, // Size of prolog (sub rsp,imm32 = 7 bytes: 48 81 ec 88 01 00 00) + 0x02, // Count of unwind codes + 0x00, // Frame Register=none + 0x07, 0x01, 0x31, 0x00, // offset 7: UWOP_ALLOC_LARGE info=0, size/8=49, alloc=392 + }; +#else /* Hardcoded SEH unwind data for JIT-ed PHP functions with "fixed stack frame" */ static const unsigned char uw_data[] = { 0x01, // UBYTE: 3 Version , UBYTE: 5 Flags @@ -3348,6 +3369,7 @@ static void zend_jit_setup_unwinder(void) 0x02, 0x50, // 1: pushq %rbp 0x01, 0x30, // 0: pushq %rbx }; +#endif zend_jit_uw_func = (PRUNTIME_FUNCTION)*dasm_ptr; *dasm_ptr = (char*)*dasm_ptr + ZEND_MM_ALIGNED_SIZE_EX(sizeof(RUNTIME_FUNCTION) * 4 + diff --git a/win32/build/confutils.js b/win32/build/confutils.js index fd1e9ce0be10..aeb55404465a 100644 --- a/win32/build/confutils.js +++ b/win32/build/confutils.js @@ -3374,6 +3374,10 @@ function toolset_setup_common_cflags() var vc_ver = probe_binary(PATH_PROG('cl', null)); ADD_FLAG("CFLAGS"," -fms-compatibility -fms-compatibility-version=" + vc_ver + " -fms-extensions"); + + if (CLANGVERS >= 1900 && (TARGET_ARCH === 'x64' || TARGET_ARCH === 'arm64')) { + AC_DEFINE('HAVE_PRESERVE_NONE', 1, 'Whether the compiler supports __attribute__((preserve_none))'); + } } if (!CLANG_TOOLSET) { From 7e287d5fa154b996087ce51b790f36ae560d3bf1 Mon Sep 17 00:00:00 2001 From: henderkes Date: Tue, 21 Apr 2026 15:00:29 +0700 Subject: [PATCH 2/4] fix copy/paste mistake --- Zend/zend_vm_gen.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index 7ca4d0154c88..24596dbef566 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -2497,7 +2497,8 @@ function gen_vm_opcodes_header( $str .= "# define ZEND_VM_KIND\t\tZEND_VM_KIND_HYBRID\n"; } if ($GLOBALS["vm_kind_name"][ZEND_VM_GEN_KIND] === "ZEND_VM_KIND_HYBRID" || $GLOBALS["vm_kind_name"][ZEND_VM_GEN_KIND] === "ZEND_VM_KIND_CALL") { - $str .= "#elif defined(HAVE_MUSTTAIL) && defined(HAVE_PRESERVE_NONE) && (defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64))\n"; $str .= "# define ZEND_VM_KIND\t\tZEND_VM_KIND_TAILCALL\n"; + $str .= "#elif defined(HAVE_MUSTTAIL) && defined(HAVE_PRESERVE_NONE) && (defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64))\n"; + $str .= "# define ZEND_VM_KIND\t\tZEND_VM_KIND_TAILCALL\n"; $str .= "#else\n"; $str .= "# define ZEND_VM_KIND\t\tZEND_VM_KIND_CALL\n"; } else { From 4b35586bc01d303b2f5d0501a0560e52490dc86a Mon Sep 17 00:00:00 2001 From: henderkes Date: Tue, 21 Apr 2026 17:27:41 +0700 Subject: [PATCH 3/4] add test to verify clang 19+ -> tailcall vm --- .../tests/vm_kind_tailcall_clang_windows.phpt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Zend/tests/vm_kind_tailcall_clang_windows.phpt diff --git a/Zend/tests/vm_kind_tailcall_clang_windows.phpt b/Zend/tests/vm_kind_tailcall_clang_windows.phpt new file mode 100644 index 000000000000..2ff94be22d48 --- /dev/null +++ b/Zend/tests/vm_kind_tailcall_clang_windows.phpt @@ -0,0 +1,24 @@ +--TEST-- +Tailcall VM is selected when compiled with Clang >= 19 on Windows +--SKIPIF-- + clang version (\d+)/', $info, $m)) { + die('skip not compiled with clang'); +} + +if ((int)$m[1] < 19) { + die('skip requires clang >= 19'); +} +?> +--FILE-- + +--EXPECT-- +string(21) "ZEND_VM_KIND_TAILCALL" From 00bfbd02464fce49c3697d5086315aead3013b7b Mon Sep 17 00:00:00 2001 From: henderkes Date: Tue, 21 Apr 2026 20:40:46 +0700 Subject: [PATCH 4/4] tailcall VM only for x64 --- Zend/tests/vm_kind_tailcall_clang_windows.phpt | 3 ++- Zend/zend_vm_gen.php | 2 +- Zend/zend_vm_opcodes.h | 2 +- win32/build/confutils.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Zend/tests/vm_kind_tailcall_clang_windows.phpt b/Zend/tests/vm_kind_tailcall_clang_windows.phpt index 2ff94be22d48..f66a17ae0daf 100644 --- a/Zend/tests/vm_kind_tailcall_clang_windows.phpt +++ b/Zend/tests/vm_kind_tailcall_clang_windows.phpt @@ -1,8 +1,9 @@ --TEST-- -Tailcall VM is selected when compiled with Clang >= 19 on Windows +Tailcall VM is selected when compiled with Clang >= 19 on Windows x64 --SKIPIF-- = 1900 && (TARGET_ARCH === 'x64' || TARGET_ARCH === 'arm64')) { + if (CLANGVERS >= 1900 && TARGET_ARCH === 'x64') { AC_DEFINE('HAVE_PRESERVE_NONE', 1, 'Whether the compiler supports __attribute__((preserve_none))'); } }