Skip to content

Commit c82d921

Browse files
committed
JIT: add ZEND_BW_NOT handler for integer operands
1 parent 1b2bde4 commit c82d921

5 files changed

Lines changed: 283 additions & 0 deletions

File tree

‎ext/opcache/jit/zend_jit.c‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,23 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
16651665
goto jit_failure;
16661666
}
16671667
goto done;
1668+
case ZEND_BW_NOT:
1669+
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
1670+
break;
1671+
}
1672+
if (opline->op1_type == IS_CONST) {
1673+
break;
1674+
}
1675+
op1_info = OP1_INFO();
1676+
if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) != MAY_BE_LONG) {
1677+
break;
1678+
}
1679+
if (!zend_jit_bw_not(&ctx, opline,
1680+
op1_info, OP1_REG_ADDR(),
1681+
-1, RES_INFO(), RES_REG_ADDR())) {
1682+
goto jit_failure;
1683+
}
1684+
goto done;
16681685
case ZEND_BW_OR:
16691686
case ZEND_BW_AND:
16701687
case ZEND_BW_XOR:

‎ext/opcache/jit/zend_jit_ir.c‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5987,6 +5987,38 @@ static int zend_jit_long_math(zend_jit_ctx *jit, const zend_op *opline, uint32_t
59875987
return 1;
59885988
}
59895989

