/src/php-src/Zend/Optimizer/pass1.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Zend OPcache | |
4 | | +----------------------------------------------------------------------+ |
5 | | | Copyright © The PHP Group and Contributors. | |
6 | | +----------------------------------------------------------------------+ |
7 | | | This source file is subject to the Modified BSD License that is | |
8 | | | bundled with this package in the file LICENSE, and is available | |
9 | | | through the World Wide Web at <https://www.php.net/license/>. | |
10 | | | | |
11 | | | SPDX-License-Identifier: BSD-3-Clause | |
12 | | +----------------------------------------------------------------------+ |
13 | | | Authors: Andi Gutmans <andi@php.net> | |
14 | | | Zeev Suraski <zeev@php.net> | |
15 | | | Stanislav Malyshev <stas@zend.com> | |
16 | | | Dmitry Stogov <dmitry@php.net> | |
17 | | +----------------------------------------------------------------------+ |
18 | | */ |
19 | | |
20 | | /* pass 1 (Simple local optimizations) |
21 | | * - persistent constant substitution (true, false, null, etc) |
22 | | * - constant casting (ADD expects numbers, CONCAT strings, etc) |
23 | | * - constant expression evaluation |
24 | | * - optimize constant conditional JMPs |
25 | | * - pre-evaluate constant function calls |
26 | | */ |
27 | | |
28 | | #include "Optimizer/zend_optimizer.h" |
29 | | #include "Optimizer/zend_optimizer_internal.h" |
30 | | #include "zend_API.h" |
31 | | #include "zend_constants.h" |
32 | | #include "zend_execute.h" |
33 | | #include "zend_vm.h" |
34 | | |
35 | 32 | #define TO_STRING_NOWARN(val) do { \ |
36 | 32 | if (Z_TYPE_P(val) < IS_ARRAY) { \ |
37 | 26 | convert_to_string(val); \ |
38 | 26 | } \ |
39 | 32 | } while (0) |
40 | | |
41 | 910 | static void replace_by_const_or_qm_assign(zend_op_array *op_array, zend_op *opline, zval *result) { |
42 | 910 | if (opline->op1_type == IS_CONST) { |
43 | 896 | literal_dtor(&ZEND_OP1_LITERAL(opline)); |
44 | 896 | } |
45 | 910 | if (opline->op2_type == IS_CONST) { |
46 | 712 | literal_dtor(&ZEND_OP2_LITERAL(opline)); |
47 | 712 | } |
48 | 910 | if (zend_optimizer_replace_by_const(op_array, opline + 1, opline->result_type, opline->result.var, result)) { |
49 | 886 | MAKE_NOP(opline); |
50 | 886 | } else { |
51 | 24 | opline->opcode = ZEND_QM_ASSIGN; |
52 | 24 | opline->extended_value = 0; |
53 | 24 | SET_UNUSED(opline->op2); |
54 | 24 | zend_optimizer_update_op1_const(op_array, opline, result); |
55 | 24 | } |
56 | 910 | } |
57 | | |
58 | | void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) |
59 | 47.7k | { |
60 | 47.7k | zend_op *opline = op_array->opcodes; |
61 | 47.7k | zend_op *end = opline + op_array->last; |
62 | 47.7k | bool collect_constants = (ZEND_OPTIMIZER_PASS_15 & ctx->optimization_level)? |
63 | 47.7k | (op_array == &ctx->script->main_op_array) : 0; |
64 | 47.7k | zval result; |
65 | | |
66 | 1.18M | while (opline < end) { |
67 | 1.13M | switch (opline->opcode) { |
68 | 22.0k | case ZEND_CONCAT: |
69 | 24.4k | case ZEND_FAST_CONCAT: |
70 | 24.4k | if (opline->op1_type == IS_CONST && Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_STRING) { |
71 | 0 | TO_STRING_NOWARN(&ZEND_OP1_LITERAL(opline)); |
72 | 0 | } |
73 | 24.4k | if (opline->op2_type == IS_CONST && Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_STRING) { |
74 | 0 | TO_STRING_NOWARN(&ZEND_OP2_LITERAL(opline)); |
75 | 0 | } |
76 | 24.4k | ZEND_FALLTHROUGH; |
77 | 29.9k | case ZEND_ADD: |
78 | 33.1k | case ZEND_SUB: |
79 | 37.2k | case ZEND_MUL: |
80 | 39.0k | case ZEND_DIV: |
81 | 39.4k | case ZEND_POW: |
82 | 40.6k | case ZEND_MOD: |
83 | 41.6k | case ZEND_SL: |
84 | 42.1k | case ZEND_SR: |
85 | 42.8k | case ZEND_BW_OR: |
86 | 45.8k | case ZEND_BW_AND: |
87 | 48.5k | case ZEND_BW_XOR: |
88 | 54.8k | case ZEND_IS_EQUAL: |
89 | 56.7k | case ZEND_IS_NOT_EQUAL: |
90 | 61.5k | case ZEND_IS_SMALLER: |
91 | 63.0k | case ZEND_IS_SMALLER_OR_EQUAL: |
92 | 64.3k | case ZEND_IS_IDENTICAL: |
93 | 64.7k | case ZEND_IS_NOT_IDENTICAL: |
94 | 65.4k | case ZEND_BOOL_XOR: |
95 | 65.4k | case ZEND_SPACESHIP: |
96 | 65.5k | case ZEND_CASE: |
97 | 65.6k | case ZEND_CASE_STRICT: |
98 | 65.6k | if (opline->op1_type == IS_CONST && opline->op2_type == IS_CONST && |
99 | 3.65k | zend_optimizer_eval_binary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline)) == SUCCESS) { |
100 | 160 | replace_by_const_or_qm_assign(op_array, opline, &result); |
101 | 160 | } |
102 | 65.6k | break; |
103 | | |
104 | 40.3k | case ZEND_ASSIGN_OP: |
105 | 40.3k | if (opline->extended_value == ZEND_CONCAT && opline->op2_type == IS_CONST |
106 | 164 | && Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_STRING) { |
107 | 32 | TO_STRING_NOWARN(&ZEND_OP2_LITERAL(opline)); |
108 | 32 | } |
109 | 40.3k | break; |
110 | | |
111 | 1.48k | case ZEND_CAST: |
112 | 1.48k | if (opline->op1_type == IS_CONST && |
113 | 408 | zend_optimizer_eval_cast(&result, opline->extended_value, &ZEND_OP1_LITERAL(opline)) == SUCCESS) { |
114 | 198 | replace_by_const_or_qm_assign(op_array, opline, &result); |
115 | 198 | } |
116 | 1.48k | break; |
117 | | |
118 | 19.4k | case ZEND_BW_NOT: |
119 | 25.5k | case ZEND_BOOL_NOT: |
120 | 25.5k | if (opline->op1_type == IS_CONST && |
121 | 123 | zend_optimizer_eval_unary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline)) == SUCCESS) { |
122 | 0 | replace_by_const_or_qm_assign(op_array, opline, &result); |
123 | 0 | } |
124 | 25.5k | break; |
125 | | |
126 | 15.0k | case ZEND_FETCH_CONSTANT: |
127 | 15.0k | if (opline->op2_type == IS_CONST && |
128 | 15.0k | Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING && |
129 | 15.0k | zend_string_equals_literal(Z_STR(ZEND_OP2_LITERAL(opline)), "__COMPILER_HALT_OFFSET__")) { |
130 | | /* substitute __COMPILER_HALT_OFFSET__ constant */ |
131 | 8 | zend_execute_data *orig_execute_data = EG(current_execute_data); |
132 | 8 | zend_execute_data fake_execute_data; |
133 | 8 | zval *offset; |
134 | | |
135 | 8 | memset(&fake_execute_data, 0, sizeof(zend_execute_data)); |
136 | 8 | fake_execute_data.func = (zend_function*)op_array; |
137 | 8 | EG(current_execute_data) = &fake_execute_data; |
138 | 8 | if ((offset = zend_get_constant_str("__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__") - 1)) != NULL) { |
139 | |
|
140 | 0 | literal_dtor(&ZEND_OP2_LITERAL(opline)); |
141 | 0 | replace_by_const_or_qm_assign(op_array, opline, offset); |
142 | 0 | } |
143 | 8 | EG(current_execute_data) = orig_execute_data; |
144 | 8 | break; |
145 | 8 | } |
146 | | |
147 | 15.0k | if (opline->op2_type == IS_CONST && |
148 | 15.0k | Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) { |
149 | | /* substitute persistent constants */ |
150 | 15.0k | if (!zend_optimizer_get_persistent_constant(Z_STR(ZEND_OP2_LITERAL(opline)), &result, true)) { |
151 | 15.0k | if (!ctx->constants || !zend_optimizer_get_collected_constant(ctx->constants, &ZEND_OP2_LITERAL(opline), &result)) { |
152 | 15.0k | break; |
153 | 15.0k | } |
154 | 15.0k | } |
155 | 0 | if (Z_TYPE(result) == IS_CONSTANT_AST) { |
156 | 0 | break; |
157 | 0 | } |
158 | 0 | replace_by_const_or_qm_assign(op_array, opline, &result); |
159 | 0 | } |
160 | 0 | break; |
161 | | |
162 | 2.07k | case ZEND_FETCH_CLASS_CONSTANT: { |
163 | 2.07k | bool is_prototype; |
164 | 2.07k | const zend_class_constant *cc = zend_fetch_class_const_info(ctx->script, op_array, opline, &is_prototype); |
165 | 2.07k | if (!cc || is_prototype) { |
166 | 1.24k | break; |
167 | 1.24k | } |
168 | 836 | const zval *c = &cc->value; |
169 | 836 | if (Z_TYPE_P(c) == IS_CONSTANT_AST) { |
170 | 284 | zend_ast *ast = Z_ASTVAL_P(c); |
171 | 284 | if (ast->kind != ZEND_AST_CONSTANT |
172 | 60 | || !zend_optimizer_get_persistent_constant(zend_ast_get_constant_name(ast), &result, true) |
173 | 284 | || Z_TYPE(result) == IS_CONSTANT_AST) { |
174 | 284 | break; |
175 | 284 | } |
176 | 552 | } else { |
177 | 552 | ZVAL_COPY_OR_DUP(&result, c); |
178 | 552 | } |
179 | 552 | replace_by_const_or_qm_assign(op_array, opline, &result); |
180 | 552 | break; |
181 | 836 | } |
182 | | |
183 | 0 | case ZEND_DO_ICALL: { |
184 | 0 | zend_op *send1_opline = opline - 1; |
185 | 0 | zend_op *send2_opline = NULL; |
186 | 0 | zend_op *init_opline = NULL; |
187 | |
|
188 | 0 | while (send1_opline->opcode == ZEND_NOP) { |
189 | 0 | send1_opline--; |
190 | 0 | } |
191 | 0 | if (send1_opline->opcode != ZEND_SEND_VAL || |
192 | 0 | send1_opline->op1_type != IS_CONST) { |
193 | | /* don't collect constants after unknown function call */ |
194 | 0 | collect_constants = false; |
195 | 0 | break; |
196 | 0 | } |
197 | 0 | if (send1_opline->op2.num == 2) { |
198 | 0 | send2_opline = send1_opline; |
199 | 0 | send1_opline--; |
200 | 0 | while (send1_opline->opcode == ZEND_NOP) { |
201 | 0 | send1_opline--; |
202 | 0 | } |
203 | 0 | if (send1_opline->opcode != ZEND_SEND_VAL || |
204 | 0 | send1_opline->op1_type != IS_CONST) { |
205 | | /* don't collect constants after unknown function call */ |
206 | 0 | collect_constants = false; |
207 | 0 | break; |
208 | 0 | } |
209 | 0 | } |
210 | 0 | init_opline = send1_opline - 1; |
211 | 0 | while (init_opline->opcode == ZEND_NOP) { |
212 | 0 | init_opline--; |
213 | 0 | } |
214 | 0 | if (init_opline->opcode != ZEND_INIT_FCALL || |
215 | 0 | init_opline->op2_type != IS_CONST || |
216 | 0 | Z_TYPE(ZEND_OP2_LITERAL(init_opline)) != IS_STRING) { |
217 | | /* don't collect constants after unknown function call */ |
218 | 0 | collect_constants = false; |
219 | 0 | break; |
220 | 0 | } |
221 | | |
222 | | /* define("name", scalar); */ |
223 | 0 | if (zend_string_equals_literal_ci(Z_STR(ZEND_OP2_LITERAL(init_opline)), "define")) { |
224 | |
|
225 | 0 | if (Z_TYPE(ZEND_OP1_LITERAL(send1_opline)) == IS_STRING && send2_opline) { |
226 | |
|
227 | 0 | if (collect_constants) { |
228 | 0 | zend_optimizer_collect_constant(ctx, &ZEND_OP1_LITERAL(send1_opline), &ZEND_OP1_LITERAL(send2_opline)); |
229 | 0 | } |
230 | |
|
231 | 0 | if (RESULT_UNUSED(opline) && |
232 | 0 | !zend_memnstr(Z_STRVAL(ZEND_OP1_LITERAL(send1_opline)), "::", sizeof("::") - 1, Z_STRVAL(ZEND_OP1_LITERAL(send1_opline)) + Z_STRLEN(ZEND_OP1_LITERAL(send1_opline)))) { |
233 | |
|
234 | 0 | opline->opcode = ZEND_DECLARE_CONST; |
235 | 0 | opline->op1_type = IS_CONST; |
236 | 0 | opline->op2_type = IS_CONST; |
237 | 0 | opline->result_type = IS_UNUSED; |
238 | 0 | opline->op1.constant = send1_opline->op1.constant; |
239 | 0 | opline->op2.constant = send2_opline->op1.constant; |
240 | 0 | opline->result.num = 0; |
241 | |
|
242 | 0 | literal_dtor(&ZEND_OP2_LITERAL(init_opline)); |
243 | 0 | MAKE_NOP(init_opline); |
244 | 0 | MAKE_NOP(send1_opline); |
245 | 0 | MAKE_NOP(send2_opline); |
246 | 0 | } |
247 | 0 | break; |
248 | 0 | } |
249 | 0 | } |
250 | | |
251 | 0 | if (!send2_opline && Z_TYPE(ZEND_OP1_LITERAL(send1_opline)) == IS_STRING && |
252 | 0 | zend_optimizer_eval_special_func_call(&result, Z_STR(ZEND_OP2_LITERAL(init_opline)), Z_STR(ZEND_OP1_LITERAL(send1_opline))) == SUCCESS) { |
253 | 0 | literal_dtor(&ZEND_OP2_LITERAL(init_opline)); |
254 | 0 | MAKE_NOP(init_opline); |
255 | 0 | literal_dtor(&ZEND_OP1_LITERAL(send1_opline)); |
256 | 0 | MAKE_NOP(send1_opline); |
257 | 0 | replace_by_const_or_qm_assign(op_array, opline, &result); |
258 | 0 | break; |
259 | 0 | } |
260 | | |
261 | | /* don't collect constants after any other function call */ |
262 | 0 | collect_constants = false; |
263 | 0 | break; |
264 | 0 | } |
265 | 4.97k | case ZEND_DO_UCALL: |
266 | 103k | case ZEND_DO_FCALL: |
267 | 103k | case ZEND_DO_FCALL_BY_NAME: |
268 | 103k | case ZEND_FRAMELESS_ICALL_0: |
269 | 103k | case ZEND_FRAMELESS_ICALL_1: |
270 | 103k | case ZEND_FRAMELESS_ICALL_2: |
271 | 103k | case ZEND_FRAMELESS_ICALL_3: |
272 | | /* don't collect constants after any UCALL/FCALL/FRAMELESS ICALL */ |
273 | 103k | collect_constants = 0; |
274 | 103k | break; |
275 | 1.21k | case ZEND_STRLEN: |
276 | 1.21k | if (opline->op1_type == IS_CONST && |
277 | 4 | zend_optimizer_eval_strlen(&result, &ZEND_OP1_LITERAL(opline)) == SUCCESS) { |
278 | 0 | replace_by_const_or_qm_assign(op_array, opline, &result); |
279 | 0 | } |
280 | 1.21k | break; |
281 | 51 | case ZEND_DEFINED: |
282 | 51 | if (!zend_optimizer_get_persistent_constant(Z_STR(ZEND_OP1_LITERAL(opline)), &result, false)) { |
283 | 51 | break; |
284 | 51 | } |
285 | 0 | ZVAL_TRUE(&result); |
286 | 0 | literal_dtor(&ZEND_OP1_LITERAL(opline)); |
287 | 0 | replace_by_const_or_qm_assign(op_array, opline, &result); |
288 | 0 | break; |
289 | 760 | case ZEND_DECLARE_CONST: |
290 | 760 | if (collect_constants && |
291 | 0 | Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING && |
292 | 0 | Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_CONSTANT_AST) { |
293 | 0 | zend_optimizer_collect_constant(ctx, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline)); |
294 | 0 | } |
295 | 760 | break; |
296 | | |
297 | 1.61k | case ZEND_JMPZ_EX: |
298 | 3.14k | case ZEND_JMPNZ_EX: |
299 | | /* convert Ti = JMPZ_EX(C, L) => Ti = QM_ASSIGN(C) |
300 | | in case we know it wouldn't jump */ |
301 | 3.14k | if (opline->op1_type == IS_CONST) { |
302 | 0 | if (zend_is_true(&ZEND_OP1_LITERAL(opline))) { |
303 | 0 | if (opline->opcode == ZEND_JMPZ_EX) { |
304 | 0 | opline->opcode = ZEND_QM_ASSIGN; |
305 | 0 | zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline)); |
306 | 0 | ZVAL_TRUE(&ZEND_OP1_LITERAL(opline)); |
307 | 0 | opline->op2.num = 0; |
308 | 0 | break; |
309 | 0 | } |
310 | 0 | } else { |
311 | 0 | if (opline->opcode == ZEND_JMPNZ_EX) { |
312 | 0 | opline->opcode = ZEND_QM_ASSIGN; |
313 | 0 | zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline)); |
314 | 0 | ZVAL_FALSE(&ZEND_OP1_LITERAL(opline)); |
315 | 0 | opline->op2.num = 0; |
316 | 0 | break; |
317 | 0 | } |
318 | 0 | } |
319 | 0 | } |
320 | 3.14k | collect_constants = false; |
321 | 3.14k | break; |
322 | | |
323 | 8.70k | case ZEND_JMPZ: |
324 | 13.6k | case ZEND_JMPNZ: |
325 | 13.6k | if (opline->op1_type == IS_CONST) { |
326 | 1.08k | bool should_jmp = zend_is_true(&ZEND_OP1_LITERAL(opline)); |
327 | | |
328 | 1.08k | if (opline->opcode == ZEND_JMPZ) { |
329 | 136 | should_jmp = !should_jmp; |
330 | 136 | } |
331 | 1.08k | literal_dtor(&ZEND_OP1_LITERAL(opline)); |
332 | 1.08k | opline->op1_type = IS_UNUSED; |
333 | 1.08k | if (should_jmp) { |
334 | 914 | opline->opcode = ZEND_JMP; |
335 | 914 | COPY_NODE(opline->op1, opline->op2); |
336 | 914 | opline->op2.num = 0; |
337 | 914 | } else { |
338 | 170 | MAKE_NOP(opline); |
339 | 170 | break; |
340 | 170 | } |
341 | 1.08k | } |
342 | 13.4k | collect_constants = false; |
343 | 13.4k | break; |
344 | | |
345 | 52.4k | case ZEND_RETURN: |
346 | 53.6k | case ZEND_RETURN_BY_REF: |
347 | 54.8k | case ZEND_GENERATOR_RETURN: |
348 | 56.0k | case ZEND_THROW: |
349 | 56.3k | case ZEND_MATCH_ERROR: |
350 | 70.0k | case ZEND_CATCH: |
351 | 70.5k | case ZEND_FAST_CALL: |
352 | 71.0k | case ZEND_FAST_RET: |
353 | 100k | case ZEND_JMP: |
354 | 106k | case ZEND_FE_RESET_R: |
355 | 106k | case ZEND_FE_RESET_RW: |
356 | 113k | case ZEND_FE_FETCH_R: |
357 | 113k | case ZEND_FE_FETCH_RW: |
358 | 114k | case ZEND_JMP_SET: |
359 | 116k | case ZEND_COALESCE: |
360 | 117k | case ZEND_ASSERT_CHECK: |
361 | 135k | case ZEND_JMP_NULL: |
362 | 135k | case ZEND_VERIFY_NEVER_TYPE: |
363 | 136k | case ZEND_BIND_INIT_STATIC_OR_JMP: |
364 | 136k | case ZEND_JMP_FRAMELESS: |
365 | 136k | collect_constants = false; |
366 | 136k | break; |
367 | 1.13M | } |
368 | 1.13M | opline++; |
369 | 1.13M | } |
370 | 47.7k | } |