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
17 changes: 17 additions & 0 deletions ext/opcache/jit/zend_jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -1594,6 +1594,23 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
goto jit_failure;
}
goto done;
case ZEND_BW_NOT:
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
op1_info = OP1_INFO();
/* Only the definitely-LONG fast path; anything else falls
* through to the VM handler (its previous behaviour). */
if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_LONG) {
break;
}
res_addr = RES_REG_ADDR();
if (!zend_jit_bw_not(&ctx, opline,
op1_info, OP1_REG_ADDR(),
RES_INFO(), res_addr)) {
goto jit_failure;
}
goto done;
case ZEND_ADD:
case ZEND_SUB:
case ZEND_MUL:
Expand Down
21 changes: 21 additions & 0 deletions ext/opcache/jit/zend_jit_ir.c
Original file line number Diff line number Diff line change
Expand Up @@ -6141,6 +6141,27 @@ static int zend_jit_long_math(zend_jit_ctx *jit, const zend_op *opline, uint32_t
return 1;
}

/* Inlined ZEND_BW_NOT for the definitely-LONG case. ~x == x ^ -1; using the
* inlined XOR path (rather than the ZEND_BW_NOT_SPEC helper) avoids a helper
* call whose result slot could alias a spilled loop-carried CV. */
static int zend_jit_bw_not(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, uint32_t res_info, zend_jit_addr res_addr)
{
ir_ref op1_lval_ref, ref;

ZEND_ASSERT((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) == MAY_BE_LONG);

op1_lval_ref = jit_Z_LVAL(jit, op1_addr);
ref = ir_XOR_L(op1_lval_ref, ir_CONST_LONG(-1));
jit_set_Z_LVAL(jit, res_addr, ref);
if (Z_MODE(res_addr) != IS_REG) {
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
}
if (!zend_jit_store_var_if_necessary(jit, opline->result.var, res_addr, res_info)) {
return 0;
}
return 1;
}

static int zend_jit_concat_helper(zend_jit_ctx *jit,
const zend_op *opline,
uint8_t op1_type,
Expand Down
17 changes: 17 additions & 0 deletions ext/opcache/jit/zend_jit_trace.c
Original file line number Diff line number Diff line change
Expand Up @@ -2008,6 +2008,7 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin
case ZEND_JMPNZ_EX:
case ZEND_BOOL:
case ZEND_BOOL_NOT:
case ZEND_BW_NOT:
ADD_OP1_TRACE_GUARD();
break;
case ZEND_ISSET_ISEMPTY_CV:
Expand Down Expand Up @@ -4465,6 +4466,22 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
}
goto done;
case ZEND_BW_NOT:
op1_info = OP1_INFO();
CHECK_OP1_TRACE_TYPE();
/* Only the definitely-LONG fast path; otherwise fall back
* to the VM handler (its previous behaviour). */
if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_LONG) {
break;
}
res_info = RES_INFO();
res_addr = RES_REG_ADDR();
if (!zend_jit_bw_not(&ctx, opline,
op1_info, OP1_REG_ADDR(),
res_info, res_addr)) {
goto jit_failure;
}
goto done;
case ZEND_BW_OR:
case ZEND_BW_AND:
case ZEND_BW_XOR:
Expand Down
43 changes: 43 additions & 0 deletions ext/opcache/tests/jit/gh22558.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--TEST--
GH-22558 (Tracing JIT: bitwise-NOT high bits leak past a mask into a typed int property)
--EXTENSIONS--
opcache
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit_buffer_size=64M
opcache.jit=tracing
; run-tests forces jit_hot_side_exit=1, which compiles side traces so eagerly
; that the buggy trace never forms — override back to the default so the bug shows.
opcache.jit_hot_side_exit=8
--FILE--
<?php
final class C {
/** @var int[] */ public array $t = [];
public int $a = 0;
public int $f = 0;
public function __construct() { for ($i = 0; $i < 256; $i++) $this->t[$i] = $i & 0xA8; }
public function add8(int $value, int $carry): void {
$a = $this->a;
$total = $a + $value + $carry;
$result = $total & 0xFF;
$this->f = $this->t[$result]
| (($total & 0x100) ? 0x01 : 0)
| (((($a & 0x0F) + ($value & 0x0F) + $carry) & 0x10) ? 0x10 : 0)
| ((~($a ^ $value) & ($a ^ $result) & 0x80) ? 0x04 : 0);
$this->a = $result;
}
}
$c = new C();
for ($i = 0; $i < 100000; $i++) {
$c->a = $i & 0xFF;
$c->add8(($i >> 8) & 0xFF, 0);
if (($c->f & ~0xFF) !== 0) {
printf("MISCOMPILED at i=%d: \$f = %d (0x%X)\n", $i, $c->f, $c->f);
break;
}
}
echo "done\n";
?>
--EXPECT--
done
Loading