Commit 88b48dc323b for php

commit 88b48dc323be893a941828dc72e6129ea5c5ec2f
Author: ndossche <7771979+ndossche@users.noreply.github.com>
Date:   Fri Oct 9 00:26:12 2026 +0200

    Fix GH-24206: Tracing JIT loses loop-carried CV when its loop PHI is reloaded from memory

    > exit_5: 0040/0038/11 CV0($string):string CV1($startOffset):int(type_only) CV3($strlen):int(r12:1) CV4($charPos):int(type_only) CV5($foundChars):int(type_only) CV6($invalid):bool CV7($i):int(rcx:1) CV8($char):string CV9($size):int(rdx:1) CV10($j):int(type_only)

    Inside the JITted loop, `$size` is in a register.
    At the start of each loop iteration, the compiled code doesn't take `$size` from that register. It reads `$size` from memory.
    That's because `$size` from before the loop isn't a single type (`[null, long]`) so it cannot be in a register _there_.
    But at the _end_ of the loop iteration, right when it backedges, JIT doesn't write this back from register into memory... It's kept type-only.

    Closes GH-24209.

diff --git a/NEWS b/NEWS
index a55a4c4d1ff..417fa0b71f2 100644
--- a/NEWS
+++ b/NEWS
@@ -77,6 +77,8 @@ PHP                                                                        NEWS
     exits). (Lazizbek Ergashev)
   . Fixed bug GH-24391 (JIT switch on constant null takes default instead of
     case 0). (ndossche)
+  . Fixed bug GH-24206 (Tracing JIT loses loop-carried CV when its loop PHI
+    is reloaded from memory). (ndossche)

 - PDO:
   . Fixed a duplicate warning when ATTR_STATEMENT_CLASS is rejected on a
diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c
index 41136bcd805..6eef49baf9c 100644
--- a/ext/opcache/jit/zend_jit_trace.c
+++ b/ext/opcache/jit/zend_jit_trace.c
@@ -3290,6 +3290,7 @@ static zend_jit_reg_var* zend_jit_trace_allocate_registers(zend_jit_trace_rec *t
 						RA_REG_FLAGS(def) |= ZREG_LOAD;
 						RA_REG_FLAGS(use) |= ZREG_STORE;
 					} else {
+						/* trace-entry value */
 						use = phi->sources[0];
 						if (zend_jit_var_supports_reg(ssa, use)) {
 							ZEND_ASSERT(!RA_HAS_REG(use));
@@ -3298,6 +3299,8 @@ static zend_jit_reg_var* zend_jit_trace_allocate_registers(zend_jit_trace_rec *t
 							count++;
 						} else {
 							RA_REG_FLAGS(def) |= ZREG_LOAD;
+							/* back-edge value */
+							RA_REG_FLAGS(phi->sources[1]) |= ZREG_STORE;
 						}
 					}
 				} else if (RA_HAS_REG(use)) {
diff --git a/ext/opcache/tests/jit/gh24206.phpt b/ext/opcache/tests/jit/gh24206.phpt
new file mode 100644
index 00000000000..258aa9d2eb1
--- /dev/null
+++ b/ext/opcache/tests/jit/gh24206.phpt
@@ -0,0 +1,65 @@
+--TEST--
+GH-24206: Tracing JIT loses loop-carried CV when its loop PHI is reloaded from memory
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.jit_buffer_size=64M
+opcache.jit=tracing
+--CREDITS--
+Fabien Potencier and Symfony contributors
+mercierj
+--FILE--
+<?php
+
+// Reduced from Symfony Mime v7.4.13 CharacterStream (MIT).
+// Copyright (c) Fabien Potencier and Symfony contributors.
+// LICENSE (from https://github.com/symfony/mime/tree/v7.4.13):
+// Copyright (c) 2010-present Fabien Potencier
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is furnished
+// to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+final class Scanner {
+    private const MAP = ["a" => 1, "\xc3" => 2, "\xa9" => 0];
+
+    public static function scan(string $s): int {
+        $n = strlen($s);
+        $count = 0;
+        for ($i = 0; $i < $n; ++$i) {
+            $char = $s[$i];
+            $size = self::MAP[$char];
+            if ($size == 0) {
+                return -1;
+            }
+            for ($j = 1; $j < $size; ++$j) {
+            }
+            $i += $j - 1;
+            ++$count;
+        }
+        return $count;
+    }
+}
+
+var_dump(Scanner::scan(str_repeat("a", 200) . "\xc3\xa9" . "aaa"));
+
+?>
+--EXPECT--
+int(204)