Skip to content

Commit bf784bd

Browse files
committed
Elide ZEND_VERIFY_RETURN_TYPE for directly provable instances of $this
1 parent b373a1f commit bf784bd

2 files changed

Lines changed: 152 additions & 7 deletions

File tree

Zend/zend_compile.c

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2642,6 +2642,53 @@ static void zend_compile_memoized_expr(znode *result, zend_ast *expr, uint32_t t
26422642
}
26432643
/* }}} */
26442644

2645+
static bool zend_is_this_instance_of_name(const zend_string *type_name)
2646+
{
2647+
if (zend_string_equals_ci(CG(active_class_entry)->name, type_name)) {
2648+
return true;
2649+
}
2650+
if (zend_string_equals_ci(type_name, ZSTR_KNOWN(ZEND_STR_SELF))) {
2651+
return true;
2652+
}
2653+
if (zend_string_equals_ci(type_name, ZSTR_KNOWN(ZEND_STR_PARENT))) {
2654+
return true;
2655+
}
2656+
2657+
ZEND_ASSERT((CG(active_class_entry)->ce_flags & ZEND_ACC_LINKED) == 0);
2658+
if (CG(active_class_entry)->num_interfaces) {
2659+
for (uint32_t i = 0; i < CG(active_class_entry)->num_interfaces; i++) {
2660+
if (zend_string_equals_ci(CG(active_class_entry)->interface_names[i].lc_name, type_name)) {
2661+
return true;
2662+
}
2663+
}
2664+
}
2665+
const zend_string *parent_name = CG(active_class_entry)->parent_name;
2666+
if (parent_name && zend_string_equals_ci(parent_name, type_name)) {
2667+
return true;
2668+
}
2669+
2670+
return false;
2671+
}
2672+
2673+
static bool zend_is_this_valid_for_return_type(zend_type type)
2674+
{
2675+
if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_OBJECT|MAY_BE_STATIC)) {
2676+
return true;
2677+
}
2678+
2679+
const zend_type *single_type;
2680+
ZEND_TYPE_FOREACH(type, single_type) {
2681+
if (ZEND_TYPE_HAS_NAME(*single_type)) {
2682+
const zend_string *name = ZEND_TYPE_NAME(*single_type);
2683+
if (zend_is_this_instance_of_name(name)) {
2684+
return true;
2685+
}
2686+
}
2687+
} ZEND_TYPE_FOREACH_END();
2688+
2689+
return false;
2690+
}
2691+
26452692
static void zend_emit_return_type_check(
26462693
znode *expr, const zend_arg_info *return_info, bool implicit) /* {{{ */
26472694
{
@@ -2696,16 +2743,14 @@ static void zend_emit_return_type_check(
26962743
/* we don't need run-time check */
26972744
return;
26982745
}
2699-
2746+
27002747
/* If return type contains static and we are returning $this
27012748
* (determined by checking if the previous opcode is ZEND_FETCH_THIS)
27022749
* then we don't need to check the return type */
2703-
if (expr && ZEND_TYPE_CONTAINS_CODE(type, IS_STATIC)) {
2704-
const zend_op_array *op_array = CG(active_op_array);
2705-
zend_op previous = op_array->opcodes[op_array->last-1];
2706-
if (previous.opcode == ZEND_FETCH_THIS) {
2707-
return;
2708-
}
2750+
const zend_op_array *op_array = CG(active_op_array);
2751+
zend_op previous = op_array->opcodes[op_array->last-1];
2752+
if (expr && previous.opcode == ZEND_FETCH_THIS && zend_is_this_valid_for_return_type(type)) {
2753+
return;
27092754
}
27102755

27112756
opline = zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL);
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
--TEST--
2+
Return type check elision
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.opt_debug_level=0x20000
8+
opcache.preload=
9+
--EXTENSIONS--
10+
opcache
11+
--FILE--
12+
<?php
13+
14+
class C1 {
15+
public function foo(): self {
16+
return $this;
17+
}
18+
}
19+
20+
class C2 {}
21+
class C3 extends C2 {
22+
public function foo(): C3 {
23+
return $this;
24+
}
25+
}
26+
27+
interface I1 {}
28+
29+
class C4 implements I1 {
30+
public function foo(): I1 {
31+
return $this;
32+
}
33+
}
34+
35+
interface I2 extends I1 {}
36+
37+
class C5 implements I2 {
38+
public function foo(): I1 {
39+
return $this;
40+
}
41+
}
42+
43+
class C6 extends C5 {
44+
public function foo(): I2 {
45+
return $this;
46+
}
47+
}
48+
49+
?>
50+
--EXPECTF--
51+
$_main:
52+
; (lines=5, args=0, vars=0, tmps=0)
53+
; (after optimizer)
54+
; %s:1-39
55+
0000 DECLARE_CLASS string("c4")
56+
0001 DECLARE_CLASS string("i2")
57+
0002 DECLARE_CLASS string("c5")
58+
0003 DECLARE_CLASS_DELAYED string("c6") string("c5")
59+
0004 RETURN int(1)
60+
61+
C1::foo:
62+
; (lines=2, args=0, vars=0, tmps=1)
63+
; (after optimizer)
64+
; %s:4-6
65+
0000 T0 = FETCH_THIS
66+
0001 RETURN T0
67+
68+
C3::foo:
69+
; (lines=2, args=0, vars=0, tmps=1)
70+
; (after optimizer)
71+
; %s:11-13
72+
0000 T0 = FETCH_THIS
73+
0001 RETURN T0
74+
75+
C4::foo:
76+
; (lines=2, args=0, vars=0, tmps=1)
77+
; (after optimizer)
78+
; %s:19-21
79+
0000 T0 = FETCH_THIS
80+
0001 RETURN T0
81+
82+
C5::foo:
83+
; (lines=3, args=0, vars=0, tmps=1)
84+
; (after optimizer)
85+
; %s:27-29
86+
0000 T0 = FETCH_THIS
87+
0001 VERIFY_RETURN_TYPE T0
88+
0002 RETURN T0
89+
LIVE RANGES:
90+
0: 0001 - 0002 (tmp/var)
91+
92+
C6::foo:
93+
; (lines=3, args=0, vars=0, tmps=1)
94+
; (after optimizer)
95+
; %s:33-35
96+
0000 T0 = FETCH_THIS
97+
0001 VERIFY_RETURN_TYPE T0
98+
0002 RETURN T0
99+
LIVE RANGES:
100+
0: 0001 - 0002 (tmp/var)

0 commit comments

Comments
 (0)