From cfe3b473d8027a4f82c9a94451c02abaa34a8544 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Fri, 9 Oct 2026 00:26:12 +0200 Subject: [PATCH] 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. --- ext/opcache/jit/zend_jit_trace.c | 3 ++ ext/opcache/tests/jit/gh24206.phpt | 65 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 ext/opcache/tests/jit/gh24206.phpt diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 5aa62d2e0878..9e53faaf0e08 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 000000000000..258aa9d2eb1d --- /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-- + 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)