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)