/src/php-src/Zend/Optimizer/zend_optimizer.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 | | #include "Optimizer/zend_optimizer.h" |
21 | | #include "Optimizer/zend_optimizer_internal.h" |
22 | | #include "zend_API.h" |
23 | | #include "zend_constants.h" |
24 | | #include "zend_execute.h" |
25 | | #include "zend_vm.h" |
26 | | #include "zend_cfg.h" |
27 | | #include "zend_func_info.h" |
28 | | #include "zend_call_graph.h" |
29 | | #include "zend_inference.h" |
30 | | #include "zend_dump.h" |
31 | | #include "php.h" |
32 | | |
33 | | #ifndef ZEND_OPTIMIZER_MAX_REGISTERED_PASSES |
34 | 0 | # define ZEND_OPTIMIZER_MAX_REGISTERED_PASSES 32 |
35 | | #endif |
36 | | |
37 | | struct { |
38 | | zend_optimizer_pass_t pass[ZEND_OPTIMIZER_MAX_REGISTERED_PASSES]; |
39 | | int last; |
40 | | } zend_optimizer_registered_passes = {{NULL}, 0}; |
41 | | |
42 | | void zend_optimizer_collect_constant(zend_optimizer_ctx *ctx, const zval *name, zval* value) |
43 | 0 | { |
44 | 0 | if (!ctx->constants) { |
45 | 0 | ctx->constants = zend_arena_alloc(&ctx->arena, sizeof(HashTable)); |
46 | 0 | zend_hash_init(ctx->constants, 16, NULL, zval_ptr_dtor_nogc, 0); |
47 | 0 | } |
48 | |
|
49 | 0 | if (zend_hash_add(ctx->constants, Z_STR_P(name), value)) { |
50 | 0 | Z_TRY_ADDREF_P(value); |
51 | 0 | } |
52 | 0 | } |
53 | | |
54 | | zend_result zend_optimizer_eval_binary_op(zval *result, uint8_t opcode, zval *op1, zval *op2) /* {{{ */ |
55 | 36.0k | { |
56 | 36.0k | if (zend_binary_op_produces_error(opcode, op1, op2)) { |
57 | 29.5k | return FAILURE; |
58 | 29.5k | } |
59 | | |
60 | 6.46k | binary_op_type binary_op = get_binary_op(opcode); |
61 | 6.46k | return binary_op(result, op1, op2); |
62 | 36.0k | } |
63 | | /* }}} */ |
64 | | |
65 | | zend_result zend_optimizer_eval_unary_op(zval *result, uint8_t opcode, zval *op1) /* {{{ */ |
66 | 3.88k | { |
67 | 3.88k | unary_op_type unary_op = get_unary_op(opcode); |
68 | | |
69 | 3.88k | if (unary_op) { |
70 | 1.76k | if (zend_unary_op_produces_error(opcode, op1)) { |
71 | 1.23k | return FAILURE; |
72 | 1.23k | } |
73 | 527 | return unary_op(result, op1); |
74 | 2.12k | } else { /* ZEND_BOOL */ |
75 | 2.12k | if (Z_TYPE_P(op1) == IS_DOUBLE && zend_isnan(Z_DVAL_P(op1))) { |
76 | 6 | return FAILURE; |
77 | 6 | } |
78 | 2.11k | ZVAL_BOOL(result, zend_is_true(op1)); |
79 | 2.11k | return SUCCESS; |
80 | 2.12k | } |
81 | 3.88k | } |
82 | | /* }}} */ |
83 | | |
84 | | zend_result zend_optimizer_eval_cast(zval *result, uint32_t type, zval *op1) /* {{{ */ |
85 | 18.8k | { |
86 | 18.8k | if (zend_try_ct_eval_cast(result, type, op1)) { |
87 | 17.0k | return SUCCESS; |
88 | 17.0k | } |
89 | 1.78k | return FAILURE; |
90 | 18.8k | } |
91 | | /* }}} */ |
92 | | |
93 | | zend_result zend_optimizer_eval_strlen(zval *result, const zval *op1) /* {{{ */ |
94 | 112 | { |
95 | 112 | if (Z_TYPE_P(op1) != IS_STRING) { |
96 | 30 | return FAILURE; |
97 | 30 | } |
98 | 82 | ZVAL_LONG(result, Z_STRLEN_P(op1)); |
99 | 82 | return SUCCESS; |
100 | 112 | } |
101 | | /* }}} */ |
102 | | |
103 | | zend_result zend_optimizer_eval_special_func_call( |
104 | 0 | zval *result, const zend_string *name, zend_string *arg) { |
105 | 0 | if (zend_string_equals_literal(name, "function_exists") || |
106 | 0 | zend_string_equals_literal(name, "is_callable")) { |
107 | 0 | const zend_internal_function *func = zend_hash_find_ptr_lc(EG(function_table), arg); |
108 | |
|
109 | 0 | if (func && func->type == ZEND_INTERNAL_FUNCTION |
110 | 0 | && func->module->type == MODULE_PERSISTENT |
111 | | #ifdef ZEND_WIN32 |
112 | | && func->module->handle == NULL |
113 | | #endif |
114 | 0 | ) { |
115 | 0 | ZVAL_TRUE(result); |
116 | 0 | return SUCCESS; |
117 | 0 | } |
118 | 0 | return FAILURE; |
119 | 0 | } |
120 | 0 | if (zend_string_equals_literal(name, "extension_loaded")) { |
121 | 0 | zend_module_entry *m = zend_hash_find_ptr_lc(&module_registry, arg); |
122 | |
|
123 | 0 | if (!m) { |
124 | 0 | if (PG(enable_dl)) { |
125 | 0 | return FAILURE; |
126 | 0 | } |
127 | 0 | ZVAL_FALSE(result); |
128 | 0 | return SUCCESS; |
129 | 0 | } |
130 | | |
131 | 0 | if (m->type == MODULE_PERSISTENT |
132 | | #ifdef ZEND_WIN32 |
133 | | && m->handle == NULL |
134 | | #endif |
135 | 0 | ) { |
136 | 0 | ZVAL_TRUE(result); |
137 | 0 | return SUCCESS; |
138 | 0 | } |
139 | 0 | return FAILURE; |
140 | 0 | } |
141 | 0 | if (zend_string_equals_literal(name, "constant")) { |
142 | 0 | return zend_optimizer_get_persistent_constant(arg, result, true) ? SUCCESS : FAILURE; |
143 | 0 | } |
144 | 0 | if (zend_string_equals_literal(name, "dirname")) { |
145 | 0 | if (!IS_ABSOLUTE_PATH(ZSTR_VAL(arg), ZSTR_LEN(arg))) { |
146 | 0 | return FAILURE; |
147 | 0 | } |
148 | | |
149 | 0 | zend_string *dirname = zend_string_init(ZSTR_VAL(arg), ZSTR_LEN(arg), 0); |
150 | 0 | ZSTR_LEN(dirname) = zend_dirname(ZSTR_VAL(dirname), ZSTR_LEN(dirname)); |
151 | 0 | if (IS_ABSOLUTE_PATH(ZSTR_VAL(dirname), ZSTR_LEN(dirname))) { |
152 | 0 | ZVAL_STR(result, dirname); |
153 | 0 | return SUCCESS; |
154 | 0 | } |
155 | 0 | zend_string_release_ex(dirname, 0); |
156 | 0 | return FAILURE; |
157 | 0 | } |
158 | 0 | if (zend_string_equals_literal(name, "ini_get")) { |
159 | 0 | zend_ini_entry *ini_entry = zend_hash_find_ptr(EG(ini_directives), arg); |
160 | 0 | if (!ini_entry) { |
161 | 0 | if (PG(enable_dl)) { |
162 | 0 | return FAILURE; |
163 | 0 | } |
164 | 0 | ZVAL_FALSE(result); |
165 | 0 | } else if (ini_entry->modifiable != ZEND_INI_SYSTEM) { |
166 | 0 | return FAILURE; |
167 | 0 | } else if (ini_entry->value) { |
168 | 0 | ZVAL_STR_COPY(result, ini_entry->value); |
169 | 0 | } else { |
170 | 0 | ZVAL_EMPTY_STRING(result); |
171 | 0 | } |
172 | 0 | return SUCCESS; |
173 | 0 | } |
174 | 0 | return FAILURE; |
175 | 0 | } |
176 | | |
177 | | bool zend_optimizer_get_collected_constant(const HashTable *constants, const zval *name, zval* value) |
178 | 0 | { |
179 | 0 | zval *val; |
180 | |
|
181 | 0 | if ((val = zend_hash_find(constants, Z_STR_P(name))) != NULL) { |
182 | 0 | ZVAL_COPY(value, val); |
183 | 0 | return true; |
184 | 0 | } |
185 | 0 | return false; |
186 | 0 | } |
187 | | |
188 | | void zend_optimizer_convert_to_free_op1(const zend_op_array *op_array, zend_op *opline) |
189 | 7.49k | { |
190 | 7.49k | if (opline->op1_type == IS_CV) { |
191 | 459 | opline->opcode = ZEND_CHECK_VAR; |
192 | 459 | SET_UNUSED(opline->op2); |
193 | 459 | SET_UNUSED(opline->result); |
194 | 459 | opline->extended_value = 0; |
195 | 7.03k | } else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) { |
196 | 5.54k | opline->opcode = ZEND_FREE; |
197 | 5.54k | SET_UNUSED(opline->op2); |
198 | 5.54k | SET_UNUSED(opline->result); |
199 | 5.54k | opline->extended_value = 0; |
200 | 5.54k | } else { |
201 | 1.48k | ZEND_ASSERT(opline->op1_type == IS_CONST); |
202 | 1.48k | literal_dtor(&ZEND_OP1_LITERAL(opline)); |
203 | 1.48k | MAKE_NOP(opline); |
204 | 1.48k | } |
205 | 7.49k | } |
206 | | |
207 | | uint32_t zend_optimizer_add_literal(zend_op_array *op_array, const zval *zv) |
208 | 31.8k | { |
209 | 31.8k | uint32_t i = op_array->last_literal; |
210 | 31.8k | op_array->last_literal++; |
211 | 31.8k | op_array->literals = (zval*)erealloc(op_array->literals, op_array->last_literal * sizeof(zval)); |
212 | 31.8k | ZVAL_COPY_VALUE(&op_array->literals[i], zv); |
213 | 31.8k | Z_EXTRA(op_array->literals[i]) = 0; |
214 | 31.8k | return i; |
215 | 31.8k | } |
216 | | |
217 | 110 | static inline uint32_t zend_optimizer_add_literal_string(zend_op_array *op_array, zend_string *str) { |
218 | 110 | zval zv; |
219 | 110 | ZVAL_STR(&zv, str); |
220 | 110 | zend_string_hash_val(str); |
221 | 110 | return zend_optimizer_add_literal(op_array, &zv); |
222 | 110 | } |
223 | | |
224 | 110 | static inline void drop_leading_backslash(zval *val) { |
225 | 110 | if (Z_STRVAL_P(val)[0] == '\\') { |
226 | 2 | zend_string *str = zend_string_init(Z_STRVAL_P(val) + 1, Z_STRLEN_P(val) - 1, 0); |
227 | 2 | zval_ptr_dtor_nogc(val); |
228 | 2 | ZVAL_STR(val, str); |
229 | 2 | } |
230 | 110 | } |
231 | | |
232 | 206 | static inline uint32_t alloc_cache_slots(zend_op_array *op_array, uint32_t num) { |
233 | 206 | uint32_t ret = op_array->cache_size; |
234 | 206 | op_array->cache_size += num * sizeof(void *); |
235 | 206 | return ret; |
236 | 206 | } |
237 | | |
238 | 116 | #define REQUIRES_STRING(val) do { \ |
239 | 116 | if (Z_TYPE_P(val) != IS_STRING) { \ |
240 | 10 | return 0; \ |
241 | 10 | } \ |
242 | 116 | } while (0) |
243 | | |
244 | 2.04k | #define TO_STRING_NOWARN(val) do { \ |
245 | 2.04k | if (Z_TYPE_P(val) >= IS_ARRAY) { \ |
246 | 28 | return 0; \ |
247 | 28 | } \ |
248 | 2.04k | convert_to_string(val); \ |
249 | 2.01k | } while (0) |
250 | | |
251 | | bool zend_optimizer_update_op1_const(zend_op_array *op_array, |
252 | | zend_op *opline, |
253 | | zval *val) |
254 | 16.7k | { |
255 | 16.7k | switch (opline->opcode) { |
256 | 605 | case ZEND_OP_DATA: |
257 | 605 | switch ((opline-1)->opcode) { |
258 | 0 | case ZEND_ASSIGN_OBJ_REF: |
259 | 0 | case ZEND_ASSIGN_STATIC_PROP_REF: |
260 | 0 | return false; |
261 | 605 | } |
262 | 605 | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
263 | 605 | break; |
264 | 897 | case ZEND_FREE: |
265 | 899 | case ZEND_CHECK_VAR: |
266 | 899 | MAKE_NOP(opline); |
267 | 899 | zval_ptr_dtor_nogc(val); |
268 | 899 | return true; |
269 | 0 | case ZEND_SEND_VAR_EX: |
270 | 0 | case ZEND_SEND_FUNC_ARG: |
271 | 0 | case ZEND_FETCH_DIM_W: |
272 | 0 | case ZEND_FETCH_DIM_RW: |
273 | 0 | case ZEND_FETCH_DIM_FUNC_ARG: |
274 | 0 | case ZEND_FETCH_DIM_UNSET: |
275 | 0 | case ZEND_FETCH_LIST_W: |
276 | 0 | case ZEND_ASSIGN_DIM: |
277 | 52 | case ZEND_RETURN_BY_REF: |
278 | 56 | case ZEND_INSTANCEOF: |
279 | 56 | case ZEND_MAKE_REF: |
280 | 60 | case ZEND_SEPARATE: |
281 | 60 | case ZEND_SEND_VAR_NO_REF: |
282 | 96 | case ZEND_SEND_VAR_NO_REF_EX: |
283 | 96 | case ZEND_ASSIGN_OP: |
284 | 96 | case ZEND_ASSIGN_DIM_OP: |
285 | 114 | case ZEND_ASSIGN_OBJ: |
286 | 118 | case ZEND_ASSIGN_OBJ_OP: |
287 | 118 | case ZEND_ASSIGN_OBJ_REF: |
288 | 122 | case ZEND_UNSET_OBJ: |
289 | 126 | case ZEND_FETCH_OBJ_W: |
290 | 126 | case ZEND_FETCH_OBJ_RW: |
291 | 126 | case ZEND_FETCH_OBJ_UNSET: |
292 | 130 | case ZEND_PRE_INC_OBJ: |
293 | 130 | case ZEND_PRE_DEC_OBJ: |
294 | 130 | case ZEND_POST_INC_OBJ: |
295 | 130 | case ZEND_POST_DEC_OBJ: |
296 | 130 | return false; |
297 | 0 | case ZEND_CATCH: |
298 | 0 | REQUIRES_STRING(val); |
299 | 0 | drop_leading_backslash(val); |
300 | 0 | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
301 | 0 | opline->extended_value = alloc_cache_slots(op_array, 1) | (opline->extended_value & ZEND_LAST_CATCH); |
302 | 0 | zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); |
303 | 0 | break; |
304 | 0 | case ZEND_DEFINED: |
305 | 0 | REQUIRES_STRING(val); |
306 | 0 | drop_leading_backslash(val); |
307 | 0 | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
308 | 0 | opline->extended_value = alloc_cache_slots(op_array, 1); |
309 | 0 | zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); |
310 | 0 | break; |
311 | 10 | case ZEND_NEW: |
312 | 10 | REQUIRES_STRING(val); |
313 | 10 | drop_leading_backslash(val); |
314 | 10 | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
315 | 10 | opline->op2.num = alloc_cache_slots(op_array, 1); |
316 | 10 | zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); |
317 | 10 | break; |
318 | 8 | case ZEND_INIT_STATIC_METHOD_CALL: |
319 | 8 | REQUIRES_STRING(val); |
320 | 8 | drop_leading_backslash(val); |
321 | 8 | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
322 | 8 | if (opline->op2_type != IS_CONST) { |
323 | 0 | opline->result.num = alloc_cache_slots(op_array, 1); |
324 | 0 | } |
325 | 8 | zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); |
326 | 8 | break; |
327 | 24 | case ZEND_FETCH_CLASS_CONSTANT: |
328 | 24 | REQUIRES_STRING(val); |
329 | 24 | drop_leading_backslash(val); |
330 | 24 | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
331 | 24 | if (opline->op2_type != IS_CONST) { |
332 | 16 | opline->extended_value = alloc_cache_slots(op_array, 1); |
333 | 16 | } |
334 | 24 | zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); |
335 | 24 | break; |
336 | 0 | case ZEND_ASSIGN_STATIC_PROP_OP: |
337 | 0 | case ZEND_ASSIGN_STATIC_PROP: |
338 | 0 | case ZEND_ASSIGN_STATIC_PROP_REF: |
339 | 8 | case ZEND_FETCH_STATIC_PROP_R: |
340 | 8 | case ZEND_FETCH_STATIC_PROP_W: |
341 | 8 | case ZEND_FETCH_STATIC_PROP_RW: |
342 | 8 | case ZEND_FETCH_STATIC_PROP_IS: |
343 | 8 | case ZEND_FETCH_STATIC_PROP_UNSET: |
344 | 10 | case ZEND_FETCH_STATIC_PROP_FUNC_ARG: |
345 | 10 | case ZEND_UNSET_STATIC_PROP: |
346 | 10 | case ZEND_ISSET_ISEMPTY_STATIC_PROP: |
347 | 10 | case ZEND_PRE_INC_STATIC_PROP: |
348 | 10 | case ZEND_PRE_DEC_STATIC_PROP: |
349 | 10 | case ZEND_POST_INC_STATIC_PROP: |
350 | 10 | case ZEND_POST_DEC_STATIC_PROP: |
351 | 10 | TO_STRING_NOWARN(val); |
352 | 10 | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
353 | 10 | if (opline->op2_type == IS_CONST && (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) == op_array->cache_size) { |
354 | 0 | op_array->cache_size += sizeof(void *); |
355 | 10 | } else { |
356 | 10 | opline->extended_value = alloc_cache_slots(op_array, 3) | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS); |
357 | 10 | } |
358 | 10 | break; |
359 | 300 | case ZEND_SEND_VAR: |
360 | 300 | opline->opcode = ZEND_SEND_VAL; |
361 | 300 | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
362 | 300 | break; |
363 | 36 | case ZEND_CASE: |
364 | 36 | opline->opcode = ZEND_IS_EQUAL; |
365 | 36 | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
366 | 36 | break; |
367 | 32 | case ZEND_CASE_STRICT: |
368 | 32 | opline->opcode = ZEND_IS_IDENTICAL; |
369 | 32 | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
370 | 32 | break; |
371 | 0 | case ZEND_VERIFY_RETURN_TYPE: |
372 | | /* This would require a non-local change. |
373 | | * zend_optimizer_replace_by_const() supports this. */ |
374 | 0 | return false; |
375 | 108 | case ZEND_COPY_TMP: |
376 | 112 | case ZEND_FETCH_CLASS_NAME: |
377 | 112 | return false; |
378 | 680 | case ZEND_ECHO: |
379 | 680 | { |
380 | 680 | zval zv; |
381 | 680 | if (Z_TYPE_P(val) != IS_STRING && zend_optimizer_eval_cast(&zv, IS_STRING, val) == SUCCESS) { |
382 | 52 | zval_ptr_dtor_nogc(val); |
383 | 52 | val = &zv; |
384 | 52 | } |
385 | 680 | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
386 | 680 | if (Z_TYPE_P(val) == IS_STRING && Z_STRLEN_P(val) == 0) { |
387 | 20 | MAKE_NOP(opline); |
388 | 20 | return true; |
389 | 20 | } |
390 | | /* TODO: In a subsequent pass, *after* this step and compacting nops, combine consecutive ZEND_ECHOs using the block information from ssa->cfg */ |
391 | | /* (e.g. for ext/opcache/tests/opt/sccp_010.phpt) */ |
392 | 660 | break; |
393 | 680 | } |
394 | 660 | case ZEND_CONCAT: |
395 | 135 | case ZEND_FAST_CONCAT: |
396 | 141 | case ZEND_FETCH_R: |
397 | 145 | case ZEND_FETCH_W: |
398 | 145 | case ZEND_FETCH_RW: |
399 | 145 | case ZEND_FETCH_IS: |
400 | 145 | case ZEND_FETCH_UNSET: |
401 | 149 | case ZEND_FETCH_FUNC_ARG: |
402 | 149 | case ZEND_ISSET_ISEMPTY_VAR: |
403 | 149 | case ZEND_UNSET_VAR: |
404 | 149 | TO_STRING_NOWARN(val); |
405 | 149 | if (opline->opcode == ZEND_CONCAT && opline->op2_type == IS_CONST) { |
406 | 107 | opline->opcode = ZEND_FAST_CONCAT; |
407 | 107 | } |
408 | 149 | ZEND_FALLTHROUGH; |
409 | 13.9k | default: |
410 | 13.9k | opline->op1.constant = zend_optimizer_add_literal(op_array, val); |
411 | 13.9k | break; |
412 | 16.7k | } |
413 | | |
414 | 15.5k | opline->op1_type = IS_CONST; |
415 | 15.5k | if (Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING) { |
416 | 2.14k | zend_string_hash_val(Z_STR(ZEND_OP1_LITERAL(opline))); |
417 | 2.14k | } |
418 | 15.5k | return true; |
419 | 16.7k | } |
420 | | |
421 | | bool zend_optimizer_update_op2_const(zend_op_array *op_array, |
422 | | zend_op *opline, |
423 | | zval *val) |
424 | 8.05k | { |
425 | 8.05k | zval tmp; |
426 | | |
427 | 8.05k | switch (opline->opcode) { |
428 | 102 | case ZEND_ASSIGN_REF: |
429 | 102 | case ZEND_FAST_CALL: |
430 | 102 | return false; |
431 | 46 | case ZEND_FETCH_CLASS: |
432 | 46 | case ZEND_INSTANCEOF: |
433 | 46 | REQUIRES_STRING(val); |
434 | 38 | drop_leading_backslash(val); |
435 | 38 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
436 | 38 | zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); |
437 | 38 | opline->extended_value = alloc_cache_slots(op_array, 1); |
438 | 38 | break; |
439 | 0 | case ZEND_INIT_FCALL_BY_NAME: |
440 | 0 | REQUIRES_STRING(val); |
441 | 0 | drop_leading_backslash(val); |
442 | 0 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
443 | 0 | zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); |
444 | 0 | opline->result.num = alloc_cache_slots(op_array, 1); |
445 | 0 | break; |
446 | 0 | case ZEND_ASSIGN_STATIC_PROP: |
447 | 0 | case ZEND_ASSIGN_STATIC_PROP_REF: |
448 | 24 | case ZEND_FETCH_STATIC_PROP_R: |
449 | 24 | case ZEND_FETCH_STATIC_PROP_W: |
450 | 24 | case ZEND_FETCH_STATIC_PROP_RW: |
451 | 24 | case ZEND_FETCH_STATIC_PROP_IS: |
452 | 24 | case ZEND_FETCH_STATIC_PROP_UNSET: |
453 | 28 | case ZEND_FETCH_STATIC_PROP_FUNC_ARG: |
454 | 28 | case ZEND_UNSET_STATIC_PROP: |
455 | 28 | case ZEND_ISSET_ISEMPTY_STATIC_PROP: |
456 | 28 | case ZEND_PRE_INC_STATIC_PROP: |
457 | 28 | case ZEND_PRE_DEC_STATIC_PROP: |
458 | 28 | case ZEND_POST_INC_STATIC_PROP: |
459 | 28 | case ZEND_POST_DEC_STATIC_PROP: |
460 | 28 | case ZEND_ASSIGN_STATIC_PROP_OP: |
461 | 28 | REQUIRES_STRING(val); |
462 | 26 | drop_leading_backslash(val); |
463 | 26 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
464 | 26 | zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); |
465 | 26 | if (opline->op1_type != IS_CONST) { |
466 | 0 | opline->extended_value = alloc_cache_slots(op_array, 1) | (opline->extended_value & (ZEND_RETURNS_FUNCTION|ZEND_ISEMPTY|ZEND_FETCH_OBJ_FLAGS)); |
467 | 0 | } |
468 | 26 | break; |
469 | 0 | case ZEND_INIT_FCALL: |
470 | 0 | REQUIRES_STRING(val); |
471 | 0 | if (Z_REFCOUNT_P(val) == 1) { |
472 | 0 | zend_str_tolower(Z_STRVAL_P(val), Z_STRLEN_P(val)); |
473 | 0 | } else { |
474 | 0 | ZVAL_STR(&tmp, zend_string_tolower(Z_STR_P(val))); |
475 | 0 | zval_ptr_dtor_nogc(val); |
476 | 0 | val = &tmp; |
477 | 0 | } |
478 | 0 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
479 | 0 | opline->result.num = alloc_cache_slots(op_array, 1); |
480 | 0 | break; |
481 | 64 | case ZEND_INIT_DYNAMIC_CALL: |
482 | 64 | if (Z_TYPE_P(val) == IS_STRING) { |
483 | 36 | if (zend_memrchr(Z_STRVAL_P(val), ':', Z_STRLEN_P(val))) { |
484 | 20 | return false; |
485 | 20 | } |
486 | | |
487 | 16 | if (zend_optimizer_classify_function(Z_STR_P(val), opline->extended_value)) { |
488 | | /* Dynamic call to various special functions must stay dynamic, |
489 | | * otherwise would drop a warning */ |
490 | 12 | return false; |
491 | 12 | } |
492 | | |
493 | 4 | opline->opcode = ZEND_INIT_FCALL_BY_NAME; |
494 | 4 | drop_leading_backslash(val); |
495 | 4 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
496 | 4 | zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); |
497 | 4 | opline->result.num = alloc_cache_slots(op_array, 1); |
498 | 28 | } else { |
499 | 28 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
500 | 28 | } |
501 | 32 | break; |
502 | 32 | case ZEND_INIT_METHOD_CALL: |
503 | 0 | REQUIRES_STRING(val); |
504 | 0 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
505 | 0 | zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); |
506 | 0 | opline->result.num = alloc_cache_slots(op_array, 2); |
507 | 0 | break; |
508 | 0 | case ZEND_INIT_STATIC_METHOD_CALL: |
509 | 0 | REQUIRES_STRING(val); |
510 | 0 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
511 | 0 | zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val))); |
512 | 0 | if (opline->op1_type != IS_CONST) { |
513 | 0 | opline->result.num = alloc_cache_slots(op_array, 2); |
514 | 0 | } |
515 | 0 | break; |
516 | 47 | case ZEND_ASSIGN_OBJ: |
517 | 51 | case ZEND_ASSIGN_OBJ_REF: |
518 | 95 | case ZEND_FETCH_OBJ_R: |
519 | 95 | case ZEND_FETCH_OBJ_W: |
520 | 95 | case ZEND_FETCH_OBJ_RW: |
521 | 95 | case ZEND_FETCH_OBJ_IS: |
522 | 95 | case ZEND_FETCH_OBJ_UNSET: |
523 | 95 | case ZEND_FETCH_OBJ_FUNC_ARG: |
524 | 95 | case ZEND_UNSET_OBJ: |
525 | 95 | case ZEND_PRE_INC_OBJ: |
526 | 107 | case ZEND_PRE_DEC_OBJ: |
527 | 107 | case ZEND_POST_INC_OBJ: |
528 | 107 | case ZEND_POST_DEC_OBJ: |
529 | 107 | TO_STRING_NOWARN(val); |
530 | 105 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
531 | 105 | opline->extended_value = alloc_cache_slots(op_array, 3); |
532 | 105 | break; |
533 | 25 | case ZEND_ASSIGN_OBJ_OP: |
534 | 25 | TO_STRING_NOWARN(val); |
535 | 23 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
536 | 23 | ZEND_ASSERT((opline + 1)->opcode == ZEND_OP_DATA); |
537 | 23 | (opline + 1)->extended_value = alloc_cache_slots(op_array, 3); |
538 | 23 | break; |
539 | 0 | case ZEND_ISSET_ISEMPTY_PROP_OBJ: |
540 | 0 | TO_STRING_NOWARN(val); |
541 | 0 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
542 | 0 | opline->extended_value = alloc_cache_slots(op_array, 3) | (opline->extended_value & ZEND_ISEMPTY); |
543 | 0 | break; |
544 | 9 | case ZEND_ASSIGN_DIM_OP: |
545 | 62 | case ZEND_ISSET_ISEMPTY_DIM_OBJ: |
546 | 457 | case ZEND_ASSIGN_DIM: |
547 | 558 | case ZEND_UNSET_DIM: |
548 | 828 | case ZEND_FETCH_DIM_R: |
549 | 990 | case ZEND_FETCH_DIM_W: |
550 | 990 | case ZEND_FETCH_DIM_RW: |
551 | 990 | case ZEND_FETCH_DIM_IS: |
552 | 1.00k | case ZEND_FETCH_DIM_FUNC_ARG: |
553 | 1.00k | case ZEND_FETCH_DIM_UNSET: |
554 | 1.00k | case ZEND_FETCH_LIST_R: |
555 | 1.00k | case ZEND_FETCH_LIST_W: |
556 | 1.00k | if (Z_TYPE_P(val) == IS_STRING) { |
557 | 267 | zend_ulong index; |
558 | | |
559 | 267 | if (ZEND_HANDLE_NUMERIC(Z_STR_P(val), index)) { |
560 | 169 | ZVAL_LONG(&tmp, index); |
561 | 169 | opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp); |
562 | 169 | zend_string_hash_val(Z_STR_P(val)); |
563 | 169 | zend_optimizer_add_literal(op_array, val); |
564 | 169 | Z_EXTRA(op_array->literals[opline->op2.constant]) = ZEND_EXTRA_VALUE; |
565 | 169 | break; |
566 | 169 | } |
567 | 267 | } |
568 | 840 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
569 | 840 | break; |
570 | 6 | case ZEND_ADD_ARRAY_ELEMENT: |
571 | 28 | case ZEND_INIT_ARRAY: |
572 | 28 | if (Z_TYPE_P(val) == IS_STRING) { |
573 | 0 | zend_ulong index; |
574 | 0 | if (ZEND_HANDLE_NUMERIC(Z_STR_P(val), index)) { |
575 | 0 | zval_ptr_dtor_nogc(val); |
576 | 0 | ZVAL_LONG(val, index); |
577 | 0 | } |
578 | 0 | } |
579 | 28 | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
580 | 28 | break; |
581 | 4 | case ZEND_ROPE_INIT: |
582 | 1.62k | case ZEND_ROPE_ADD: |
583 | 1.62k | case ZEND_ROPE_END: |
584 | 1.75k | case ZEND_CONCAT: |
585 | 1.75k | case ZEND_FAST_CONCAT: |
586 | 1.75k | TO_STRING_NOWARN(val); |
587 | 1.73k | if (opline->opcode == ZEND_CONCAT && opline->op1_type == IS_CONST) { |
588 | 36 | opline->opcode = ZEND_FAST_CONCAT; |
589 | 36 | } |
590 | 1.73k | ZEND_FALLTHROUGH; |
591 | 6.61k | default: |
592 | 6.61k | opline->op2.constant = zend_optimizer_add_literal(op_array, val); |
593 | 6.61k | break; |
594 | 8.05k | } |
595 | | |
596 | 7.87k | opline->op2_type = IS_CONST; |
597 | 7.87k | if (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) { |
598 | 2.40k | zend_string_hash_val(Z_STR(ZEND_OP2_LITERAL(opline))); |
599 | 2.40k | } |
600 | 7.87k | return true; |
601 | 8.05k | } |
602 | | |
603 | | bool zend_optimizer_replace_by_const(zend_op_array *op_array, |
604 | | zend_op *opline, |
605 | | uint8_t type, |
606 | | uint32_t var, |
607 | | zval *val) |
608 | 2.09k | { |
609 | 2.09k | const zend_op *end = op_array->opcodes + op_array->last; |
610 | | |
611 | 2.13k | while (opline < end) { |
612 | 2.13k | if (opline->op1_type == type && |
613 | 1.82k | opline->op1.var == var) { |
614 | 1.73k | switch (opline->opcode) { |
615 | | /* In most cases IS_TMP_VAR operand may be used only once. |
616 | | * The operands are usually destroyed by the opcode handler. |
617 | | * However, there are some exception which keep the operand alive. In that case |
618 | | * we want to try to replace all uses of the temporary. |
619 | | */ |
620 | 0 | case ZEND_FETCH_LIST_R: |
621 | 8 | case ZEND_CASE: |
622 | 8 | case ZEND_CASE_STRICT: |
623 | 8 | case ZEND_SWITCH_LONG: |
624 | 8 | case ZEND_SWITCH_STRING: |
625 | 8 | case ZEND_MATCH: |
626 | 8 | case ZEND_MATCH_ERROR: |
627 | 24 | case ZEND_JMP_NULL: { |
628 | 24 | const zend_op *end = op_array->opcodes + op_array->last; |
629 | 88 | while (opline < end) { |
630 | 88 | if (opline->op1_type == type && opline->op1.var == var) { |
631 | | /* If this opcode doesn't keep the operand alive, we're done. Check |
632 | | * this early, because op replacement may modify the opline. */ |
633 | 48 | bool is_last = opline->opcode != ZEND_FETCH_LIST_R |
634 | 48 | && opline->opcode != ZEND_CASE |
635 | 40 | && opline->opcode != ZEND_CASE_STRICT |
636 | 40 | && opline->opcode != ZEND_SWITCH_LONG |
637 | 40 | && opline->opcode != ZEND_SWITCH_STRING |
638 | 40 | && opline->opcode != ZEND_MATCH |
639 | 40 | && opline->opcode != ZEND_MATCH_ERROR |
640 | 40 | && opline->opcode != ZEND_JMP_NULL |
641 | 24 | && (opline->opcode != ZEND_FREE |
642 | 8 | || opline->extended_value != ZEND_FREE_ON_RETURN); |
643 | | |
644 | 48 | Z_TRY_ADDREF_P(val); |
645 | 48 | if (!zend_optimizer_update_op1_const(op_array, opline, val)) { |
646 | 0 | zval_ptr_dtor(val); |
647 | 0 | return false; |
648 | 0 | } |
649 | 48 | if (is_last) { |
650 | 24 | break; |
651 | 24 | } |
652 | 48 | } |
653 | 64 | opline++; |
654 | 64 | } |
655 | 24 | zval_ptr_dtor_nogc(val); |
656 | 24 | return true; |
657 | 24 | } |
658 | 4 | case ZEND_VERIFY_RETURN_TYPE: { |
659 | 4 | const zend_arg_info *ret_info = op_array->arg_info - 1; |
660 | 4 | if (!ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(val)) |
661 | 4 | || (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)) { |
662 | 0 | return false; |
663 | 0 | } |
664 | 4 | MAKE_NOP(opline); |
665 | | |
666 | | /* zend_handle_loops_and_finally may inserts other oplines */ |
667 | 4 | do { |
668 | 4 | ++opline; |
669 | 4 | } while (opline->opcode != ZEND_RETURN && opline->opcode != ZEND_RETURN_BY_REF); |
670 | 4 | ZEND_ASSERT(opline->op1.var == var); |
671 | | |
672 | 4 | break; |
673 | 4 | } |
674 | 1.71k | default: |
675 | 1.71k | break; |
676 | 1.73k | } |
677 | 1.71k | return zend_optimizer_update_op1_const(op_array, opline, val); |
678 | 1.73k | } |
679 | | |
680 | 396 | if (opline->op2_type == type && |
681 | 360 | opline->op2.var == var) { |
682 | 360 | return zend_optimizer_update_op2_const(op_array, opline, val); |
683 | 360 | } |
684 | 36 | opline++; |
685 | 36 | } |
686 | | |
687 | 0 | return true; |
688 | 2.09k | } |
689 | | |
690 | | /* Update jump offsets after a jump was migrated to another opline */ |
691 | 49.2k | void zend_optimizer_migrate_jump(const zend_op_array *op_array, zend_op *new_opline, zend_op *opline) { |
692 | 49.2k | switch (new_opline->opcode) { |
693 | 7.84k | case ZEND_JMP: |
694 | 7.84k | case ZEND_FAST_CALL: |
695 | 7.84k | ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op1, ZEND_OP1_JMP_ADDR(opline)); |
696 | 7.84k | break; |
697 | 4.72k | case ZEND_JMPZ: |
698 | 11.4k | case ZEND_JMPNZ: |
699 | 11.5k | case ZEND_JMPZ_EX: |
700 | 11.7k | case ZEND_JMPNZ_EX: |
701 | 12.1k | case ZEND_FE_RESET_R: |
702 | 12.1k | case ZEND_FE_RESET_RW: |
703 | 12.6k | case ZEND_JMP_SET: |
704 | 13.4k | case ZEND_COALESCE: |
705 | 13.4k | case ZEND_ASSERT_CHECK: |
706 | 15.8k | case ZEND_JMP_NULL: |
707 | 15.8k | case ZEND_BIND_INIT_STATIC_OR_JMP: |
708 | 15.8k | case ZEND_JMP_FRAMELESS: |
709 | 15.8k | ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline)); |
710 | 15.8k | break; |
711 | 348 | case ZEND_FE_FETCH_R: |
712 | 396 | case ZEND_FE_FETCH_RW: |
713 | 396 | new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); |
714 | 396 | break; |
715 | 0 | case ZEND_CATCH: |
716 | 0 | if (!(opline->extended_value & ZEND_LAST_CATCH)) { |
717 | 0 | ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline)); |
718 | 0 | } |
719 | 0 | break; |
720 | 2 | case ZEND_SWITCH_LONG: |
721 | 11 | case ZEND_SWITCH_STRING: |
722 | 19 | case ZEND_MATCH: |
723 | 19 | { |
724 | 19 | const HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline)); |
725 | 19 | zval *zv; |
726 | 115 | ZEND_HASH_FOREACH_VAL(jumptable, zv) { |
727 | 115 | Z_LVAL_P(zv) = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv))); |
728 | 115 | } ZEND_HASH_FOREACH_END(); |
729 | 19 | new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); |
730 | 19 | break; |
731 | 11 | } |
732 | 49.2k | } |
733 | 49.2k | } |
734 | | |
735 | | /* Shift jump offsets based on shiftlist */ |
736 | 70.4k | void zend_optimizer_shift_jump(const zend_op_array *op_array, zend_op *opline, const uint32_t *shiftlist) { |
737 | 70.4k | switch (opline->opcode) { |
738 | 9.47k | case ZEND_JMP: |
739 | 9.47k | case ZEND_FAST_CALL: |
740 | 9.47k | ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(opline) - shiftlist[ZEND_OP1_JMP_ADDR(opline) - op_array->opcodes]); |
741 | 9.47k | break; |
742 | 5.81k | case ZEND_JMPZ: |
743 | 13.4k | case ZEND_JMPNZ: |
744 | 13.6k | case ZEND_JMPZ_EX: |
745 | 13.9k | case ZEND_JMPNZ_EX: |
746 | 14.7k | case ZEND_FE_RESET_R: |
747 | 14.8k | case ZEND_FE_RESET_RW: |
748 | 15.3k | case ZEND_JMP_SET: |
749 | 16.6k | case ZEND_COALESCE: |
750 | 16.7k | case ZEND_ASSERT_CHECK: |
751 | 25.4k | case ZEND_JMP_NULL: |
752 | 25.4k | case ZEND_BIND_INIT_STATIC_OR_JMP: |
753 | 25.4k | case ZEND_JMP_FRAMELESS: |
754 | 25.4k | ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]); |
755 | 25.4k | break; |
756 | 0 | case ZEND_CATCH: |
757 | 0 | if (!(opline->extended_value & ZEND_LAST_CATCH)) { |
758 | 0 | ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]); |
759 | 0 | } |
760 | 0 | break; |
761 | 768 | case ZEND_FE_FETCH_R: |
762 | 882 | case ZEND_FE_FETCH_RW: |
763 | 882 | opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); |
764 | 882 | break; |
765 | 6 | case ZEND_SWITCH_LONG: |
766 | 23 | case ZEND_SWITCH_STRING: |
767 | 67 | case ZEND_MATCH: |
768 | 67 | { |
769 | 67 | const HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline)); |
770 | 67 | zval *zv; |
771 | 629 | ZEND_HASH_FOREACH_VAL(jumptable, zv) { |
772 | 629 | Z_LVAL_P(zv) = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv)) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv))]); |
773 | 629 | } ZEND_HASH_FOREACH_END(); |
774 | 67 | opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); |
775 | 67 | break; |
776 | 23 | } |
777 | 70.4k | } |
778 | 70.4k | } |
779 | | |
780 | | static bool zend_optimizer_ignore_class(zval *ce_zv, const zend_string *filename) |
781 | 73.7k | { |
782 | 73.7k | const zend_class_entry *ce = Z_PTR_P(ce_zv); |
783 | | |
784 | 73.7k | if (ce->ce_flags & ZEND_ACC_PRELOADED) { |
785 | 0 | if (CG(compiler_options) & ZEND_COMPILE_WITH_FILE_CACHE) { |
786 | 0 | return true; |
787 | 0 | } |
788 | 0 | const Bucket *ce_bucket = ZEND_CONTAINER_OF(ce_zv, Bucket, val); |
789 | 0 | size_t offset = ce_bucket - EG(class_table)->arData; |
790 | 0 | if (offset < EG(persistent_classes_count)) { |
791 | 0 | return false; |
792 | 0 | } |
793 | 0 | } |
794 | 73.7k | return ce->type == ZEND_USER_CLASS |
795 | 38 | && (!ce->info.user.filename || ce->info.user.filename != filename); |
796 | 73.7k | } |
797 | | |
798 | | static bool zend_optimizer_ignore_function(zval *fbc_zv, const zend_string *filename) |
799 | 217k | { |
800 | 217k | const zend_function *fbc = Z_PTR_P(fbc_zv); |
801 | | |
802 | 217k | if (fbc->type == ZEND_INTERNAL_FUNCTION) { |
803 | 216k | return false; |
804 | 216k | } else if (fbc->type == ZEND_USER_FUNCTION) { |
805 | 1.09k | if (fbc->op_array.fn_flags & ZEND_ACC_PRELOADED) { |
806 | 0 | if (CG(compiler_options) & ZEND_COMPILE_WITH_FILE_CACHE) { |
807 | 0 | return true; |
808 | 0 | } |
809 | 0 | const Bucket *fbc_bucket = ZEND_CONTAINER_OF(fbc_zv, Bucket, val); |
810 | 0 | size_t offset = fbc_bucket - EG(function_table)->arData; |
811 | 0 | if (offset < EG(persistent_functions_count)) { |
812 | 0 | return false; |
813 | 0 | } |
814 | 0 | } |
815 | 1.09k | return !fbc->op_array.filename || fbc->op_array.filename != filename; |
816 | 1.09k | } else { |
817 | 0 | ZEND_ASSERT(fbc->type == ZEND_EVAL_CODE); |
818 | 0 | return true; |
819 | 0 | } |
820 | 217k | } |
821 | | |
822 | | zend_class_entry *zend_optimizer_get_class_entry( |
823 | 150k | const zend_script *script, const zend_op_array *op_array, zend_string *lcname) { |
824 | 150k | zend_class_entry *ce = script ? zend_hash_find_ptr(&script->class_table, lcname) : NULL; |
825 | 150k | if (ce) { |
826 | 57.0k | return ce; |
827 | 57.0k | } |
828 | | |
829 | 93.7k | zval *ce_zv = zend_hash_find(CG(class_table), lcname); |
830 | 93.7k | if (ce_zv && !zend_optimizer_ignore_class(ce_zv, op_array ? op_array->filename : NULL)) { |
831 | 73.6k | return Z_PTR_P(ce_zv); |
832 | 73.6k | } |
833 | | |
834 | 20.0k | if (op_array && op_array->scope && zend_string_equals_ci(op_array->scope->name, lcname)) { |
835 | 145 | return op_array->scope; |
836 | 145 | } |
837 | | |
838 | 19.9k | return NULL; |
839 | 20.0k | } |
840 | | |
841 | | zend_class_entry *zend_optimizer_get_class_entry_from_op1( |
842 | 142k | const zend_script *script, const zend_op_array *op_array, const zend_op *opline) { |
843 | 142k | if (opline->op1_type == IS_CONST) { |
844 | 136k | const zval *op1 = CRT_CONSTANT(opline->op1); |
845 | 136k | if (Z_TYPE_P(op1) == IS_STRING) { |
846 | 136k | return zend_optimizer_get_class_entry(script, op_array, Z_STR_P(op1 + 1)); |
847 | 136k | } |
848 | 136k | } else if (opline->op1_type == IS_UNUSED && op_array->scope |
849 | 2.43k | && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT) |
850 | 2.31k | && ((opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF |
851 | 1.49k | || ((opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_STATIC |
852 | 853 | && (op_array->scope->ce_flags & ZEND_ACC_FINAL)))) { |
853 | 853 | return op_array->scope; |
854 | 853 | } |
855 | 5.54k | return NULL; |
856 | 142k | } |
857 | | |
858 | | const zend_class_constant *zend_fetch_class_const_info( |
859 | 8.82k | const zend_script *script, const zend_op_array *op_array, const zend_op *opline, bool *is_prototype) { |
860 | 8.82k | const zend_class_entry *ce = NULL; |
861 | 8.82k | bool is_static_reference = false; |
862 | | |
863 | 8.82k | if (!opline || !op_array || opline->op2_type != IS_CONST || Z_TYPE_P(CRT_CONSTANT(opline->op2)) != IS_STRING) { |
864 | 236 | return NULL; |
865 | 236 | } |
866 | 8.59k | if (opline->op1_type == IS_CONST) { |
867 | 6.76k | const zval *op1 = CRT_CONSTANT(opline->op1); |
868 | 6.76k | if (Z_TYPE_P(op1) == IS_STRING) { |
869 | 6.76k | if (script) { |
870 | 6.76k | ce = zend_optimizer_get_class_entry(script, op_array, Z_STR_P(op1 + 1)); |
871 | 6.76k | } else { |
872 | 0 | zval *ce_zv = zend_hash_find(EG(class_table), Z_STR_P(op1 + 1)); |
873 | 0 | if (ce_zv && !zend_optimizer_ignore_class(ce_zv, op_array->filename)) { |
874 | 0 | ce = Z_PTR_P(ce_zv); |
875 | 0 | } |
876 | 0 | } |
877 | 6.76k | } |
878 | 6.76k | } else if (opline->op1_type == IS_UNUSED |
879 | 803 | && op_array->scope && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT) |
880 | 679 | && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) { |
881 | 679 | uint32_t fetch_type = opline->op1.num & ZEND_FETCH_CLASS_MASK; |
882 | 679 | if (fetch_type == ZEND_FETCH_CLASS_SELF) { |
883 | 455 | ce = op_array->scope; |
884 | 455 | } else if (fetch_type == ZEND_FETCH_CLASS_STATIC) { |
885 | 164 | ce = op_array->scope; |
886 | 164 | is_static_reference = true; |
887 | 164 | } else if (fetch_type == ZEND_FETCH_CLASS_PARENT) { |
888 | 60 | if (op_array->scope->ce_flags & ZEND_ACC_LINKED) { |
889 | 36 | ce = op_array->scope->parent; |
890 | 36 | } |
891 | 60 | } |
892 | 679 | } |
893 | 8.59k | if (!ce || (ce->ce_flags & ZEND_ACC_TRAIT)) { |
894 | 5.38k | return NULL; |
895 | 5.38k | } |
896 | 3.20k | zend_class_constant *const_info = zend_hash_find_ptr(&ce->constants_table, Z_STR_P(CRT_CONSTANT(opline->op2))); |
897 | 3.20k | if (!const_info) { |
898 | 280 | return NULL; |
899 | 280 | } |
900 | 2.92k | if ((ZEND_CLASS_CONST_FLAGS(const_info) & ZEND_ACC_DEPRECATED) |
901 | 2.82k | || ((ZEND_CLASS_CONST_FLAGS(const_info) & ZEND_ACC_PPP_MASK) != ZEND_ACC_PUBLIC && const_info->ce != op_array->scope)) { |
902 | 112 | return NULL; |
903 | 112 | } |
904 | 2.81k | *is_prototype = is_static_reference |
905 | 116 | && !(const_info->ce->ce_flags & ZEND_ACC_FINAL) && !(ZEND_CLASS_CONST_FLAGS(const_info) & ZEND_ACC_FINAL); |
906 | | |
907 | 2.81k | return const_info; |
908 | 2.92k | } |
909 | | |
910 | | zend_function *zend_optimizer_get_called_func( |
911 | | const zend_script *script, const zend_op_array *op_array, zend_op *opline, bool *is_prototype) |
912 | 450k | { |
913 | 450k | *is_prototype = false; |
914 | 450k | switch (opline->opcode) { |
915 | 273k | case ZEND_INIT_FCALL: |
916 | 273k | { |
917 | 273k | zend_string *function_name = Z_STR_P(CRT_CONSTANT(opline->op2)); |
918 | 273k | zend_function *func; |
919 | 273k | zval *func_zv; |
920 | 273k | if (script && (func = zend_hash_find_ptr(&script->function_table, function_name)) != NULL) { |
921 | 56.0k | return func; |
922 | 217k | } else if ((func_zv = zend_hash_find(EG(function_table), function_name)) != NULL) { |
923 | 217k | if (!zend_optimizer_ignore_function(func_zv, op_array->filename)) { |
924 | 217k | return Z_PTR_P(func_zv); |
925 | 217k | } |
926 | 217k | } |
927 | 0 | break; |
928 | 273k | } |
929 | 9.45k | case ZEND_INIT_FCALL_BY_NAME: |
930 | 12.5k | case ZEND_INIT_NS_FCALL_BY_NAME: |
931 | 12.5k | if (opline->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_STRING) { |
932 | 12.5k | const zval *function_name = CRT_CONSTANT(opline->op2) + 1; |
933 | 12.5k | zend_function *func; |
934 | 12.5k | zval *func_zv; |
935 | 12.5k | if (script && (func = zend_hash_find_ptr(&script->function_table, Z_STR_P(function_name)))) { |
936 | 1.92k | return func; |
937 | 10.6k | } else if ((func_zv = zend_hash_find(EG(function_table), Z_STR_P(function_name))) != NULL) { |
938 | 44 | if (!zend_optimizer_ignore_function(func_zv, op_array->filename)) { |
939 | 10 | return Z_PTR_P(func_zv); |
940 | 10 | } |
941 | 44 | } |
942 | 12.5k | } |
943 | 10.6k | break; |
944 | 15.0k | case ZEND_INIT_STATIC_METHOD_CALL: |
945 | 15.0k | if (opline->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_STRING) { |
946 | 13.7k | const zend_class_entry *ce = zend_optimizer_get_class_entry_from_op1( |
947 | 13.7k | script, op_array, opline); |
948 | 13.7k | if (ce) { |
949 | 9.65k | zend_string *func_name = Z_STR_P(CRT_CONSTANT(opline->op2) + 1); |
950 | 9.65k | zend_function *fbc = zend_hash_find_ptr(&ce->function_table, func_name); |
951 | 9.65k | if (fbc && !(fbc->common.fn_flags & ZEND_ACC_ABSTRACT)) { |
952 | 8.49k | bool is_public = (fbc->common.fn_flags & ZEND_ACC_PUBLIC) != 0; |
953 | 8.49k | bool same_scope = fbc->common.scope == op_array->scope; |
954 | 8.49k | if (is_public || same_scope) { |
955 | 8.34k | return fbc; |
956 | 8.34k | } |
957 | 8.49k | } |
958 | 9.65k | } |
959 | 13.7k | } |
960 | 6.74k | break; |
961 | 89.9k | case ZEND_INIT_METHOD_CALL: |
962 | 89.9k | if (opline->op1_type == IS_UNUSED |
963 | 1.86k | && opline->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_STRING |
964 | 1.75k | && op_array->scope |
965 | 1.73k | && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE) |
966 | 1.73k | && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT)) { |
967 | 1.68k | zend_string *method_name = Z_STR_P(CRT_CONSTANT(opline->op2) + 1); |
968 | 1.68k | zend_function *fbc = zend_hash_find_ptr( |
969 | 1.68k | &op_array->scope->function_table, method_name); |
970 | 1.68k | if (fbc) { |
971 | 1.36k | bool is_private = (fbc->common.fn_flags & ZEND_ACC_PRIVATE) != 0; |
972 | 1.36k | if (is_private) { |
973 | | /* Only use private method if in the same scope. We can't even use it |
974 | | * as a prototype, as it may be overridden with changed signature. */ |
975 | 238 | bool same_scope = fbc->common.scope == op_array->scope; |
976 | 238 | return same_scope ? fbc : NULL; |
977 | 238 | } |
978 | | /* Prototype methods are potentially overridden. fbc still contains useful type information. |
979 | | * Some optimizations may not be applied, like inlining or inferring the send-mode of superfluous args. |
980 | | * A method cannot be overridden if the class or method is final. */ |
981 | 1.12k | if ((fbc->common.fn_flags & ZEND_ACC_FINAL) == 0 && |
982 | 1.11k | (fbc->common.scope->ce_flags & ZEND_ACC_FINAL) == 0) { |
983 | 1.11k | *is_prototype = true; |
984 | 1.11k | } |
985 | 1.12k | return fbc; |
986 | 1.36k | } |
987 | 1.68k | } |
988 | 88.6k | break; |
989 | 88.6k | case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: { |
990 | 520 | const zend_class_entry *scope = op_array->scope; |
991 | 520 | ZEND_ASSERT(scope != NULL); |
992 | 520 | if ((scope->ce_flags & ZEND_ACC_LINKED) && scope->parent) { |
993 | 480 | const zend_class_entry *parent_scope = scope->parent; |
994 | 480 | zend_string *prop_name = Z_STR_P(CRT_CONSTANT(opline->op1)); |
995 | 480 | zend_property_hook_kind hook_kind = opline->op2.num; |
996 | 480 | const zend_property_info *prop_info = zend_get_property_info(parent_scope, prop_name, /* silent */ true); |
997 | | |
998 | 480 | if (prop_info |
999 | 436 | && prop_info != ZEND_WRONG_PROPERTY_INFO |
1000 | 420 | && !(prop_info->flags & ZEND_ACC_PRIVATE) |
1001 | 420 | && prop_info->hooks) { |
1002 | 160 | zend_function *fbc = prop_info->hooks[hook_kind]; |
1003 | 160 | if (fbc) { |
1004 | 152 | *is_prototype = false; |
1005 | 152 | return fbc; |
1006 | 152 | } |
1007 | 160 | } |
1008 | 480 | } |
1009 | 368 | break; |
1010 | 520 | } |
1011 | 59.1k | case ZEND_NEW: |
1012 | 59.1k | { |
1013 | 59.1k | const zend_class_entry *ce = zend_optimizer_get_class_entry_from_op1( |
1014 | 59.1k | script, op_array, opline); |
1015 | 59.1k | if (ce && ce->type == ZEND_USER_CLASS) { |
1016 | 14.2k | return ce->constructor; |
1017 | 14.2k | } |
1018 | 44.8k | break; |
1019 | 59.1k | } |
1020 | 450k | } |
1021 | 151k | return NULL; |
1022 | 450k | } |
1023 | | |
1024 | 194k | uint32_t zend_optimizer_classify_function(const zend_string *name, uint32_t num_args) { |
1025 | 194k | if (zend_string_equals_literal(name, "extract")) { |
1026 | 60 | return ZEND_FUNC_INDIRECT_VAR_ACCESS; |
1027 | 194k | } else if (zend_string_equals_literal(name, "compact")) { |
1028 | 128 | return ZEND_FUNC_INDIRECT_VAR_ACCESS; |
1029 | 194k | } else if (zend_string_equals_literal(name, "get_defined_vars")) { |
1030 | 148 | return ZEND_FUNC_INDIRECT_VAR_ACCESS; |
1031 | 193k | } else if (zend_string_equals_literal(name, "db2_execute")) { |
1032 | 0 | return ZEND_FUNC_INDIRECT_VAR_ACCESS; |
1033 | 193k | } else if (zend_string_equals_literal(name, "func_num_args")) { |
1034 | 48 | return ZEND_FUNC_VARARG; |
1035 | 193k | } else if (zend_string_equals_literal(name, "func_get_arg")) { |
1036 | 292 | return ZEND_FUNC_VARARG; |
1037 | 193k | } else if (zend_string_equals_literal(name, "func_get_args")) { |
1038 | 74 | return ZEND_FUNC_VARARG; |
1039 | 193k | } else { |
1040 | 193k | return 0; |
1041 | 193k | } |
1042 | 194k | } |
1043 | | |
1044 | 152 | zend_op *zend_optimizer_get_loop_var_def(const zend_op_array *op_array, zend_op *free_opline) { |
1045 | 152 | uint32_t var = free_opline->op1.var; |
1046 | 152 | ZEND_ASSERT(zend_optimizer_is_loop_var_free(free_opline)); |
1047 | | |
1048 | 1.72k | while (--free_opline >= op_array->opcodes) { |
1049 | 1.72k | if ((free_opline->result_type & (IS_TMP_VAR|IS_VAR)) && free_opline->result.var == var) { |
1050 | 148 | return free_opline; |
1051 | 148 | } |
1052 | 1.72k | } |
1053 | 4 | return NULL; |
1054 | 152 | } |
1055 | | |
1056 | | static void zend_optimize(zend_op_array *op_array, |
1057 | | zend_optimizer_ctx *ctx) |
1058 | 115k | { |
1059 | 115k | if (op_array->type == ZEND_EVAL_CODE) { |
1060 | 0 | return; |
1061 | 0 | } |
1062 | | |
1063 | 115k | if (ctx->debug_level & ZEND_DUMP_BEFORE_OPTIMIZER) { |
1064 | 0 | uint32_t additional_dump_flags = (ctx->debug_level & ZEND_DUMP_LINE_NUMBERS_PASSTHRU) |
1065 | 0 | ? ZEND_DUMP_LINE_NUMBERS |
1066 | 0 | : 0; |
1067 | 0 | zend_dump_op_array(op_array, ZEND_DUMP_LIVE_RANGES|additional_dump_flags, "before optimizer", NULL); |
1068 | 0 | } |
1069 | | |
1070 | | /* pass 1 (Simple local optimizations) |
1071 | | * - persistent constant substitution (true, false, null, etc) |
1072 | | * - constant casting (ADD expects numbers, CONCAT strings, etc) |
1073 | | * - constant expression evaluation |
1074 | | * - optimize constant conditional JMPs |
1075 | | * - pre-evaluate constant function calls |
1076 | | * - eliminate FETCH $GLOBALS followed by FETCH_DIM/UNSET_DIM/ISSET_ISEMPTY_DIM |
1077 | | */ |
1078 | 115k | if (ZEND_OPTIMIZER_PASS_1 & ctx->optimization_level) { |
1079 | 115k | zend_optimizer_pass1(op_array, ctx); |
1080 | 115k | if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_1) { |
1081 | 0 | zend_dump_op_array(op_array, 0, "after pass 1", NULL); |
1082 | 0 | } |
1083 | 115k | } |
1084 | | |
1085 | | /* pass 3: (Jump optimization) |
1086 | | * - optimize series of JMPs |
1087 | | */ |
1088 | 115k | if (ZEND_OPTIMIZER_PASS_3 & ctx->optimization_level) { |
1089 | 115k | zend_optimizer_pass3(op_array, ctx); |
1090 | 115k | if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_3) { |
1091 | 0 | zend_dump_op_array(op_array, 0, "after pass 3", NULL); |
1092 | 0 | } |
1093 | 115k | } |
1094 | | |
1095 | | /* pass 4: |
1096 | | * - INIT_FCALL_BY_NAME -> DO_FCALL |
1097 | | */ |
1098 | 115k | if (ZEND_OPTIMIZER_PASS_4 & ctx->optimization_level) { |
1099 | 115k | zend_optimize_func_calls(op_array, ctx); |
1100 | 115k | if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_4) { |
1101 | 0 | zend_dump_op_array(op_array, 0, "after pass 4", NULL); |
1102 | 0 | } |
1103 | 115k | } |
1104 | | |
1105 | | /* pass 5: |
1106 | | * - CFG optimization |
1107 | | */ |
1108 | 115k | if (ZEND_OPTIMIZER_PASS_5 & ctx->optimization_level) { |
1109 | 115k | zend_optimize_cfg(op_array, ctx); |
1110 | 115k | if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_5) { |
1111 | 0 | zend_dump_op_array(op_array, 0, "after pass 5", NULL); |
1112 | 0 | } |
1113 | 115k | } |
1114 | | |
1115 | | /* pass 6: |
1116 | | * - DFA optimization |
1117 | | */ |
1118 | 115k | if ((ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) && |
1119 | 115k | !(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level)) { |
1120 | 0 | zend_optimize_dfa(op_array, ctx); |
1121 | 0 | if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_6) { |
1122 | 0 | zend_dump_op_array(op_array, 0, "after pass 6", NULL); |
1123 | 0 | } |
1124 | 0 | } |
1125 | | |
1126 | | /* pass 9: |
1127 | | * - Optimize temp variables usage |
1128 | | */ |
1129 | 115k | if ((ZEND_OPTIMIZER_PASS_9 & ctx->optimization_level) && |
1130 | 115k | !(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level)) { |
1131 | 0 | zend_optimize_temporary_variables(op_array, ctx); |
1132 | 0 | if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_9) { |
1133 | 0 | zend_dump_op_array(op_array, 0, "after pass 9", NULL); |
1134 | 0 | } |
1135 | 0 | } |
1136 | | |
1137 | | /* pass 10: |
1138 | | * - remove NOPs |
1139 | | */ |
1140 | 115k | if (((ZEND_OPTIMIZER_PASS_10|ZEND_OPTIMIZER_PASS_5) & ctx->optimization_level) == ZEND_OPTIMIZER_PASS_10) { |
1141 | 0 | zend_optimizer_nop_removal(op_array, ctx); |
1142 | 0 | if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_10) { |
1143 | 0 | zend_dump_op_array(op_array, 0, "after pass 10", NULL); |
1144 | 0 | } |
1145 | 0 | } |
1146 | | |
1147 | | /* pass 11: |
1148 | | * - Compact literals table |
1149 | | */ |
1150 | 115k | if ((ZEND_OPTIMIZER_PASS_11 & ctx->optimization_level) && |
1151 | 115k | (!(ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) || |
1152 | 115k | !(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level))) { |
1153 | 0 | zend_optimizer_compact_literals(op_array, ctx); |
1154 | 0 | if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_11) { |
1155 | 0 | zend_dump_op_array(op_array, 0, "after pass 11", NULL); |
1156 | 0 | } |
1157 | 0 | } |
1158 | | |
1159 | 115k | if ((ZEND_OPTIMIZER_PASS_13 & ctx->optimization_level) && |
1160 | 115k | (!(ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) || |
1161 | 115k | !(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level))) { |
1162 | 0 | zend_optimizer_compact_vars(op_array); |
1163 | 0 | if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_13) { |
1164 | 0 | zend_dump_op_array(op_array, 0, "after pass 13", NULL); |
1165 | 0 | } |
1166 | 0 | } |
1167 | | |
1168 | 115k | if (ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level) { |
1169 | 115k | return; |
1170 | 115k | } |
1171 | | |
1172 | 0 | if (ctx->debug_level & ZEND_DUMP_AFTER_OPTIMIZER) { |
1173 | 0 | zend_dump_op_array(op_array, 0, "after optimizer", NULL); |
1174 | 0 | } |
1175 | 0 | } |
1176 | | |
1177 | | static void zend_revert_pass_two(zend_op_array *op_array) |
1178 | 115k | { |
1179 | 115k | zend_op *opline; |
1180 | | |
1181 | 115k | ZEND_ASSERT((op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) != 0); |
1182 | | |
1183 | 115k | opline = op_array->opcodes; |
1184 | 115k | const zend_op *end = opline + op_array->last; |
1185 | 2.94M | while (opline < end) { |
1186 | 2.82M | if (opline->op1_type == IS_CONST) { |
1187 | 513k | ZEND_PASS_TWO_UNDO_CONSTANT(op_array, opline, opline->op1); |
1188 | 513k | } |
1189 | 2.82M | if (opline->op2_type == IS_CONST) { |
1190 | 710k | ZEND_PASS_TWO_UNDO_CONSTANT(op_array, opline, opline->op2); |
1191 | 710k | } |
1192 | | /* reset smart branch flags IS_SMART_BRANCH_JMP[N]Z */ |
1193 | 2.82M | opline->result_type &= (IS_TMP_VAR|IS_VAR|IS_CV|IS_CONST); |
1194 | 2.82M | opline++; |
1195 | 2.82M | } |
1196 | 115k | #if !ZEND_USE_ABS_CONST_ADDR |
1197 | 115k | if (op_array->literals) { |
1198 | 115k | zval *literals = emalloc(sizeof(zval) * op_array->last_literal); |
1199 | 115k | memcpy(literals, op_array->literals, sizeof(zval) * op_array->last_literal); |
1200 | 115k | op_array->literals = literals; |
1201 | 115k | } |
1202 | 115k | #endif |
1203 | | |
1204 | 115k | op_array->fn_flags &= ~ZEND_ACC_DONE_PASS_TWO; |
1205 | 115k | } |
1206 | | |
1207 | | static void zend_redo_pass_two(zend_op_array *op_array) |
1208 | 25.5k | { |
1209 | 25.5k | zend_op *opline, *end; |
1210 | | #if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR |
1211 | | zend_op *old_opcodes = op_array->opcodes; |
1212 | | #endif |
1213 | | |
1214 | 25.5k | ZEND_ASSERT((op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) == 0); |
1215 | | |
1216 | 25.5k | #if !ZEND_USE_ABS_CONST_ADDR |
1217 | 25.5k | if (op_array->last_literal) { |
1218 | 25.5k | op_array->opcodes = (zend_op *) erealloc(op_array->opcodes, |
1219 | 25.5k | ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16) + |
1220 | 25.5k | sizeof(zval) * op_array->last_literal); |
1221 | 25.5k | memcpy(((char*)op_array->opcodes) + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16), |
1222 | 25.5k | op_array->literals, sizeof(zval) * op_array->last_literal); |
1223 | 25.5k | efree(op_array->literals); |
1224 | 25.5k | op_array->literals = (zval*)(((char*)op_array->opcodes) + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16)); |
1225 | 25.5k | } else { |
1226 | 74 | if (op_array->literals) { |
1227 | 74 | efree(op_array->literals); |
1228 | 74 | } |
1229 | 74 | op_array->literals = NULL; |
1230 | 74 | } |
1231 | 25.5k | #endif |
1232 | | |
1233 | 25.5k | opline = op_array->opcodes; |
1234 | 25.5k | end = opline + op_array->last; |
1235 | 986k | while (opline < end) { |
1236 | 960k | if (opline->op1_type == IS_CONST) { |
1237 | 209k | ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline, opline->op1); |
1238 | 209k | } |
1239 | 960k | if (opline->op2_type == IS_CONST) { |
1240 | 191k | ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline, opline->op2); |
1241 | 191k | } |
1242 | | /* fix jumps to point to new array */ |
1243 | 960k | switch (opline->opcode) { |
1244 | | #if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR |
1245 | | case ZEND_JMP: |
1246 | | case ZEND_FAST_CALL: |
1247 | | opline->op1.jmp_addr = &op_array->opcodes[opline->op1.jmp_addr - old_opcodes]; |
1248 | | break; |
1249 | | case ZEND_JMPZ: |
1250 | | case ZEND_JMPNZ: |
1251 | | case ZEND_JMPZ_EX: |
1252 | | case ZEND_JMPNZ_EX: |
1253 | | case ZEND_JMP_SET: |
1254 | | case ZEND_COALESCE: |
1255 | | case ZEND_FE_RESET_R: |
1256 | | case ZEND_FE_RESET_RW: |
1257 | | case ZEND_ASSERT_CHECK: |
1258 | | case ZEND_JMP_NULL: |
1259 | | case ZEND_BIND_INIT_STATIC_OR_JMP: |
1260 | | case ZEND_JMP_FRAMELESS: |
1261 | | opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes]; |
1262 | | break; |
1263 | | case ZEND_CATCH: |
1264 | | if (!(opline->extended_value & ZEND_LAST_CATCH)) { |
1265 | | opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes]; |
1266 | | } |
1267 | | break; |
1268 | | case ZEND_FE_FETCH_R: |
1269 | | case ZEND_FE_FETCH_RW: |
1270 | | case ZEND_SWITCH_LONG: |
1271 | | case ZEND_SWITCH_STRING: |
1272 | | case ZEND_MATCH: |
1273 | | /* relative extended_value don't have to be changed */ |
1274 | | break; |
1275 | | #endif |
1276 | 333 | case ZEND_IS_IDENTICAL: |
1277 | 449 | case ZEND_IS_NOT_IDENTICAL: |
1278 | 10.8k | case ZEND_IS_EQUAL: |
1279 | 11.8k | case ZEND_IS_NOT_EQUAL: |
1280 | 14.9k | case ZEND_IS_SMALLER: |
1281 | 15.8k | case ZEND_IS_SMALLER_OR_EQUAL: |
1282 | 15.9k | case ZEND_CASE: |
1283 | 15.9k | case ZEND_CASE_STRICT: |
1284 | 15.9k | case ZEND_ISSET_ISEMPTY_CV: |
1285 | 16.0k | case ZEND_ISSET_ISEMPTY_VAR: |
1286 | 16.8k | case ZEND_ISSET_ISEMPTY_DIM_OBJ: |
1287 | 17.0k | case ZEND_ISSET_ISEMPTY_PROP_OBJ: |
1288 | 17.0k | case ZEND_ISSET_ISEMPTY_STATIC_PROP: |
1289 | 17.2k | case ZEND_INSTANCEOF: |
1290 | 17.4k | case ZEND_TYPE_CHECK: |
1291 | 17.4k | case ZEND_DEFINED: |
1292 | 17.4k | case ZEND_IN_ARRAY: |
1293 | 17.4k | case ZEND_ARRAY_KEY_EXISTS: |
1294 | 17.4k | if (opline->result_type & IS_TMP_VAR) { |
1295 | | /* reinitialize result_type of smart branch instructions */ |
1296 | 17.4k | if (opline + 1 < end) { |
1297 | 17.4k | if ((opline+1)->opcode == ZEND_JMPZ |
1298 | 10.0k | && (opline+1)->op1_type == IS_TMP_VAR |
1299 | 10.0k | && (opline+1)->op1.var == opline->result.var) { |
1300 | 10.0k | opline->result_type = IS_SMART_BRANCH_JMPZ | IS_TMP_VAR; |
1301 | 10.0k | } else if ((opline+1)->opcode == ZEND_JMPNZ |
1302 | 1.68k | && (opline+1)->op1_type == IS_TMP_VAR |
1303 | 1.68k | && (opline+1)->op1.var == opline->result.var) { |
1304 | 1.68k | opline->result_type = IS_SMART_BRANCH_JMPNZ | IS_TMP_VAR; |
1305 | 1.68k | } |
1306 | 17.4k | } |
1307 | 17.4k | } |
1308 | 17.4k | break; |
1309 | 960k | } |
1310 | 960k | ZEND_VM_SET_OPCODE_HANDLER(opline); |
1311 | 960k | opline++; |
1312 | 960k | } |
1313 | | |
1314 | 25.5k | op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO; |
1315 | 25.5k | } |
1316 | | |
1317 | | static void zend_redo_pass_two_ex(zend_op_array *op_array, const zend_ssa *ssa) |
1318 | 90.1k | { |
1319 | 90.1k | zend_op *opline, *end; |
1320 | | #if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR |
1321 | | zend_op *old_opcodes = op_array->opcodes; |
1322 | | #endif |
1323 | | |
1324 | 90.1k | ZEND_ASSERT((op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) == 0); |
1325 | | |
1326 | 90.1k | #if !ZEND_USE_ABS_CONST_ADDR |
1327 | 90.1k | if (op_array->last_literal) { |
1328 | 87.7k | op_array->opcodes = (zend_op *) erealloc(op_array->opcodes, |
1329 | 87.7k | ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16) + |
1330 | 87.7k | sizeof(zval) * op_array->last_literal); |
1331 | 87.7k | memcpy(((char*)op_array->opcodes) + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16), |
1332 | 87.7k | op_array->literals, sizeof(zval) * op_array->last_literal); |
1333 | 87.7k | efree(op_array->literals); |
1334 | 87.7k | op_array->literals = (zval*)(((char*)op_array->opcodes) + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16)); |
1335 | 87.7k | } else { |
1336 | 2.42k | if (op_array->literals) { |
1337 | 2.41k | efree(op_array->literals); |
1338 | 2.41k | } |
1339 | 2.42k | op_array->literals = NULL; |
1340 | 2.42k | } |
1341 | 90.1k | #endif |
1342 | | |
1343 | 90.1k | opline = op_array->opcodes; |
1344 | 90.1k | end = opline + op_array->last; |
1345 | 1.82M | while (opline < end) { |
1346 | 1.73M | const zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes]; |
1347 | 1.73M | uint32_t op1_info = opline->op1_type == IS_UNUSED ? 0 : (OP1_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)); |
1348 | 1.73M | uint32_t op2_info = opline->op1_type == IS_UNUSED ? 0 : (OP2_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)); |
1349 | 1.73M | uint32_t res_info = |
1350 | 1.73M | (opline->opcode == ZEND_PRE_INC || |
1351 | 1.72M | opline->opcode == ZEND_PRE_DEC || |
1352 | 1.72M | opline->opcode == ZEND_POST_INC || |
1353 | 1.72M | opline->opcode == ZEND_POST_DEC) ? |
1354 | 10.6k | ((ssa->ops[opline - op_array->opcodes].op1_def >= 0) ? (OP1_DEF_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)) : MAY_BE_ANY) : |
1355 | 1.73M | (opline->result_type == IS_UNUSED ? 0 : (RES_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY))); |
1356 | | |
1357 | 1.73M | if (opline->op1_type == IS_CONST) { |
1358 | 282k | ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline, opline->op1); |
1359 | 282k | } |
1360 | 1.73M | if (opline->op2_type == IS_CONST) { |
1361 | 501k | ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline, opline->op2); |
1362 | 501k | } |
1363 | | |
1364 | | /* fix jumps to point to new array */ |
1365 | 1.73M | switch (opline->opcode) { |
1366 | | #if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR |
1367 | | case ZEND_JMP: |
1368 | | case ZEND_FAST_CALL: |
1369 | | opline->op1.jmp_addr = &op_array->opcodes[opline->op1.jmp_addr - old_opcodes]; |
1370 | | break; |
1371 | | case ZEND_JMPZ: |
1372 | | case ZEND_JMPNZ: |
1373 | | case ZEND_JMPZ_EX: |
1374 | | case ZEND_JMPNZ_EX: |
1375 | | case ZEND_JMP_SET: |
1376 | | case ZEND_COALESCE: |
1377 | | case ZEND_FE_RESET_R: |
1378 | | case ZEND_FE_RESET_RW: |
1379 | | case ZEND_ASSERT_CHECK: |
1380 | | case ZEND_JMP_NULL: |
1381 | | case ZEND_BIND_INIT_STATIC_OR_JMP: |
1382 | | case ZEND_JMP_FRAMELESS: |
1383 | | opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes]; |
1384 | | break; |
1385 | | case ZEND_CATCH: |
1386 | | if (!(opline->extended_value & ZEND_LAST_CATCH)) { |
1387 | | opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes]; |
1388 | | } |
1389 | | break; |
1390 | | case ZEND_FE_FETCH_R: |
1391 | | case ZEND_FE_FETCH_RW: |
1392 | | case ZEND_SWITCH_LONG: |
1393 | | case ZEND_SWITCH_STRING: |
1394 | | case ZEND_MATCH: |
1395 | | /* relative extended_value don't have to be changed */ |
1396 | | break; |
1397 | | #endif |
1398 | 2.81k | case ZEND_IS_IDENTICAL: |
1399 | 3.98k | case ZEND_IS_NOT_IDENTICAL: |
1400 | 8.42k | case ZEND_IS_EQUAL: |
1401 | 13.0k | case ZEND_IS_NOT_EQUAL: |
1402 | 24.6k | case ZEND_IS_SMALLER: |
1403 | 28.6k | case ZEND_IS_SMALLER_OR_EQUAL: |
1404 | 28.9k | case ZEND_CASE: |
1405 | 29.1k | case ZEND_CASE_STRICT: |
1406 | 29.1k | case ZEND_ISSET_ISEMPTY_CV: |
1407 | 29.2k | case ZEND_ISSET_ISEMPTY_VAR: |
1408 | 31.5k | case ZEND_ISSET_ISEMPTY_DIM_OBJ: |
1409 | 32.7k | case ZEND_ISSET_ISEMPTY_PROP_OBJ: |
1410 | 32.8k | case ZEND_ISSET_ISEMPTY_STATIC_PROP: |
1411 | 33.1k | case ZEND_INSTANCEOF: |
1412 | 34.6k | case ZEND_TYPE_CHECK: |
1413 | 34.7k | case ZEND_DEFINED: |
1414 | 34.7k | case ZEND_IN_ARRAY: |
1415 | 34.9k | case ZEND_ARRAY_KEY_EXISTS: |
1416 | 34.9k | if (opline->result_type & IS_TMP_VAR) { |
1417 | | /* reinitialize result_type of smart branch instructions */ |
1418 | 34.3k | if (opline + 1 < end) { |
1419 | 34.3k | if ((opline+1)->opcode == ZEND_JMPZ |
1420 | 8.40k | && (opline+1)->op1_type == IS_TMP_VAR |
1421 | 8.38k | && (opline+1)->op1.var == opline->result.var) { |
1422 | 8.38k | opline->result_type = IS_SMART_BRANCH_JMPZ | IS_TMP_VAR; |
1423 | 25.9k | } else if ((opline+1)->opcode == ZEND_JMPNZ |
1424 | 9.83k | && (opline+1)->op1_type == IS_TMP_VAR |
1425 | 9.81k | && (opline+1)->op1.var == opline->result.var) { |
1426 | 9.81k | opline->result_type = IS_SMART_BRANCH_JMPNZ | IS_TMP_VAR; |
1427 | 9.81k | } |
1428 | 34.3k | } |
1429 | 34.3k | } |
1430 | 34.9k | break; |
1431 | 1.73M | } |
1432 | | #ifdef ZEND_VERIFY_TYPE_INFERENCE |
1433 | | if (ssa_op->op1_use >= 0) { |
1434 | | opline->op1_use_type = ssa->var_info[ssa_op->op1_use].type; |
1435 | | } |
1436 | | if (ssa_op->op2_use >= 0) { |
1437 | | opline->op2_use_type = ssa->var_info[ssa_op->op2_use].type; |
1438 | | } |
1439 | | if (ssa_op->result_use >= 0) { |
1440 | | opline->result_use_type = ssa->var_info[ssa_op->result_use].type; |
1441 | | } |
1442 | | if (ssa_op->op1_def >= 0) { |
1443 | | opline->op1_def_type = ssa->var_info[ssa_op->op1_def].type; |
1444 | | } |
1445 | | if (ssa_op->op2_def >= 0) { |
1446 | | opline->op2_def_type = ssa->var_info[ssa_op->op2_def].type; |
1447 | | } |
1448 | | if (ssa_op->result_def >= 0) { |
1449 | | opline->result_def_type = ssa->var_info[ssa_op->result_def].type; |
1450 | | } |
1451 | | #endif |
1452 | 1.73M | zend_vm_set_opcode_handler_ex(opline, op1_info, op2_info, res_info); |
1453 | 1.73M | opline++; |
1454 | 1.73M | } |
1455 | | |
1456 | 90.1k | op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO; |
1457 | 90.1k | } |
1458 | | |
1459 | | static void zend_optimize_op_array(zend_op_array *op_array, |
1460 | | zend_optimizer_ctx *ctx) |
1461 | 0 | { |
1462 | | /* Revert pass_two() */ |
1463 | 0 | zend_revert_pass_two(op_array); |
1464 | | |
1465 | | /* Do actual optimizations */ |
1466 | 0 | zend_optimize(op_array, ctx); |
1467 | | |
1468 | | /* Redo pass_two() */ |
1469 | 0 | zend_redo_pass_two(op_array); |
1470 | |
|
1471 | 0 | if (op_array->live_range) { |
1472 | 0 | zend_recalc_live_ranges(op_array, NULL); |
1473 | 0 | } |
1474 | 0 | } |
1475 | | |
1476 | | static void zend_adjust_fcall_stack_size(const zend_op_array *op_array, const zend_optimizer_ctx *ctx) |
1477 | 0 | { |
1478 | 0 | zend_op *opline; |
1479 | |
|
1480 | 0 | opline = op_array->opcodes; |
1481 | 0 | const zend_op* end = opline + op_array->last; |
1482 | 0 | while (opline < end) { |
1483 | 0 | if (opline->opcode == ZEND_INIT_FCALL) { |
1484 | 0 | const zend_function *func = zend_hash_find_ptr( |
1485 | 0 | &ctx->script->function_table, |
1486 | 0 | Z_STR_P(RT_CONSTANT(opline, opline->op2))); |
1487 | 0 | if (func) { |
1488 | 0 | opline->op1.num = zend_vm_calc_used_stack(opline->extended_value, func); |
1489 | 0 | } |
1490 | 0 | } |
1491 | 0 | opline++; |
1492 | 0 | } |
1493 | 0 | } |
1494 | | |
1495 | | static void zend_adjust_fcall_stack_size_graph(const zend_op_array *op_array) |
1496 | 115k | { |
1497 | 115k | const zend_func_info *func_info = ZEND_FUNC_INFO(op_array); |
1498 | | |
1499 | 115k | if (func_info) { |
1500 | 90.1k | const zend_call_info *call_info =func_info->callee_info; |
1501 | | |
1502 | 195k | while (call_info) { |
1503 | 105k | zend_op *opline = call_info->caller_init_opline; |
1504 | | |
1505 | 105k | if (opline && call_info->callee_func && opline->opcode == ZEND_INIT_FCALL) { |
1506 | 101k | ZEND_ASSERT(!call_info->is_prototype); |
1507 | 101k | opline->op1.num = zend_vm_calc_used_stack(opline->extended_value, call_info->callee_func); |
1508 | 101k | } |
1509 | 105k | call_info = call_info->next_callee; |
1510 | 105k | } |
1511 | 90.1k | } |
1512 | 115k | } |
1513 | | |
1514 | 42.5k | static bool needs_live_range(const zend_op_array *op_array, const zend_op *def_opline) { |
1515 | 42.5k | const zend_func_info *func_info = ZEND_FUNC_INFO(op_array); |
1516 | 42.5k | const zend_ssa_op *ssa_op = &func_info->ssa.ops[def_opline - op_array->opcodes]; |
1517 | 42.5k | int ssa_var = ssa_op->result_def; |
1518 | 42.5k | if (ssa_var < 0) { |
1519 | | /* Be conservative. */ |
1520 | 203 | return true; |
1521 | 203 | } |
1522 | | |
1523 | | /* If the variable is used by a PHI, this may be the assignment of the final branch of a |
1524 | | * ternary/etc structure. While this is where the live range starts, the value from the other |
1525 | | * branch may also be used. As such, use the type of the PHI node for the following check. */ |
1526 | 42.3k | if (func_info->ssa.vars[ssa_var].phi_use_chain) { |
1527 | 2.15k | ssa_var = func_info->ssa.vars[ssa_var].phi_use_chain->ssa_var; |
1528 | 2.15k | } |
1529 | | |
1530 | 42.3k | uint32_t type = func_info->ssa.var_info[ssa_var].type; |
1531 | 42.3k | return (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) != 0; |
1532 | 42.5k | } |
1533 | | |
1534 | | static void zend_foreach_op_array_helper( |
1535 | 231k | zend_op_array *op_array, zend_op_array_func_t func, void *context) { |
1536 | 231k | func(op_array, context); |
1537 | 251k | for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) { |
1538 | 20.4k | zend_foreach_op_array_helper(op_array->dynamic_func_defs[i], func, context); |
1539 | 20.4k | } |
1540 | 231k | } |
1541 | | |
1542 | | void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void *context) |
1543 | 123k | { |
1544 | 123k | zval *zv; |
1545 | 123k | zend_op_array *op_array; |
1546 | | |
1547 | 123k | zend_foreach_op_array_helper(&script->main_op_array, func, context); |
1548 | | |
1549 | 317k | ZEND_HASH_MAP_FOREACH_PTR(&script->function_table, op_array) { |
1550 | 317k | zend_foreach_op_array_helper(op_array, func, context); |
1551 | 317k | } ZEND_HASH_FOREACH_END(); |
1552 | | |
1553 | 360k | ZEND_HASH_MAP_FOREACH_VAL(&script->class_table, zv) { |
1554 | 360k | if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { |
1555 | 0 | continue; |
1556 | 0 | } |
1557 | 56.4k | const zend_class_entry *ce = Z_CE_P(zv); |
1558 | 236k | ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) { |
1559 | 236k | if (op_array->scope == ce |
1560 | 48.9k | && op_array->type == ZEND_USER_FUNCTION |
1561 | 48.9k | && !(op_array->fn_flags & ZEND_ACC_ABSTRACT) |
1562 | 47.3k | && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) { |
1563 | 47.3k | zend_foreach_op_array_helper(op_array, func, context); |
1564 | 47.3k | } |
1565 | 236k | } ZEND_HASH_FOREACH_END(); |
1566 | | |
1567 | 56.4k | zend_property_info *property; |
1568 | 197k | ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, property) { |
1569 | 197k | zend_function **hooks = property->hooks; |
1570 | 197k | if (property->ce == ce && property->hooks) { |
1571 | 11.3k | for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { |
1572 | 7.56k | const zend_function *hook = hooks[i]; |
1573 | 7.56k | if (hook && hook->common.scope == ce && !(hooks[i]->op_array.fn_flags & ZEND_ACC_TRAIT_CLONE)) { |
1574 | 4.81k | zend_foreach_op_array_helper(&hooks[i]->op_array, func, context); |
1575 | 4.81k | } |
1576 | 7.56k | } |
1577 | 3.78k | } |
1578 | 197k | } ZEND_HASH_FOREACH_END(); |
1579 | 56.4k | } ZEND_HASH_FOREACH_END(); |
1580 | 123k | } |
1581 | | |
1582 | 0 | static void step_optimize_op_array(zend_op_array *op_array, void *context) { |
1583 | 0 | zend_optimize_op_array(op_array, (zend_optimizer_ctx *) context); |
1584 | 0 | } |
1585 | | |
1586 | 0 | static void step_adjust_fcall_stack_size(zend_op_array *op_array, void *context) { |
1587 | 0 | zend_adjust_fcall_stack_size(op_array, (zend_optimizer_ctx *) context); |
1588 | 0 | } |
1589 | | |
1590 | 0 | static void step_dump_after_optimizer(zend_op_array *op_array, void *context) { |
1591 | 0 | zend_dump_op_array(op_array, ZEND_DUMP_LIVE_RANGES, "after optimizer", NULL); |
1592 | 0 | } |
1593 | | |
1594 | 61.8k | static void zend_optimizer_call_registered_passes(zend_script *script, void *ctx) { |
1595 | 61.8k | for (int i = 0; i < zend_optimizer_registered_passes.last; i++) { |
1596 | 0 | if (!zend_optimizer_registered_passes.pass[i]) { |
1597 | 0 | continue; |
1598 | 0 | } |
1599 | | |
1600 | 0 | zend_optimizer_registered_passes.pass[i](script, ctx); |
1601 | 0 | } |
1602 | 61.8k | } |
1603 | | |
1604 | | ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_level, zend_long debug_level) |
1605 | 61.8k | { |
1606 | 61.8k | zend_op_array *op_array; |
1607 | 61.8k | zend_string *name; |
1608 | 61.8k | zend_optimizer_ctx ctx; |
1609 | 61.8k | zval *zv; |
1610 | | |
1611 | 61.8k | ctx.arena = zend_arena_create(64 * 1024); |
1612 | 61.8k | ctx.script = script; |
1613 | 61.8k | ctx.constants = NULL; |
1614 | 61.8k | ctx.optimization_level = optimization_level; |
1615 | 61.8k | ctx.debug_level = debug_level; |
1616 | | |
1617 | 61.8k | if ((ZEND_OPTIMIZER_PASS_6 & optimization_level) && |
1618 | 61.8k | (ZEND_OPTIMIZER_PASS_7 & optimization_level)) { |
1619 | | /* Optimize using call-graph */ |
1620 | 61.8k | zend_call_graph call_graph; |
1621 | 61.8k | zend_build_call_graph(&ctx.arena, script, &call_graph); |
1622 | | |
1623 | 61.8k | uint32_t i; |
1624 | 61.8k | zend_func_info *func_info; |
1625 | | |
1626 | 177k | for (i = 0; i < call_graph.op_arrays_count; i++) { |
1627 | 115k | zend_revert_pass_two(call_graph.op_arrays[i]); |
1628 | 115k | zend_optimize(call_graph.op_arrays[i], &ctx); |
1629 | 115k | } |
1630 | | |
1631 | 61.8k | zend_analyze_call_graph(&ctx.arena, script, &call_graph); |
1632 | | |
1633 | 177k | for (i = 0; i < call_graph.op_arrays_count; i++) { |
1634 | 115k | func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); |
1635 | 115k | if (func_info) { |
1636 | 115k | func_info->call_map = zend_build_call_map(&ctx.arena, func_info, call_graph.op_arrays[i]); |
1637 | 115k | if (call_graph.op_arrays[i]->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { |
1638 | 9.06k | zend_init_func_return_info(call_graph.op_arrays[i], script, &func_info->return_info); |
1639 | 9.06k | } |
1640 | 115k | } |
1641 | 115k | } |
1642 | | |
1643 | 177k | for (i = 0; i < call_graph.op_arrays_count; i++) { |
1644 | 115k | func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); |
1645 | 115k | if (func_info) { |
1646 | 115k | if (zend_dfa_analyze_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa) == SUCCESS) { |
1647 | 90.1k | func_info->flags = func_info->ssa.cfg.flags; |
1648 | 90.1k | } else { |
1649 | 25.5k | ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL); |
1650 | 25.5k | } |
1651 | 115k | } |
1652 | 115k | } |
1653 | | |
1654 | | //TODO: perform inner-script inference??? |
1655 | 177k | for (i = 0; i < call_graph.op_arrays_count; i++) { |
1656 | 115k | func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); |
1657 | 115k | if (func_info) { |
1658 | 90.1k | zend_dfa_optimize_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa, func_info->call_map); |
1659 | 90.1k | } |
1660 | 115k | } |
1661 | | |
1662 | 61.8k | if (debug_level & ZEND_DUMP_AFTER_PASS_7) { |
1663 | 0 | for (i = 0; i < call_graph.op_arrays_count; i++) { |
1664 | 0 | zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 7", NULL); |
1665 | 0 | } |
1666 | 0 | } |
1667 | | |
1668 | 61.8k | if (ZEND_OPTIMIZER_PASS_9 & optimization_level) { |
1669 | 177k | for (i = 0; i < call_graph.op_arrays_count; i++) { |
1670 | 115k | zend_optimize_temporary_variables(call_graph.op_arrays[i], &ctx); |
1671 | 115k | if (debug_level & ZEND_DUMP_AFTER_PASS_9) { |
1672 | 0 | zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 9", NULL); |
1673 | 0 | } |
1674 | 115k | } |
1675 | 61.8k | } |
1676 | | |
1677 | 61.8k | if (ZEND_OPTIMIZER_PASS_11 & optimization_level) { |
1678 | 177k | for (i = 0; i < call_graph.op_arrays_count; i++) { |
1679 | 115k | zend_optimizer_compact_literals(call_graph.op_arrays[i], &ctx); |
1680 | 115k | if (debug_level & ZEND_DUMP_AFTER_PASS_11) { |
1681 | 0 | zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 11", NULL); |
1682 | 0 | } |
1683 | 115k | } |
1684 | 61.8k | } |
1685 | | |
1686 | 61.8k | if (ZEND_OPTIMIZER_PASS_13 & optimization_level) { |
1687 | 177k | for (i = 0; i < call_graph.op_arrays_count; i++) { |
1688 | 115k | zend_optimizer_compact_vars(call_graph.op_arrays[i]); |
1689 | 115k | if (debug_level & ZEND_DUMP_AFTER_PASS_13) { |
1690 | 0 | zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 13", NULL); |
1691 | 0 | } |
1692 | 115k | } |
1693 | 61.8k | } |
1694 | | |
1695 | 61.8k | if (ZEND_OPTIMIZER_PASS_12 & optimization_level) { |
1696 | 177k | for (i = 0; i < call_graph.op_arrays_count; i++) { |
1697 | 115k | zend_adjust_fcall_stack_size_graph(call_graph.op_arrays[i]); |
1698 | 115k | } |
1699 | 61.8k | } |
1700 | | |
1701 | 177k | for (i = 0; i < call_graph.op_arrays_count; i++) { |
1702 | 115k | op_array = call_graph.op_arrays[i]; |
1703 | 115k | func_info = ZEND_FUNC_INFO(op_array); |
1704 | 115k | if (func_info && func_info->ssa.var_info) { |
1705 | 90.1k | zend_redo_pass_two_ex(op_array, &func_info->ssa); |
1706 | 90.1k | if (op_array->live_range) { |
1707 | 37.7k | zend_recalc_live_ranges(op_array, needs_live_range); |
1708 | 37.7k | } |
1709 | 90.1k | } else { |
1710 | 25.5k | zend_redo_pass_two(op_array); |
1711 | 25.5k | if (op_array->live_range) { |
1712 | 21.7k | zend_recalc_live_ranges(op_array, NULL); |
1713 | 21.7k | } |
1714 | 25.5k | } |
1715 | 115k | } |
1716 | | |
1717 | 177k | for (i = 0; i < call_graph.op_arrays_count; i++) { |
1718 | 115k | ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL); |
1719 | 115k | } |
1720 | 61.8k | } else { |
1721 | 0 | zend_foreach_op_array(script, step_optimize_op_array, &ctx); |
1722 | |
|
1723 | 0 | if (ZEND_OPTIMIZER_PASS_12 & optimization_level) { |
1724 | 0 | zend_foreach_op_array(script, step_adjust_fcall_stack_size, &ctx); |
1725 | 0 | } |
1726 | 0 | } |
1727 | | |
1728 | 180k | ZEND_HASH_MAP_FOREACH_VAL(&script->class_table, zv) { |
1729 | 180k | if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { |
1730 | 0 | continue; |
1731 | 0 | } |
1732 | 28.2k | const zend_class_entry *ce = Z_CE_P(zv); |
1733 | 118k | ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->function_table, name, op_array) { |
1734 | 118k | if (op_array->scope != ce && op_array->type == ZEND_USER_FUNCTION) { |
1735 | 2.08k | const zend_op_array *orig_op_array = |
1736 | 2.08k | zend_hash_find_ptr(&op_array->scope->function_table, name); |
1737 | | |
1738 | 2.08k | ZEND_ASSERT(orig_op_array != NULL); |
1739 | 2.08k | if (orig_op_array != op_array) { |
1740 | 0 | uint32_t fn_flags = op_array->fn_flags; |
1741 | 0 | uint32_t fn_flags2 = op_array->fn_flags2; |
1742 | 0 | zend_function *prototype = op_array->prototype; |
1743 | 0 | HashTable *ht = op_array->static_variables; |
1744 | |
|
1745 | 0 | *op_array = *orig_op_array; |
1746 | 0 | op_array->fn_flags = fn_flags; |
1747 | 0 | op_array->fn_flags2 = fn_flags2; |
1748 | 0 | op_array->prototype = prototype; |
1749 | 0 | op_array->static_variables = ht; |
1750 | 0 | } |
1751 | 2.08k | } |
1752 | 118k | } ZEND_HASH_FOREACH_END(); |
1753 | 28.2k | } ZEND_HASH_FOREACH_END(); |
1754 | | |
1755 | 61.8k | zend_optimizer_call_registered_passes(script, &ctx); |
1756 | | |
1757 | 61.8k | if ((debug_level & ZEND_DUMP_AFTER_OPTIMIZER) && |
1758 | 0 | (ZEND_OPTIMIZER_PASS_7 & optimization_level)) { |
1759 | 0 | zend_foreach_op_array(script, step_dump_after_optimizer, NULL); |
1760 | 0 | } |
1761 | | |
1762 | 61.8k | if (ctx.constants) { |
1763 | 0 | zend_hash_destroy(ctx.constants); |
1764 | 0 | } |
1765 | 61.8k | zend_arena_destroy(ctx.arena); |
1766 | 61.8k | } |
1767 | | |
1768 | | ZEND_API int zend_optimizer_register_pass(zend_optimizer_pass_t pass) |
1769 | 0 | { |
1770 | 0 | if (!pass) { |
1771 | 0 | return -1; |
1772 | 0 | } |
1773 | | |
1774 | 0 | if (zend_optimizer_registered_passes.last == ZEND_OPTIMIZER_MAX_REGISTERED_PASSES) { |
1775 | 0 | return -1; |
1776 | 0 | } |
1777 | | |
1778 | 0 | zend_optimizer_registered_passes.pass[ |
1779 | 0 | zend_optimizer_registered_passes.last++] = pass; |
1780 | |
|
1781 | 0 | return zend_optimizer_registered_passes.last; |
1782 | 0 | } |
1783 | | |
1784 | | ZEND_API void zend_optimizer_unregister_pass(int idx) |
1785 | 0 | { |
1786 | 0 | zend_optimizer_registered_passes.pass[idx-1] = NULL; |
1787 | 0 | } |
1788 | | |
1789 | | zend_result zend_optimizer_startup(void) |
1790 | 16 | { |
1791 | 16 | return zend_func_info_startup(); |
1792 | 16 | } |
1793 | | |
1794 | | zend_result zend_optimizer_shutdown(void) |
1795 | 0 | { |
1796 | 0 | return zend_func_info_shutdown(); |
1797 | 0 | } |