Skip to content

Commit 2bd7cea

Browse files
committed
JIT: Fold IS_UNDEF check into dereference of typed property reads for tracing JIT
When the tracing JIT reads a typed property with a known result type into a reg, the value is either of that type, a reference or IS_UNDEF. Instead of guarding IS_UNDEF separately before making a decision on the type, exit only if the value is neither of the expected type nor a reference. This removes a compare and branch from the hot path of, e.g., reads of $this->prop in loops. Example PHP code: ```php class Counter { public int $x = 1; function sum($n) { $s = 0; for ($i = 0; $i < $n; $i++) { $s += $this->x; } return $s; } } var_dump((new Counter)->sum(1000)); ``` The loop compiled previously to: ```asm .L1: movq (%rdi), %rsi leaq 8(%rsi), %rdi .L2: movq (%rdi), %rsi movq %rsi, %rdi addq %rcx, %rdi jo jit$$trace_exit_5 leaq 1(%rdx), %rdx movabsq $EG(vm_interrupt), %rcx movb (%rcx), %cl testb %cl, %cl jne jit$$trace_exit_6 movq %rdi, %rcx .L3: cmpq %rax, %rdx jge jit$$trace_exit_3 movq 0x20(%r14), %rsi leaq 0x28(%rsi), %rdi movl 0x30(%rsi), %esi testl %esi, %esi je jit$$trace_exit_4 cmpl $4, %esi je .L2 jmp .L1 ``` Notice that in `.L3` there's two checks on `%esi` (the type info): one for the IS_UNDEF (via `testl`) and one to check for an int (`$4`). The new code moves the check for reference type to the `.L1` header and only checks for the int type in `.L3`, reducing the code executed in the hot loop: ```asm .L1: cmpl $0x30a, %esi jne jit$$trace_exit_4 movq (%rdi), %rsi leaq 8(%rsi), %rdi .L2: movq (%rdi), %rsi movq %rsi, %rdi addq %rcx, %rdi jo jit$$trace_exit_5 leaq 1(%rdx), %rdx movabsq $EG(vm_interrupt), %rcx movb (%rcx), %cl testb %cl, %cl jne jit$$trace_exit_6 movq %rdi, %rcx .L3: cmpq %rax, %rdx jge jit$$trace_exit_3 movq 0x20(%r14), %rsi leaq 0x28(%rsi), %rdi movl 0x30(%rsi), %esi cmpl $4, %esi je .L2 jmp .L1 ```
1 parent a8caefc commit 2bd7cea

2 files changed

Lines changed: 105 additions & 16 deletions

File tree

‎ext/opcache/jit/zend_jit_ir.c‎

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11831,29 +11831,27 @@ static int zend_jit_rope(zend_jit_ctx *jit, const zend_op *opline, uint32_t op2_
1183111831
return 1;
1183211832
}
1183311833

11834-
static int zend_jit_zval_copy_deref_reg(zend_jit_ctx *jit, zend_jit_addr res_addr, uint32_t res_info, zend_jit_addr val_addr, ir_ref type, ir_ref *values)
11834+
/* If exit_addr is set, the value may also be IS_UNDEF */
11835+
static int zend_jit_zval_copy_deref_reg(zend_jit_ctx *jit, zend_jit_addr res_addr, uint32_t res_info, zend_jit_addr val_addr, ir_ref type, ir_ref *values, const void *exit_addr)
1183511836
{
1183611837
ir_ref if_type, val;
1183711838

1183811839
if (res_info == MAY_BE_LONG) {
1183911840
if_type = ir_IF(ir_EQ(type, ir_CONST_U32(IS_LONG)));
11840-
ir_IF_TRUE(if_type);
11841-
val = jit_ZVAL_ADDR(jit, val_addr);
11842-
ir_END_PHI_list(*values, val);
11843-
ir_IF_FALSE(if_type);
11844-
val = ir_ADD_OFFSET(jit_Z_PTR(jit, val_addr), offsetof(zend_reference, val));
11845-
ir_END_PHI_list(*values, val);
1184611841
} else if (res_info == MAY_BE_DOUBLE) {
1184711842
if_type = ir_IF(ir_EQ(type, ir_CONST_U32(IS_DOUBLE)));
11848-
ir_IF_TRUE(if_type);
11849-
val = jit_ZVAL_ADDR(jit, val_addr);
11850-
ir_END_PHI_list(*values, val);
11851-
ir_IF_FALSE(if_type);
11852-
val = ir_ADD_OFFSET(jit_Z_PTR(jit, val_addr), offsetof(zend_reference, val));
11853-
ir_END_PHI_list(*values, val);
1185411843
} else {
1185511844
ZEND_UNREACHABLE();
1185611845
}
11846+
ir_IF_TRUE(if_type);
11847+
val = jit_ZVAL_ADDR(jit, val_addr);
11848+
ir_END_PHI_list(*values, val);
11849+
ir_IF_FALSE(if_type);
11850+
if (exit_addr) {
11851+
ir_GUARD(ir_EQ(type, ir_CONST_U32(IS_REFERENCE_EX)), ir_CONST_ADDR(exit_addr));
11852+
}
11853+
val = ir_ADD_OFFSET(jit_Z_PTR(jit, val_addr), offsetof(zend_reference, val));
11854+
ir_END_PHI_list(*values, val);
1185711855
return 1;
1185811856
}
1185911857