5990+
static int zend_jit_bw_not(zend_jit_ctx *jit,
5991+
const zend_op *opline,
5992+
uint32_t op1_info,
5993+
zend_jit_addr op1_addr,
5994+
uint32_t res_use_info,
5995+
uint32_t res_info,
5996+
zend_jit_addr res_addr)
5997+
{
5998+
ir_ref ref;
5999+
6000+
ZEND_ASSERT((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG);
6001+
6002+
if (Z_MODE(op1_addr) == IS_REG
6003+
&& Z_LOAD(op1_addr)
6004+
&& jit->ra[Z_SSA_VAR(op1_addr)].ref == IR_NULL) {
6005+
/* Force load */
6006+
zend_jit_use_reg(jit, op1_addr);
6007+
}
6008+
6009+
ref = ir_NOT_L(jit_Z_LVAL(jit, op1_addr));
6010+
jit_set_Z_LVAL(jit, res_addr, ref);
6011+
if (Z_MODE(res_addr) != IS_REG) {
6012+
if (!zend_jit_same_addr(op1_addr, res_addr)) {
6013+
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_LONG) {
6014+
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
6015+
}
6016+
}
6017+
}
6018+
6019+
return zend_jit_store_var_if_necessary(jit, opline->result.var, res_addr, res_info);
6020+
}
6021+
59906022
static int zend_jit_concat_helper(zend_jit_ctx *jit,
59916023
const zend_op *opline,
59926024
uint8_t op1_type,
@@ -17606,6 +17638,13 @@ static bool zend_jit_opline_supports_reg(const zend_op_array *op_array, zend_ssa
1760617638
}
1760717639
return (op1_info & MAY_BE_LONG)
1760817640
&& (op2_info & MAY_BE_LONG);
17641+
case ZEND_BW_NOT:
17642+
op1_info = OP1_INFO();
17643+
if (trace && trace->op1_type != IS_UNKNOWN) {
17644+
op1_info &= 1U << (trace->op1_type & ~(IS_TRACE_REFERENCE|IS_TRACE_INDIRECT|IS_TRACE_PACKED));
17645+
}
17646+
return opline->op1_type != IS_CONST
17647+
&& (op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG;
1760917648
case ZEND_PRE_INC:
1761017649
case ZEND_PRE_DEC:
1761117650
case ZEND_POST_INC:

‎ext/opcache/jit/zend_jit_trace.c‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,7 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin
19851985
case ZEND_STRLEN:
19861986
case ZEND_COUNT:
19871987
case ZEND_QM_ASSIGN:
1988+
case ZEND_BW_NOT:
19881989
case ZEND_FE_RESET_R:
19891990
ADD_OP1_TRACE_GUARD();
19901991
break;
@@ -4520,6 +4521,26 @@ static zend_vm_opcode_handler_t zend_jit_trace(zend_jit_trace_rec *trace_buffer,
45204521
ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
45214522
}
45224523
goto done;
4524+
case ZEND_BW_NOT:
4525+
if (opline->op1_type == IS_CONST) {
4526+
break;
4527+
}
4528+
op1_info = OP1_INFO();
4529+
CHECK_OP1_TRACE_TYPE();
4530+
if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) != MAY_BE_LONG) {
4531+
break;
4532+
}
4533+
res_use_info = zend_jit_trace_type_to_info(
4534+
STACK_MEM_TYPE(stack, EX_VAR_TO_NUM(opline->result.var)));
4535+
if (opline->result_type == IS_CV) {
4536+
res_use_info &= (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE);
4537+
}
4538+
if (!zend_jit_bw_not(&ctx, opline,
4539+
op1_info, OP1_REG_ADDR(),
4540+
res_use_info, RES_INFO(), RES_REG_ADDR())) {
4541+
goto jit_failure;
4542+
}
4543+
goto done;
45234544
case ZEND_BW_OR:
45244545
case ZEND_BW_AND:
45254546
case ZEND_BW_XOR:
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
--TEST--
2+
JIT: BW_NOT (~) on integers in a hot loop (tracing JIT), with type changes
3+
--EXTENSIONS--
4+
opcache
5+
--INI--
6+
opcache.enable=1
7+
opcache.enable_cli=1
8+
opcache.jit=tracing
9+
opcache.jit_buffer_size=32M
10+
opcache.jit_hot_loop=1
11+
opcache.jit_hot_func=1
12+
--FILE--
13+
<?php
14+
15+
function not_cv(int $x): int {
16+
$y = ~$x; // result in a CV
17+
return $y;
18+
}
19+
20+
function not_tmp(int $x): int {
21+
return ~$x & 0xFF; // result in a TMP
22+
}
23+
24+
function not_twice(int $x): int {
25+
return ~~$x;
26+
}
27+
28+
function not_chain(int $a, int $b): int {
29+
return ~$a & $b; // and-not combination
30+
}
31+
32+
$values = [0, 1, -1, 2, 255, PHP_INT_MAX, PHP_INT_MIN, PHP_INT_MAX - 1, PHP_INT_MIN + 1,
33+
0x123456789ABCDEF, -0x123456789ABCDEF];
34+
mt_srand(7);
35+
for ($i = 0; $i < 100; $i++) {
36+
$values[] = (mt_rand() << 33) ^ (mt_rand() << 11) ^ mt_rand();
37+
}
38+
39+
$bad = 0;
40+
for ($round = 0; $round < 20; $round++) {
41+
foreach ($values as $v) {
42+
// ~x == -x - 1 for every int except PHP_INT_MIN (where -x overflows)
43+
$expected = ($v === PHP_INT_MIN) ? PHP_INT_MAX : -$v - 1;
44+
if (not_cv($v) !== $expected) $bad++;
45+
if (not_tmp($v) !== ($expected & 0xFF)) $bad++;
46+
if (not_twice($v) !== $v) $bad++;
47+
if (not_chain($v, 0x0F0F) !== ($expected & 0x0F0F)) $bad++;
48+
}
49+
}
50+
var_dump($bad);
51+
52+
// Same loop bodies, now fed other types after the trace was compiled for int.
53+
function not_any($x) {
54+
return ~$x;
55+
}
56+
for ($i = 0; $i < 200; $i++) {
57+
not_any($i);
58+
}
59+
var_dump(not_any(5));
60+
var_dump(not_any(2.0));
61+
var_dump(not_any("ab") === "\x9E\x9D");
62+
foreach ([null, true, false, [], new stdClass] as $bad_value) {
63+
try {
64+
not_any($bad_value);
65+
echo "no exception\n";
66+
} catch (TypeError $e) {
67+
echo $e->getMessage(), "\n";
68+
}
69+
}
70+
var_dump(not_any(7));
71+
72+
// Undefined variable operand
73+
function not_undef() {
74+
return ~$undefined;
75+
}
76+
for ($i = 0; $i < 3; $i++) {
77+
try {
78+
not_undef();
79+
} catch (TypeError $e) {
80+
echo $e->getMessage(), "\n";
81+
}
82+
}
83+
?>
84+
--EXPECTF--
85+
int(0)
86+
int(-6)
87+
int(-3)
88+
bool(true)
89+
Cannot perform bitwise not on null
90+
Cannot perform bitwise not on true
91+
Cannot perform bitwise not on false
92+
Cannot perform bitwise not on array
93+
Cannot perform bitwise not on stdClass
94+
int(-8)
95+
96+
Warning: Undefined variable $undefined in %s on line %d
97+
Cannot perform bitwise not on null
98+
99+
Warning: Undefined variable $undefined in %s on line %d
100+
Cannot perform bitwise not on null
101+
102+
Warning: Undefined variable $undefined in %s on line %d
103+
Cannot perform bitwise not on null
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
--TEST--
2+
JIT: BW_NOT (~) on integers in a hot loop (function JIT), with type changes
3+
--EXTENSIONS--
4+
opcache
5+
--INI--
6+
opcache.enable=1
7+
opcache.enable_cli=1
8+
opcache.jit=function
9+
opcache.jit_buffer_size=32M
10+
opcache.jit_hot_loop=1
11+
opcache.jit_hot_func=1
12+
--FILE--
13+
<?php
14+
15+
function not_cv(int $x): int {
16+
$y = ~$x; // result in a CV
17+
return $y;
18+
}
19+
20+
function not_tmp(int $x): int {
21+
return ~$x & 0xFF; // result in a TMP
22+
}
23+
24+
function not_twice(int $x): int {
25+
return ~~$x;
26+
}
27+
28+
function not_chain(int $a, int $b): int {
29+
return ~$a & $b; // and-not combination
30+
}
31+
32+
$values = [0, 1, -1, 2, 255, PHP_INT_MAX, PHP_INT_MIN, PHP_INT_MAX - 1, PHP_INT_MIN + 1,
33+
0x123456789ABCDEF, -0x123456789ABCDEF];
34+
mt_srand(7);
35+
for ($i = 0; $i < 100; $i++) {
36+
$values[] = (mt_rand() << 33) ^ (mt_rand() << 11) ^ mt_rand();
37+
}
38+
39+
$bad = 0;
40+
for ($round = 0; $round < 20; $round++) {
41+
foreach ($values as $v) {
42+
// ~x == -x - 1 for every int except PHP_INT_MIN (where -x overflows)
43+
$expected = ($v === PHP_INT_MIN) ? PHP_INT_MAX : -$v - 1;
44+
if (not_cv($v) !== $expected) $bad++;
45+
if (not_tmp($v) !== ($expected & 0xFF)) $bad++;
46+
if (not_twice($v) !== $v) $bad++;
47+
if (not_chain($v, 0x0F0F) !== ($expected & 0x0F0F)) $bad++;
48+
}
49+
}
50+
var_dump($bad);
51+
52+
// Same loop bodies, now fed other types after the trace was compiled for int.
53+
function not_any($x) {
54+
return ~$x;
55+
}
56+
for ($i = 0; $i < 200; $i++) {
57+
not_any($i);
58+
}
59+
var_dump(not_any(5));
60+
var_dump(not_any(2.0));
61+
var_dump(not_any("ab") === "\x9E\x9D");
62+
foreach ([null, true, false, [], new stdClass] as $bad_value) {
63+
try {
64+
not_any($bad_value);
65+
echo "no exception\n";
66+
} catch (TypeError $e) {
67+
echo $e->getMessage(), "\n";
68+
}
69+
}
70+
var_dump(not_any(7));
71+
72+
// Undefined variable operand
73+
function not_undef() {
74+
return ~$undefined;
75+
}
76+
for ($i = 0; $i < 3; $i++) {
77+
try {
78+
not_undef();
79+
} catch (TypeError $e) {
80+
echo $e->getMessage(), "\n";
81+
}
82+
}
83+
?>
84+
--EXPECTF--
85+
int(0)
86+
int(-6)
87+
int(-3)
88+
bool(true)
89+
Cannot perform bitwise not on null
90+
Cannot perform bitwise not on true
91+
Cannot perform bitwise not on false
92+
Cannot perform bitwise not on array
93+
Cannot perform bitwise not on stdClass
94+
int(-8)
95+
96+
Warning: Undefined variable $undefined in %s on line %d
97+
Cannot perform bitwise not on null
98+
99+
Warning: Undefined variable $undefined in %s on line %d
100+
Cannot perform bitwise not on null
101+
102+
Warning: Undefined variable $undefined in %s on line %d
103+
Cannot perform bitwise not on null

0 commit comments

Comments
 (0)