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: Dmitry Stogov <dmitry@php.net> |
16 | Xinchen Hui <laruence@php.net> |
17 +----------------------------------------------------------------------+
18 */
19
20 /* pass 4
21 * - optimize INIT_FCALL_BY_NAME to DO_FCALL
22 */
23
24 #include "Optimizer/zend_optimizer.h"
25 #include "Optimizer/zend_optimizer_internal.h"
26 #include "zend_API.h"
27 #include "zend_constants.h"
28 #include "zend_execute.h"
29 #include "zend_vm.h"
30
31 #define ZEND_OP1_IS_CONST_STRING(opline) \
32 (opline->op1_type == IS_CONST && \
33 Z_TYPE(op_array->literals[(opline)->op1.constant]) == IS_STRING)
34 #define ZEND_OP2_IS_CONST_STRING(opline) \
35 (opline->op2_type == IS_CONST && \
36 Z_TYPE(op_array->literals[(opline)->op2.constant]) == IS_STRING)
37
38 typedef struct _optimizer_call_info {
39 zend_function *func;
40 zend_op *opline;
41 zend_op *last_check_func_arg_opline;
42 bool is_prototype;
43 bool try_inline;
44 uint32_t func_arg_num;
45 } optimizer_call_info;
46
zend_delete_call_instructions(zend_op * opline)47 static void zend_delete_call_instructions(zend_op *opline)
48 {
49 int call = 0;
50
51 while (1) {
52 switch (opline->opcode) {
53 case ZEND_INIT_FCALL_BY_NAME:
54 case ZEND_INIT_NS_FCALL_BY_NAME:
55 case ZEND_INIT_STATIC_METHOD_CALL:
56 case ZEND_INIT_METHOD_CALL:
57 case ZEND_INIT_FCALL:
58 if (call == 0) {
59 MAKE_NOP(opline);
60 return;
61 }
62 ZEND_FALLTHROUGH;
63 case ZEND_NEW:
64 case ZEND_INIT_DYNAMIC_CALL:
65 case ZEND_INIT_USER_CALL:
66 call--;
67 break;
68 case ZEND_DO_FCALL:
69 case ZEND_DO_ICALL:
70 case ZEND_DO_UCALL:
71 case ZEND_DO_FCALL_BY_NAME:
72 call++;
73 break;
74 case ZEND_SEND_VAL:
75 case ZEND_SEND_VAR:
76 if (call == 0) {
77 if (opline->op1_type == IS_CONST) {
78 MAKE_NOP(opline);
79 } else if (opline->op1_type == IS_CV) {
80 opline->opcode = ZEND_CHECK_VAR;
81 opline->extended_value = 0;
82 opline->result.var = 0;
83 } else {
84 opline->opcode = ZEND_FREE;
85 opline->extended_value = 0;
86 opline->result.var = 0;
87 }
88 }
89 break;
90 }
91 opline--;
92 }
93 }
94
zend_try_inline_call(zend_op_array * op_array,zend_op * fcall,zend_op * opline,zend_function * func)95 static void zend_try_inline_call(zend_op_array *op_array, zend_op *fcall, zend_op *opline, zend_function *func)
96 {
97 if (func->type == ZEND_USER_FUNCTION
98 && !(func->op_array.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_HAS_TYPE_HINTS))
99 /* TODO: function copied from trait may be inconsistent ??? */
100 && !(func->op_array.fn_flags & (ZEND_ACC_TRAIT_CLONE))
101 && fcall->extended_value >= func->op_array.required_num_args
102 && func->op_array.opcodes[func->op_array.num_args].opcode == ZEND_RETURN) {
103
104 zend_op *ret_opline = func->op_array.opcodes + func->op_array.num_args;
105
106 if (ret_opline->op1_type == IS_CONST) {
107 uint32_t i, num_args = func->op_array.num_args;
108 num_args += (func->op_array.fn_flags & ZEND_ACC_VARIADIC) != 0;
109
110 if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
111 && !(func->op_array.fn_flags & ZEND_ACC_STATIC)) {
112 /* Don't inline static call to instance method. */
113 return;
114 }
115
116 for (i = 0; i < num_args; i++) {
117 /* Don't inline functions with by-reference arguments. This would require
118 * correct handling of INDIRECT arguments. */
119 if (ZEND_ARG_SEND_MODE(&func->op_array.arg_info[i])) {
120 return;
121 }
122 }
123
124 if (fcall->extended_value < func->op_array.num_args) {
125 /* don't inline functions with named constants in default arguments */
126 i = fcall->extended_value;
127
128 do {
129 if (Z_TYPE_P(CRT_CONSTANT_EX(&func->op_array, &func->op_array.opcodes[i], func->op_array.opcodes[i].op2)) == IS_CONSTANT_AST) {
130 return;
131 }
132 i++;
133 } while (i < func->op_array.num_args);
134 }
135
136 if (RETURN_VALUE_USED(opline)) {
137 zval zv;
138
139 ZVAL_COPY(&zv, CRT_CONSTANT_EX(&func->op_array, ret_opline, ret_opline->op1));
140 opline->opcode = ZEND_QM_ASSIGN;
141 opline->op1_type = IS_CONST;
142 opline->op1.constant = zend_optimizer_add_literal(op_array, &zv);
143 SET_UNUSED(opline->op2);
144 } else {
145 MAKE_NOP(opline);
146 }
147
148 zend_delete_call_instructions(opline-1);
149 }
150 }
151 }
152
153 /* arg_num is 1-based here, to match SEND encoding. */
has_known_send_mode(const optimizer_call_info * info,uint32_t arg_num)154 static bool has_known_send_mode(const optimizer_call_info *info, uint32_t arg_num)
155 {
156 if (!info->func) {
157 return false;
158 }
159
160 /* For prototype functions we should not make assumptions about arguments that are not part of
161 * the signature: And inheriting method can add an optional by-ref argument. */
162 return !info->is_prototype
163 || arg_num <= info->func->common.num_args
164 || (info->func->common.fn_flags & ZEND_ACC_VARIADIC);
165 }
166
zend_optimize_func_calls(zend_op_array * op_array,zend_optimizer_ctx * ctx)167 void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
168 {
169 zend_op *opline = op_array->opcodes;
170 zend_op *end = opline + op_array->last;
171 int call = 0;
172 void *checkpoint;
173 optimizer_call_info *call_stack;
174
175 if (op_array->last < 2) {
176 return;
177 }
178
179 checkpoint = zend_arena_checkpoint(ctx->arena);
180 call_stack = zend_arena_calloc(&ctx->arena, op_array->last / 2, sizeof(optimizer_call_info));
181 while (opline < end) {
182 switch (opline->opcode) {
183 case ZEND_INIT_FCALL_BY_NAME:
184 case ZEND_INIT_NS_FCALL_BY_NAME:
185 case ZEND_INIT_STATIC_METHOD_CALL:
186 case ZEND_INIT_METHOD_CALL:
187 case ZEND_INIT_FCALL:
188 case ZEND_NEW:
189 /* The argument passing optimizations are valid for prototypes as well,
190 * as inheritance cannot change between ref <-> non-ref arguments. */
191 call_stack[call].func = zend_optimizer_get_called_func(
192 ctx->script, op_array, opline, &call_stack[call].is_prototype);
193 call_stack[call].try_inline =
194 !call_stack[call].is_prototype && opline->opcode != ZEND_NEW;
195 ZEND_FALLTHROUGH;
196 case ZEND_INIT_DYNAMIC_CALL:
197 case ZEND_INIT_USER_CALL:
198 call_stack[call].opline = opline;
199 call_stack[call].func_arg_num = (uint32_t)-1;
200 call++;
201 break;
202 case ZEND_DO_FCALL:
203 case ZEND_DO_ICALL:
204 case ZEND_DO_UCALL:
205 case ZEND_DO_FCALL_BY_NAME:
206 case ZEND_CALLABLE_CONVERT:
207 call--;
208 if (call_stack[call].func && call_stack[call].opline) {
209 zend_op *fcall = call_stack[call].opline;
210
211 if (fcall->opcode == ZEND_INIT_FCALL) {
212 /* nothing to do */
213 } else if (fcall->opcode == ZEND_INIT_FCALL_BY_NAME) {
214 fcall->opcode = ZEND_INIT_FCALL;
215 fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
216 literal_dtor(&ZEND_OP2_LITERAL(fcall));
217 fcall->op2.constant = fcall->op2.constant + 1;
218 if (opline->opcode != ZEND_CALLABLE_CONVERT) {
219 opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
220 }
221 } else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
222 fcall->opcode = ZEND_INIT_FCALL;
223 fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
224 literal_dtor(&op_array->literals[fcall->op2.constant]);
225 literal_dtor(&op_array->literals[fcall->op2.constant + 2]);
226 fcall->op2.constant = fcall->op2.constant + 1;
227 if (opline->opcode != ZEND_CALLABLE_CONVERT) {
228 opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
229 }
230 } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
231 || fcall->opcode == ZEND_INIT_METHOD_CALL
232 || fcall->opcode == ZEND_NEW) {
233 /* We don't have specialized opcodes for this, do nothing */
234 } else {
235 ZEND_UNREACHABLE();
236 }
237
238 if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
239 && call_stack[call].try_inline
240 && opline->opcode != ZEND_CALLABLE_CONVERT) {
241 zend_try_inline_call(op_array, fcall, opline, call_stack[call].func);
242 }
243 }
244 call_stack[call].func = NULL;
245 call_stack[call].opline = NULL;
246 call_stack[call].try_inline = 0;
247 call_stack[call].func_arg_num = (uint32_t)-1;
248 break;
249 case ZEND_FETCH_FUNC_ARG:
250 case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
251 case ZEND_FETCH_OBJ_FUNC_ARG:
252 case ZEND_FETCH_DIM_FUNC_ARG:
253 if (call_stack[call - 1].func_arg_num != (uint32_t)-1
254 && has_known_send_mode(&call_stack[call - 1], call_stack[call - 1].func_arg_num)) {
255 if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, call_stack[call - 1].func_arg_num)) {
256 /* There's no TMP specialization for FETCH_OBJ_W/FETCH_DIM_W. Avoid
257 * converting it and error at runtime in the FUNC_ARG variant. */
258 if ((opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG || opline->opcode == ZEND_FETCH_DIM_FUNC_ARG)
259 && (opline->op1_type == IS_TMP_VAR || call_stack[call - 1].last_check_func_arg_opline == NULL)) {
260 /* Don't remove the associated CHECK_FUNC_ARG opcode. */
261 call_stack[call - 1].last_check_func_arg_opline = NULL;
262 break;
263 }
264 if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) {
265 opline->opcode -= 9;
266 } else {
267 opline->opcode = ZEND_FETCH_STATIC_PROP_W;
268 }
269 } else {
270 if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG
271 && opline->op2_type == IS_UNUSED) {
272 /* FETCH_DIM_FUNC_ARG supports UNUSED op2, while FETCH_DIM_R does not.
273 * Performing the replacement would create an invalid opcode. */
274 call_stack[call - 1].try_inline = 0;
275 break;
276 }
277
278 if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) {
279 opline->opcode -= 12;
280 } else {
281 opline->opcode = ZEND_FETCH_STATIC_PROP_R;
282 }
283 }
284 }
285 break;
286 case ZEND_SEND_VAL_EX:
287 if (opline->op2_type == IS_CONST) {
288 call_stack[call - 1].try_inline = 0;
289 break;
290 }
291
292 if (has_known_send_mode(&call_stack[call - 1], opline->op2.num)) {
293 if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
294 /* We won't convert it into_DO_FCALL to emit error at run-time */
295 call_stack[call - 1].opline = NULL;
296 } else {
297 opline->opcode = ZEND_SEND_VAL;
298 }
299 }
300 break;
301 case ZEND_CHECK_FUNC_ARG:
302 if (opline->op2_type == IS_CONST) {
303 call_stack[call - 1].try_inline = 0;
304 call_stack[call - 1].func_arg_num = (uint32_t)-1;
305 break;
306 }
307
308 if (has_known_send_mode(&call_stack[call - 1], opline->op2.num)) {
309 call_stack[call - 1].func_arg_num = opline->op2.num;
310 call_stack[call - 1].last_check_func_arg_opline = opline;
311 }
312 break;
313 case ZEND_SEND_FUNC_ARG:
314 /* Don't transform SEND_FUNC_ARG if any FETCH opcodes weren't transformed. */
315 if (call_stack[call - 1].last_check_func_arg_opline == NULL) {
316 if (opline->op2_type == IS_CONST) {
317 call_stack[call - 1].try_inline = 0;
318 }
319 break;
320 }
321 MAKE_NOP(call_stack[call - 1].last_check_func_arg_opline);
322 call_stack[call - 1].last_check_func_arg_opline = NULL;
323 ZEND_FALLTHROUGH;
324 case ZEND_SEND_VAR_EX:
325 if (opline->op2_type == IS_CONST) {
326 call_stack[call - 1].try_inline = 0;
327 break;
328 }
329
330 if (has_known_send_mode(&call_stack[call - 1], opline->op2.num)) {
331 call_stack[call - 1].func_arg_num = (uint32_t)-1;
332 if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
333 opline->opcode = ZEND_SEND_REF;
334 } else {
335 opline->opcode = ZEND_SEND_VAR;
336 }
337 }
338 break;
339 case ZEND_SEND_VAR_NO_REF_EX:
340 if (opline->op2_type == IS_CONST) {
341 call_stack[call - 1].try_inline = 0;
342 break;
343 }
344
345 if (has_known_send_mode(&call_stack[call - 1], opline->op2.num)) {
346 if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
347 opline->opcode = ZEND_SEND_VAR_NO_REF;
348 } else if (ARG_MAY_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
349 opline->opcode = ZEND_SEND_VAL;
350 } else {
351 opline->opcode = ZEND_SEND_VAR;
352 }
353 }
354 break;
355 case ZEND_SEND_VAL:
356 case ZEND_SEND_VAR:
357 case ZEND_SEND_REF:
358 if (opline->op2_type == IS_CONST) {
359 call_stack[call - 1].try_inline = 0;
360 break;
361 }
362 break;
363 case ZEND_SEND_UNPACK:
364 case ZEND_SEND_USER:
365 case ZEND_SEND_ARRAY:
366 call_stack[call - 1].try_inline = 0;
367 break;
368 default:
369 break;
370 }
371 opline++;
372 }
373
374 zend_arena_release(&ctx->arena, checkpoint);
375 }
376