@@ -14290,6 +14288,7 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
1429014288
ir_ref end_inputs = IR_UNUSED;
1429114289
ir_ref slow_inputs = IR_UNUSED;
1429214290
ir_ref end_values = IR_UNUSED;
14291+
const void *undef_exit_addr = NULL;
1429314292

1429414293
ZEND_ASSERT(opline->op2_type == IS_CONST);
1429514294
ZEND_ASSERT(op1_info & MAY_BE_OBJECT);
@@ -14544,8 +14543,15 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
1454414543
if (!exit_addr) {
1454514544
return 0;
1454614545
}
14547-
prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
14548-
ir_GUARD(prop_type_ref, ir_CONST_ADDR(exit_addr));
14546+
if (opline->opcode != ZEND_FETCH_OBJ_W
14547+
&& !(res_info & MAY_BE_GUARD)
14548+
&& Z_MODE(res_addr) == IS_REG) {
14549+
/* perform IS_UNDEF check together with the dereference of the result */
14550+
undef_exit_addr = exit_addr;
14551+
} else {
14552+
prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
14553+
ir_GUARD(prop_type_ref, ir_CONST_ADDR(exit_addr));
14554+
}
1454914555
}
1455014556
} else {
1455114557
prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
@@ -14659,7 +14665,7 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit,
1465914665
} else if (Z_MODE(res_addr) == IS_REG) {
1466014666
prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
1466114667

14662-
if (!zend_jit_zval_copy_deref_reg(jit, res_addr, res_info & ~MAY_BE_GUARD, prop_addr, prop_type_ref, &end_values)) {
14668+
if (!zend_jit_zval_copy_deref_reg(jit, res_addr, res_info & ~MAY_BE_GUARD, prop_addr, prop_type_ref, &end_values, undef_exit_addr)) {
1466314669
return 0;
1466414670
}
1466514671
} else {
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
--TEST--
2+
JIT FETCH_OBJ: Typed property that becomes a reference or undefined
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.file_update_protection=0
7+
opcache.jit=tracing
8+
opcache.jit_hot_loop=1
9+
opcache.jit_hot_func=1
10+
opcache.jit_hot_return=1
11+
opcache.jit_hot_side_exit=1
12+
--EXTENSIONS--
13+
opcache
14+
--FILE--
15+
<?php
16+
17+
class A {
18+
public int $x = 1;
19+
public float $f = 1.5;
20+
21+
function becomes_ref() {
22+
$s = 0;
23+
for ($i = 0; $i < 300; $i++) {
24+
$s += $this->x;
25+
if ($i == 150) {
26+
$r = &$this->x;
27+
$r = 2;
28+
}
29+
}
30+
return $s;
31+
}
32+
33+
function becomes_ref_float() {
34+
$s = 0.0;
35+
for ($i = 0; $i < 300; $i++) {
36+
$s += $this->f;
37+
if ($i == 150) {
38+
$r = &$this->f;
39+
$r = 2.5;
40+
}
41+
}
42+
return $s;
43+
}
44+
45+
function unset_isset() {
46+
$s = 0;
47+
for ($i = 0; $i < 300; $i++) {
48+
$s += $this->x ?? 7;
49+
if ($i == 150) unset($this->x);
50+
}
51+
return $s;
52+
}
53+
}
54+
55+
class M {
56+
public int $x = 1;
57+
58+
function __get($name) {
59+
return 1000;
60+
}
61+
62+
function unset_get() {
63+
$s = 0;
64+
for ($i = 0; $i < 300; $i++) {
65+
$s += $this->x;
66+
if ($i == 150) unset($this->x);
67+
}
68+
return $s;
69+
}
70+
}
71+
72+
var_dump((new A)->becomes_ref());
73+
var_dump((new A)->becomes_ref_float());
74+
var_dump((new A)->unset_isset());
75+
var_dump((new M)->unset_get());
76+
77+
?>
78+
--EXPECT--
79+
int(449)
80+
float(599)
81+
int(151)
82+
int(1194)
83+
int(149151)

0 commit comments

Comments
 (0)