Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions ext/opcache/jit/zend_jit_ir.c
Original file line number Diff line number Diff line change
Expand Up @@ -17476,6 +17476,20 @@ static bool zend_jit_opline_supports_reg(const zend_op_array *op_array, zend_ssa

static bool zend_jit_var_supports_reg(zend_ssa *ssa, int var)
{
/* A transient assignment to a live loop-carried CV needs an explicit,
* control-dependent store. Its spill must not be hoisted before the
* loop guards, where the incoming value is still observable. */
if (ssa->vars[var].definition >= 0
&& !ssa->vars[var].phi_use_chain
&& ssa->ops[ssa->vars[var].definition].op1_def == var) {
int use = ssa->ops[ssa->vars[var].definition].op1_use;

if (use >= 0 && !ssa->vars[use].no_val
&& ssa->vars[use].definition_phi
&& (ssa->cfg.blocks[ssa->vars[use].definition_phi->block].flags & ZEND_BB_LOOP_HEADER)) {
return 0;
}
}
if (ssa->vars[var].no_val) {
/* we don't need the value */
return 0;
Expand Down
80 changes: 80 additions & 0 deletions ext/opcache/tests/jit/gh24683_b.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
--TEST--
GH-24683 (B): tracing JIT must preserve overwritten runtime CV values
--EXTENSIONS--
opcache
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip 64-bit only"); ?>
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit=tracing
opcache.jit_buffer_size=64M
opcache.jit_hot_loop=61
opcache.jit_hot_func=127
opcache.jit_hot_return=8
opcache.jit_hot_side_exit=8
opcache.file_update_protection=0
--FILE--
<?php
// php -d opcache.enable_cli=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=64M repro.php
//
// Case 1 stores to $ram and moves on to case 2. Case 2 sets $x to a value it
// only knows at run time (0x800E0000 | $zero, where $zero is 0), uses it as an
// address, and overwrites $x with the byte loaded from there (4). Once the loop
// trace is compiled, $x sometimes comes back as 0x800E0000 instead.

final class Bus
{
public function read8(int $address): int { return 0; }
}

$fn = static function (Bus $bus, array &$r, array &$ram, array &$sp, int $pc): int {
$value = $r[4];
$base = $r[15];
$zero = $r[0];
while (true) {
switch ($pc) {
case 1:
$a = ($base + 2) & 0xFFFFFFFF;
{
$p = $a & 0x1FFFFFFF;
$inRam = $a < 0xC0000000 && $p < 0x00800000;
if ($inRam) { $k = ($p & 0x1FFFFF) >> 2; $ram[$k] = ($ram[$k] & ~(65535 << (($p & 3) << 3))) | (($value & 65535) << (($p & 3) << 3)); }
}
$pc = 2;
continue 2;
case 2:
$x = 0x800E0000 | $zero;
$a = ($x + 0xFFFFEDED) & 0xFFFFFFFF;
$p = $a & 0x1FFFFFFF;
$byte = ((($inRam ? ($ram[($p & 0x1FFFFF) >> 2] >> (($p & 3) << 3)) & 0xFF : ($inSp ? ($sp[($p - 0x1F800000) >> 2] >> (($p & 3) << 3)) & 0xFF : $bus->read8($a))) ^ 0x80) - 0x80) & 0xFFFFFFFF;
$x = $byte;
$pc = 3;
continue 2;
default:
goto leave;
}
}
leave:
$r[12] = $x;
return $pc;
};

$bus = new Bus();
$ram = array_fill(0, 0x80000, 0);
$ram[0xDEDED >> 2] = 0x0003047F; // the byte at 0xDEDED is 4
$sp = [];
$wrong = 0;
for ($k = 0; $k < 300; $k++) {
$r = array_fill(0, 32, 0);
$r[4] = 2;
$fn($bus, $r, $ram, $sp, 1);
if ($r[12] !== 4) {
if ($wrong++ === 0) {
printf("call %d: \$x is 0x%X, expected 4\n", $k, $r[12]);
}
}
}
echo "wrong: $wrong\n";
--EXPECT--
wrong: 0
47 changes: 47 additions & 0 deletions ext/opcache/tests/jit/gh24683_b_loop.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--TEST--
GH-24683 (B): loop guards must observe the incoming CV value
--EXTENSIONS--
opcache
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip 64-bit only"); ?>
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit=tracing
opcache.jit_buffer_size=64M
opcache.jit_hot_loop=61
opcache.jit_hot_func=127
opcache.jit_hot_return=8
opcache.jit_hot_side_exit=8
opcache.file_update_protection=0
--FILE--
<?php
function test(array $r, int $pc) {
$zero = $r[0];
$x = $r[2];
while (true) {
switch ($pc) {
case 2:
$x = 0x800E0000 | $zero;
$a = ($x + 0xFFFFEDED) & 0xFFFFFFFF;
$p = $a & 0x1FFFFFFF;
$byte = ((($r[1] >> (($p & 3) << 3)) & 0xFF) ^ 0x80) - 0x80;
$x = $byte;
$pc = 3;
continue 2;
default: goto leave;
}
}
leave: return $x;
}
$wrong = 0;
for ($i = 0; $i < 1000; $i++) {
$r = [0, 0x0003047F, 4];
$pc = $i < 500 ? 2 : 3;
if (test($r, $pc) !== 4) {
$wrong++;
}
}
echo "wrong: $wrong\n";
--EXPECT--
wrong: 0
Loading