Commit a9354607218 for php.net

commit a935460721865969e628079366a4343b5bd4870b
Author: henderkes <m@pyc.ac>
Date:   Tue Apr 21 14:48:58 2026 +0700

    Enable Tailcall VM with Clang >= 19 on Windows x86-64

    Closes GH-21619

diff --git a/NEWS b/NEWS
index 22762f98c2c..18d12d02dbb 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,8 @@ PHP                                                                        NEWS
   . Fixed bug GH-20174 (Assertion failure in
     ReflectionProperty::skipLazyInitialization after failed LazyProxy
     initialization). (Arnaud)
+  . Enabled the TAILCALL VM on Windows when compiling with Clang >= 19 x86_64.
+    (henderkes)

 - BCMath:
   . Added NUL-byte validation to BCMath functions. (jorgsowa)
diff --git a/UPGRADING b/UPGRADING
index 869e265af8a..f24ea681be2 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -304,6 +304,8 @@ PHP 8.6 UPGRADE NOTES
     callbacks from internal functions and providing for better insight for the
     JIT.
   . The performance of the TAILCALL VM has been improved.
+  . The TAILCALL VM is now enabled on Windows when compiling with Clang >= 19
+    x86_64.

 - DOM:
   . Made splitText() faster and consume less memory.
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 00000000000..f66a17ae0da
--- /dev/null
+++ b/Zend/tests/vm_kind_tailcall_clang_windows.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Tailcall VM is selected when compiled with Clang >= 19 on Windows x64
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY !== 'Windows') die('skip Windows only');
+if (php_uname('m') !== 'AMD64') die('skip x64 only');
+
+ob_start();
+phpinfo(INFO_GENERAL);
+$info = ob_get_clean();
+
+if (!preg_match('/Compiler => clang version (\d+)/', $info, $m)) {
+    die('skip not compiled with clang');
+}
+
+if ((int)$m[1] < 19) {
+    die('skip requires clang >= 19');
+}
+?>
+--FILE--
+<?php
+var_dump(ZEND_VM_KIND);
+?>
+--EXPECT--
+string(21) "ZEND_VM_KIND_TAILCALL"
diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php
index cef44695be8..d26d6a166dd 100755
--- a/Zend/zend_vm_gen.php
+++ b/Zend/zend_vm_gen.php
@@ -2497,7 +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 .= "#elif defined(HAVE_MUSTTAIL) && defined(HAVE_PRESERVE_NONE) && (defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__))\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";
diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h
index b63177e2421..92b46e6628f 100644
Binary files a/Zend/zend_vm_opcodes.h and b/Zend/zend_vm_opcodes.h differ
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index c4b88b74f64..e802d213aff 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 fd1e9ce0be1..e695a00c815 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') {
+            AC_DEFINE('HAVE_PRESERVE_NONE', 1, 'Whether the compiler supports __attribute__((preserve_none))');
+        }
 	}

 	if (!CLANG_TOOLSET) {