Skip to content

Commit 56cf09f

Browse files
authored
Improve optimizer support for class constants (php#13438)
The following optimizations are added: - Constant folding of final class constants - Type inference of typed class constants
1 parent 0aadc7e commit 56cf09f

8 files changed

+401
-25
lines changed

Zend/Optimizer/pass1.c

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -155,33 +155,26 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
155155
}
156156
break;
157157

158-
case ZEND_FETCH_CLASS_CONSTANT:
159-
if (opline->op2_type == IS_CONST &&
160-
Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) {
161-
162-
zend_class_entry *ce = zend_optimizer_get_class_entry_from_op1(
163-
ctx->script, op_array, opline);
164-
if (ce) {
165-
zend_class_constant *cc = zend_hash_find_ptr(
166-
&ce->constants_table, Z_STR(ZEND_OP2_LITERAL(opline)));
167-
if (cc && !(ZEND_CLASS_CONST_FLAGS(cc) & ZEND_ACC_DEPRECATED) && (ZEND_CLASS_CONST_FLAGS(cc) & ZEND_ACC_PPP_MASK) == ZEND_ACC_PUBLIC && !(ce->ce_flags & ZEND_ACC_TRAIT)) {
168-
zval *c = &cc->value;
169-
if (Z_TYPE_P(c) == IS_CONSTANT_AST) {
170-
zend_ast *ast = Z_ASTVAL_P(c);
171-
if (ast->kind != ZEND_AST_CONSTANT
172-
|| !zend_optimizer_get_persistent_constant(zend_ast_get_constant_name(ast), &result, 1)
173-
|| Z_TYPE(result) == IS_CONSTANT_AST) {
174-
break;
175-
}
176-
} else {
177-
ZVAL_COPY_OR_DUP(&result, c);
178-
}
179-
180-
replace_by_const_or_qm_assign(op_array, opline, &result);
181-
}
158+
case ZEND_FETCH_CLASS_CONSTANT: {
159+
bool is_prototype;
160+
const zend_class_constant *cc = zend_fetch_class_const_info(ctx->script, op_array, opline, &is_prototype);
161+
if (!cc || is_prototype) {
162+
break;
163+
}
164+
const zval *c = &cc->value;
165+
if (Z_TYPE_P(c) == IS_CONSTANT_AST) {
166+
zend_ast *ast = Z_ASTVAL_P(c);
167+
if (ast->kind != ZEND_AST_CONSTANT
168+
|| !zend_optimizer_get_persistent_constant(zend_ast_get_constant_name(ast), &result, 1)
169+
|| Z_TYPE(result) == IS_CONSTANT_AST) {
170+
break;
182171
}
172+
} else {
173+
ZVAL_COPY_OR_DUP(&result, c);
183174
}
175+
replace_by_const_or_qm_assign(op_array, opline, &result);
184176
break;
177+
}
185178

186179
case ZEND_DO_ICALL: {
187180
zend_op *send1_opline = opline - 1;

Zend/Optimizer/zend_inference.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3911,9 +3911,21 @@ static zend_always_inline zend_result _zend_update_type_info(
39113911
UPDATE_SSA_OBJ_TYPE(zend_ce_closure, /* is_instanceof */ false, ssa_op->result_def);
39123912
break;
39133913
case ZEND_FETCH_CONSTANT:
3914-
case ZEND_FETCH_CLASS_CONSTANT:
39153914
UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
39163915
break;
3916+
case ZEND_FETCH_CLASS_CONSTANT: {
3917+
bool is_prototype;
3918+
const zend_class_constant *cc = zend_fetch_class_const_info(script, op_array, opline, &is_prototype);
3919+
if (!cc || !ZEND_TYPE_IS_SET(cc->type)) {
3920+
UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
3921+
break;
3922+
}
3923+
UPDATE_SSA_TYPE(zend_convert_type(script, cc->type, &ce), ssa_op->result_def);
3924+
if (ce) {
3925+
UPDATE_SSA_OBJ_TYPE(ce, /* is_instanceof */ true, ssa_op->result_def);
3926+
}
3927+
break;
3928+
}
39173929
case ZEND_STRLEN:
39183930
case ZEND_COUNT:
39193931
case ZEND_FUNC_NUM_ARGS:

Zend/Optimizer/zend_optimizer.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,64 @@ zend_class_entry *zend_optimizer_get_class_entry_from_op1(
831831
return NULL;
832832
}
833833

834+
const zend_class_constant *zend_fetch_class_const_info(
835+
const zend_script *script, const zend_op_array *op_array, const zend_op *opline, bool *is_prototype) {
836+
const zend_class_entry *ce = NULL;
837+
bool is_static_reference = false;
838+
839+
if (!opline || !op_array || opline->op2_type != IS_CONST || Z_TYPE_P(CRT_CONSTANT(opline->op2)) != IS_STRING) {
840+
return NULL;
841+
}
842+
if (opline->op1_type == IS_CONST) {
843+
zval *op1 = CRT_CONSTANT(opline->op1);
844+
if (Z_TYPE_P(op1) == IS_STRING) {
845+
if (script) {
846+
ce = zend_optimizer_get_class_entry(script, op_array, Z_STR_P(op1 + 1));
847+
} else {
848+
zend_class_entry *tmp = zend_hash_find_ptr(EG(class_table), Z_STR_P(op1 + 1));
849+
if (tmp != NULL) {
850+
if (tmp->type == ZEND_INTERNAL_CLASS) {
851+
ce = tmp;
852+
} else if (tmp->type == ZEND_USER_CLASS
853+
&& tmp->info.user.filename
854+
&& tmp->info.user.filename == op_array->filename) {
855+
ce = tmp;
856+
}
857+
}
858+
}
859+
}
860+
} else if (opline->op1_type == IS_UNUSED
861+
&& op_array->scope && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT)
862+
&& !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) {
863+
int fetch_type = opline->op1.num & ZEND_FETCH_CLASS_MASK;
864+
if (fetch_type == ZEND_FETCH_CLASS_SELF) {
865+
ce = op_array->scope;
866+
} else if (fetch_type == ZEND_FETCH_CLASS_STATIC) {
867+
ce = op_array->scope;
868+
is_static_reference = true;
869+
} else if (fetch_type == ZEND_FETCH_CLASS_PARENT) {
870+
if (op_array->scope->ce_flags & ZEND_ACC_LINKED) {
871+
ce = op_array->scope->parent;
872+
}
873+
}
874+
}
875+
if (!ce || (ce->ce_flags & ZEND_ACC_TRAIT)) {
876+
return NULL;
877+
}
878+
zend_class_constant *const_info = zend_hash_find_ptr(&ce->constants_table, Z_STR_P(CRT_CONSTANT(opline->op2)));
879+
if (!const_info) {
880+
return NULL;
881+
}
882+
if ((ZEND_CLASS_CONST_FLAGS(const_info) & ZEND_ACC_DEPRECATED)
883+
|| ((ZEND_CLASS_CONST_FLAGS(const_info) & ZEND_ACC_PPP_MASK) != ZEND_ACC_PUBLIC && const_info->ce != op_array->scope)) {
884+
return NULL;
885+
}
886+
*is_prototype = is_static_reference
887+
&& !(const_info->ce->ce_flags & ZEND_ACC_FINAL) && !(ZEND_CLASS_CONST_FLAGS(const_info) & ZEND_ACC_FINAL);
888+
889+
return const_info;
890+
}
891+
834892
zend_function *zend_optimizer_get_called_func(
835893
zend_script *script, zend_op_array *op_array, zend_op *opline, bool *is_prototype)
836894
{

Zend/Optimizer/zend_optimizer_internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ zend_class_entry *zend_optimizer_get_class_entry(
105105
const zend_script *script, const zend_op_array *op_array, zend_string *lcname);
106106
zend_class_entry *zend_optimizer_get_class_entry_from_op1(
107107
const zend_script *script, const zend_op_array *op_array, const zend_op *opline);
108+
const zend_class_constant *zend_fetch_class_const_info(
109+
const zend_script *script, const zend_op_array *op_array, const zend_op *opline, bool *is_prototype);
108110

109111
void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx);
110112
void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx);
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
--TEST--
2+
Test type inference of class consts - reference by static and self
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 Test1 {
15+
public const FOO = 42;
16+
public final const BAR = 42;
17+
public const int BAZ = 42;
18+
19+
public function getSelfFoo(): int {
20+
return self::FOO;
21+
}
22+
23+
public function getSelfBar(): int {
24+
return self::BAR;
25+
}
26+
27+
public function getSelfBaz(): int {
28+
return self::BAZ;
29+
}
30+
31+
public function getStaticFoo(): int {
32+
return static::FOO;
33+
}
34+
35+
public function getStaticBar(): int {
36+
return static::BAR;
37+
}
38+
39+
public function getStaticBaz(): int {
40+
return static::BAZ;
41+
}
42+
}
43+
44+
?>
45+
--EXPECTF--
46+
$_main:
47+
; (lines=1, args=0, vars=0, tmps=0)
48+
; (after optimizer)
49+
; %s
50+
0000 RETURN int(1)
51+
52+
Test1::getSelfFoo:
53+
; (lines=1, args=0, vars=0, tmps=0)
54+
; (after optimizer)
55+
; %s
56+
0000 RETURN int(42)
57+
58+
Test1::getSelfBar:
59+
; (lines=1, args=0, vars=0, tmps=0)
60+
; (after optimizer)
61+
; %s
62+
0000 RETURN int(42)
63+
64+
Test1::getSelfBaz:
65+
; (lines=1, args=0, vars=0, tmps=0)
66+
; (after optimizer)
67+
; %s
68+
0000 RETURN int(42)
69+
70+
Test1::getStaticFoo:
71+
; (lines=3, args=0, vars=0, tmps=1)
72+
; (after optimizer)
73+
; %s
74+
0000 T0 = FETCH_CLASS_CONSTANT (static) (exception) string("FOO")
75+
0001 VERIFY_RETURN_TYPE T0
76+
0002 RETURN T0
77+
LIVE RANGES:
78+
0: 0001 - 0002 (tmp/var)
79+
80+
Test1::getStaticBar:
81+
; (lines=1, args=0, vars=0, tmps=0)
82+
; (after optimizer)
83+
; %s
84+
0000 RETURN int(42)
85+
86+
Test1::getStaticBaz:
87+
; (lines=2, args=0, vars=0, tmps=1)
88+
; (after optimizer)
89+
; %s
90+
0000 T0 = FETCH_CLASS_CONSTANT (static) (exception) string("BAZ")
91+
0001 RETURN T0
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
--TEST--
2+
Test type inference of class consts - reference by parent
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 Test1 {
15+
public const FOO = 42;
16+
public final const BAR = 42;
17+
public const int BAZ = 42;
18+
}
19+
20+
final class Test2 extends Test1 {
21+
public function getParentFoo(): int {
22+
return parent::FOO;
23+
}
24+
25+
public function getParentBar(): int {
26+
return parent::BAR;
27+
}
28+
29+
public function getParentBaz(): int {
30+
return parent::BAZ;
31+
}
32+
}
33+
34+
?>
35+
--EXPECTF--
36+
$_main:
37+
; (lines=1, args=0, vars=0, tmps=0)
38+
; (after optimizer)
39+
; %s
40+
0000 RETURN int(1)
41+
42+
Test2::getParentFoo:
43+
; (lines=1, args=0, vars=0, tmps=0)
44+
; (after optimizer)
45+
; %s
46+
0000 RETURN int(42)
47+
48+
Test2::getParentBar:
49+
; (lines=1, args=0, vars=0, tmps=0)
50+
; (after optimizer)
51+
; %s
52+
0000 RETURN int(42)
53+
54+
Test2::getParentBaz:
55+
; (lines=1, args=0, vars=0, tmps=0)
56+
; (after optimizer)
57+
; %s
58+
0000 RETURN int(42)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
--TEST--
2+
Test type inference of class consts - reference by class name
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 Test1 {
15+
public const FOO = 42;
16+
public final const BAR = 42;
17+
public const int BAZ = 42;
18+
}
19+
20+
class Test3 {
21+
public function getTestFoo(): int {
22+
return Test1::FOO;
23+
}
24+
25+
public function getTestBar(): int {
26+
return Test1::BAR;
27+
}
28+
29+
public function getTestBaz(): int {
30+
return Test1::BAZ;
31+
}
32+
}
33+
34+
?>
35+
--EXPECTF--
36+
$_main:
37+
; (lines=1, args=0, vars=0, tmps=0)
38+
; (after optimizer)
39+
; %s
40+
0000 RETURN int(1)
41+
42+
Test3::getTestFoo:
43+
; (lines=1, args=0, vars=0, tmps=0)
44+
; (after optimizer)
45+
; %s
46+
0000 RETURN int(42)
47+
48+
Test3::getTestBar:
49+
; (lines=1, args=0, vars=0, tmps=0)
50+
; (after optimizer)
51+
; %s
52+
0000 RETURN int(42)
53+
54+
Test3::getTestBaz:
55+
; (lines=1, args=0, vars=0, tmps=0)
56+
; (after optimizer)
57+
; %s
58+
0000 RETURN int(42)

0 commit comments

Comments
 (0)