1 /*
2 +----------------------------------------------------------------------+
3 | Zend OPcache |
4 +----------------------------------------------------------------------+
5 | Copyright (c) The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | https://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Andi Gutmans <andi@php.net> |
16 | Zeev Suraski <zeev@php.net> |
17 | Stanislav Malyshev <stas@zend.com> |
18 | Dmitry Stogov <dmitry@php.net> |
19 +----------------------------------------------------------------------+
20 */
21
22 /* pass 1 (Simple local optimizations)
23 * - persistent constant substitution (true, false, null, etc)
24 * - constant casting (ADD expects numbers, CONCAT strings, etc)
25 * - constant expression evaluation
26 * - optimize constant conditional JMPs
27 * - pre-evaluate constant function calls
28 */
29
30 #include "Optimizer/zend_optimizer.h"
31 #include "Optimizer/zend_optimizer_internal.h"
32 #include "zend_API.h"
33 #include "zend_constants.h"
34 #include "zend_execute.h"
35 #include "zend_vm.h"
36
replace_by_const_or_qm_assign(zend_op_array * op_array,zend_op * opline,zval * result)37 static void replace_by_const_or_qm_assign(zend_op_array *op_array, zend_op *opline, zval *result) {
38 if (opline->op1_type == IS_CONST) {
39 literal_dtor(&ZEND_OP1_LITERAL(opline));
40 }
41 if (opline->op2_type == IS_CONST) {
42 literal_dtor(&ZEND_OP2_LITERAL(opline));
43 }
44 if (zend_optimizer_replace_by_const(op_array, opline + 1, opline->result_type, opline->result.var, result)) {
45 MAKE_NOP(opline);
46 } else {
47 opline->opcode = ZEND_QM_ASSIGN;
48 opline->extended_value = 0;
49 SET_UNUSED(opline->op2);
50 zend_optimizer_update_op1_const(op_array, opline, result);
51 }
52 }
53
zend_optimizer_pass1(zend_op_array * op_array,zend_optimizer_ctx * ctx)54 void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
55 {
56 zend_op *opline = op_array->opcodes;
57 zend_op *end = opline + op_array->last;
58 bool collect_constants = (ZEND_OPTIMIZER_PASS_15 & ctx->optimization_level)?
59 (op_array == &ctx->script->main_op_array) : 0;
60 zval result;
61
62 while (opline < end) {
63 switch (opline->opcode) {
64 case ZEND_CONCAT:
65 case ZEND_FAST_CONCAT:
66 if (opline->op1_type == IS_CONST && Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_STRING) {
67 convert_to_string(&ZEND_OP1_LITERAL(opline));
68 }
69 if (opline->op2_type == IS_CONST && Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_STRING) {
70 convert_to_string(&ZEND_OP2_LITERAL(opline));
71 }
72 ZEND_FALLTHROUGH;
73 case ZEND_ADD:
74 case ZEND_SUB:
75 case ZEND_MUL:
76 case ZEND_DIV:
77 case ZEND_POW:
78 case ZEND_MOD:
79 case ZEND_SL:
80 case ZEND_SR:
81 case ZEND_BW_OR:
82 case ZEND_BW_AND:
83 case ZEND_BW_XOR:
84 case ZEND_IS_EQUAL:
85 case ZEND_IS_NOT_EQUAL:
86 case ZEND_IS_SMALLER:
87 case ZEND_IS_SMALLER_OR_EQUAL:
88 case ZEND_IS_IDENTICAL:
89 case ZEND_IS_NOT_IDENTICAL:
90 case ZEND_BOOL_XOR:
91 case ZEND_SPACESHIP:
92 case ZEND_CASE:
93 case ZEND_CASE_STRICT:
94 if (opline->op1_type == IS_CONST && opline->op2_type == IS_CONST &&
95 zend_optimizer_eval_binary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline)) == SUCCESS) {
96 replace_by_const_or_qm_assign(op_array, opline, &result);
97 }
98 break;
99
100 case ZEND_ASSIGN_OP:
101 if (opline->extended_value == ZEND_CONCAT && opline->op2_type == IS_CONST
102 && Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_STRING) {
103 convert_to_string(&ZEND_OP2_LITERAL(opline));
104 }
105 break;
106
107 case ZEND_CAST:
108 if (opline->op1_type == IS_CONST &&
109 zend_optimizer_eval_cast(&result, opline->extended_value, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
110 replace_by_const_or_qm_assign(op_array, opline, &result);
111 }
112 break;
113
114 case ZEND_BW_NOT:
115 case ZEND_BOOL_NOT:
116 if (opline->op1_type == IS_CONST &&
117 zend_optimizer_eval_unary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
118 replace_by_const_or_qm_assign(op_array, opline, &result);
119 }
120 break;
121
122 case ZEND_FETCH_CONSTANT:
123 if (opline->op2_type == IS_CONST &&
124 Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING &&
125 zend_string_equals_literal(Z_STR(ZEND_OP2_LITERAL(opline)), "__COMPILER_HALT_OFFSET__")) {
126 /* substitute __COMPILER_HALT_OFFSET__ constant */
127 zend_execute_data *orig_execute_data = EG(current_execute_data);
128 zend_execute_data fake_execute_data;
129 zval *offset;
130
131 memset(&fake_execute_data, 0, sizeof(zend_execute_data));
132 fake_execute_data.func = (zend_function*)op_array;
133 EG(current_execute_data) = &fake_execute_data;
134 if ((offset = zend_get_constant_str("__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__") - 1)) != NULL) {
135
136 literal_dtor(&ZEND_OP2_LITERAL(opline));
137 replace_by_const_or_qm_assign(op_array, opline, offset);
138 }
139 EG(current_execute_data) = orig_execute_data;
140 break;
141 }
142
143 if (opline->op2_type == IS_CONST &&
144 Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) {
145 /* substitute persistent constants */
146 if (!zend_optimizer_get_persistent_constant(Z_STR(ZEND_OP2_LITERAL(opline)), &result, 1)) {
147 if (!ctx->constants || !zend_optimizer_get_collected_constant(ctx->constants, &ZEND_OP2_LITERAL(opline), &result)) {
148 break;
149 }
150 }
151 if (Z_TYPE(result) == IS_CONSTANT_AST) {
152 break;
153 }
154 replace_by_const_or_qm_assign(op_array, opline, &result);
155 }
156 break;
157
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_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 }
182 }
183 }
184 break;
185
186 case ZEND_DO_ICALL: {
187 zend_op *send1_opline = opline - 1;
188 zend_op *send2_opline = NULL;
189 zend_op *init_opline = NULL;
190
191 while (send1_opline->opcode == ZEND_NOP) {
192 send1_opline--;
193 }
194 if (send1_opline->opcode != ZEND_SEND_VAL ||
195 send1_opline->op1_type != IS_CONST) {
196 /* don't collect constants after unknown function call */
197 collect_constants = 0;
198 break;
199 }
200 if (send1_opline->op2.num == 2) {
201 send2_opline = send1_opline;
202 send1_opline--;
203 while (send1_opline->opcode == ZEND_NOP) {
204 send1_opline--;
205 }
206 if (send1_opline->opcode != ZEND_SEND_VAL ||
207 send1_opline->op1_type != IS_CONST) {
208 /* don't collect constants after unknown function call */
209 collect_constants = 0;
210 break;
211 }
212 }
213 init_opline = send1_opline - 1;
214 while (init_opline->opcode == ZEND_NOP) {
215 init_opline--;
216 }
217 if (init_opline->opcode != ZEND_INIT_FCALL ||
218 init_opline->op2_type != IS_CONST ||
219 Z_TYPE(ZEND_OP2_LITERAL(init_opline)) != IS_STRING) {
220 /* don't collect constants after unknown function call */
221 collect_constants = 0;
222 break;
223 }
224
225 /* define("name", scalar); */
226 if (zend_string_equals_literal_ci(Z_STR(ZEND_OP2_LITERAL(init_opline)), "define")) {
227
228 if (Z_TYPE(ZEND_OP1_LITERAL(send1_opline)) == IS_STRING && send2_opline) {
229
230 if (collect_constants) {
231 zend_optimizer_collect_constant(ctx, &ZEND_OP1_LITERAL(send1_opline), &ZEND_OP1_LITERAL(send2_opline));
232 }
233
234 if (RESULT_UNUSED(opline) &&
235 !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)))) {
236
237 opline->opcode = ZEND_DECLARE_CONST;
238 opline->op1_type = IS_CONST;
239 opline->op2_type = IS_CONST;
240 opline->result_type = IS_UNUSED;
241 opline->op1.constant = send1_opline->op1.constant;
242 opline->op2.constant = send2_opline->op1.constant;
243 opline->result.num = 0;
244
245 literal_dtor(&ZEND_OP2_LITERAL(init_opline));
246 MAKE_NOP(init_opline);
247 MAKE_NOP(send1_opline);
248 MAKE_NOP(send2_opline);
249 }
250 break;
251 }
252 }
253
254 if (!send2_opline && Z_TYPE(ZEND_OP1_LITERAL(send1_opline)) == IS_STRING &&
255 zend_optimizer_eval_special_func_call(&result, Z_STR(ZEND_OP2_LITERAL(init_opline)), Z_STR(ZEND_OP1_LITERAL(send1_opline))) == SUCCESS) {
256 literal_dtor(&ZEND_OP2_LITERAL(init_opline));
257 MAKE_NOP(init_opline);
258 literal_dtor(&ZEND_OP1_LITERAL(send1_opline));
259 MAKE_NOP(send1_opline);
260 replace_by_const_or_qm_assign(op_array, opline, &result);
261 break;
262 }
263
264 /* don't collect constants after any other function call */
265 collect_constants = 0;
266 break;
267 }
268 case ZEND_STRLEN:
269 if (opline->op1_type == IS_CONST &&
270 zend_optimizer_eval_strlen(&result, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
271 replace_by_const_or_qm_assign(op_array, opline, &result);
272 }
273 break;
274 case ZEND_DEFINED:
275 if (!zend_optimizer_get_persistent_constant(Z_STR(ZEND_OP1_LITERAL(opline)), &result, 0)) {
276 break;
277 }
278 ZVAL_TRUE(&result);
279 literal_dtor(&ZEND_OP1_LITERAL(opline));
280 replace_by_const_or_qm_assign(op_array, opline, &result);
281 break;
282 case ZEND_DECLARE_CONST:
283 if (collect_constants &&
284 Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING &&
285 Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_CONSTANT_AST) {
286 zend_optimizer_collect_constant(ctx, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline));
287 }
288 break;
289
290 case ZEND_JMPZ_EX:
291 case ZEND_JMPNZ_EX:
292 /* convert Ti = JMPZ_EX(C, L) => Ti = QM_ASSIGN(C)
293 in case we know it wouldn't jump */
294 if (opline->op1_type == IS_CONST) {
295 if (zend_is_true(&ZEND_OP1_LITERAL(opline))) {
296 if (opline->opcode == ZEND_JMPZ_EX) {
297 opline->opcode = ZEND_QM_ASSIGN;
298 zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline));
299 ZVAL_TRUE(&ZEND_OP1_LITERAL(opline));
300 opline->op2.num = 0;
301 break;
302 }
303 } else {
304 if (opline->opcode == ZEND_JMPNZ_EX) {
305 opline->opcode = ZEND_QM_ASSIGN;
306 zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline));
307 ZVAL_FALSE(&ZEND_OP1_LITERAL(opline));
308 opline->op2.num = 0;
309 break;
310 }
311 }
312 }
313 collect_constants = 0;
314 break;
315
316 case ZEND_JMPZ:
317 case ZEND_JMPNZ:
318 if (opline->op1_type == IS_CONST) {
319 int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(opline));
320
321 if (opline->opcode == ZEND_JMPZ) {
322 should_jmp = !should_jmp;
323 }
324 literal_dtor(&ZEND_OP1_LITERAL(opline));
325 opline->op1_type = IS_UNUSED;
326 if (should_jmp) {
327 opline->opcode = ZEND_JMP;
328 COPY_NODE(opline->op1, opline->op2);
329 opline->op2.num = 0;
330 } else {
331 MAKE_NOP(opline);
332 break;
333 }
334 }
335 collect_constants = 0;
336 break;
337
338 case ZEND_RETURN:
339 case ZEND_RETURN_BY_REF:
340 case ZEND_GENERATOR_RETURN:
341 case ZEND_EXIT:
342 case ZEND_THROW:
343 case ZEND_MATCH_ERROR:
344 case ZEND_CATCH:
345 case ZEND_FAST_CALL:
346 case ZEND_FAST_RET:
347 case ZEND_JMP:
348 case ZEND_FE_RESET_R:
349 case ZEND_FE_RESET_RW:
350 case ZEND_FE_FETCH_R:
351 case ZEND_FE_FETCH_RW:
352 case ZEND_JMP_SET:
353 case ZEND_COALESCE:
354 case ZEND_ASSERT_CHECK:
355 case ZEND_JMP_NULL:
356 case ZEND_VERIFY_NEVER_TYPE:
357 collect_constants = 0;
358 break;
359 }
360 opline++;
361 }
362 }
363