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 | http://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 "php.h"
25 #include "Optimizer/zend_optimizer.h"
26 #include "Optimizer/zend_optimizer_internal.h"
27 #include "zend_API.h"
28 #include "zend_constants.h"
29 #include "zend_execute.h"
30 #include "zend_vm.h"
31
32 #define ZEND_OP1_IS_CONST_STRING(opline) \
33 (opline->op1_type == IS_CONST && \
34 Z_TYPE(op_array->literals[(opline)->op1.constant]) == IS_STRING)
35 #define ZEND_OP2_IS_CONST_STRING(opline) \
36 (opline->op2_type == IS_CONST && \
37 Z_TYPE(op_array->literals[(opline)->op2.constant]) == IS_STRING)
38
39 typedef struct _optimizer_call_info {
40 zend_function *func;
41 zend_op *opline;
42 zend_bool is_prototype;
43 zend_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 /* break missing intentionally */
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
zend_optimize_func_calls(zend_op_array * op_array,zend_optimizer_ctx * ctx)153 void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
154 {
155 zend_op *opline = op_array->opcodes;
156 zend_op *end = opline + op_array->last;
157 int call = 0;
158 void *checkpoint;
159 optimizer_call_info *call_stack;
160
161 if (op_array->last < 2) {
162 return;
163 }
164
165 checkpoint = zend_arena_checkpoint(ctx->arena);
166 call_stack = zend_arena_calloc(&ctx->arena, op_array->last / 2, sizeof(optimizer_call_info));
167 while (opline < end) {
168 switch (opline->opcode) {
169 case ZEND_INIT_FCALL_BY_NAME:
170 case ZEND_INIT_NS_FCALL_BY_NAME:
171 case ZEND_INIT_STATIC_METHOD_CALL:
172 case ZEND_INIT_METHOD_CALL:
173 case ZEND_INIT_FCALL:
174 case ZEND_NEW:
175 /* The argument passing optimizations are valid for prototypes as well,
176 * as inheritance cannot change between ref <-> non-ref arguments. */
177 call_stack[call].func = zend_optimizer_get_called_func(
178 ctx->script, op_array, opline, &call_stack[call].is_prototype);
179 call_stack[call].try_inline =
180 !call_stack[call].is_prototype && opline->opcode != ZEND_NEW;
181 /* break missing intentionally */
182 case ZEND_INIT_DYNAMIC_CALL:
183 case ZEND_INIT_USER_CALL:
184 call_stack[call].opline = opline;
185 call_stack[call].func_arg_num = (uint32_t)-1;
186 call++;
187 break;
188 case ZEND_DO_FCALL:
189 case ZEND_DO_ICALL:
190 case ZEND_DO_UCALL:
191 case ZEND_DO_FCALL_BY_NAME:
192 call--;
193 if (call_stack[call].func && call_stack[call].opline) {
194 zend_op *fcall = call_stack[call].opline;
195
196 if (fcall->opcode == ZEND_INIT_FCALL) {
197 /* nothing to do */
198 } else if (fcall->opcode == ZEND_INIT_FCALL_BY_NAME) {
199 fcall->opcode = ZEND_INIT_FCALL;
200 fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
201 literal_dtor(&ZEND_OP2_LITERAL(fcall));
202 fcall->op2.constant = fcall->op2.constant + 1;
203 opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
204 } else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
205 fcall->opcode = ZEND_INIT_FCALL;
206 fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
207 literal_dtor(&op_array->literals[fcall->op2.constant]);
208 literal_dtor(&op_array->literals[fcall->op2.constant + 2]);
209 fcall->op2.constant = fcall->op2.constant + 1;
210 opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
211 } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
212 || fcall->opcode == ZEND_INIT_METHOD_CALL
213 || fcall->opcode == ZEND_NEW) {
214 /* We don't have specialized opcodes for this, do nothing */
215 } else {
216 ZEND_UNREACHABLE();
217 }
218
219 if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
220 && call_stack[call].try_inline) {
221 zend_try_inline_call(op_array, fcall, opline, call_stack[call].func);
222 }
223 }
224 call_stack[call].func = NULL;
225 call_stack[call].opline = NULL;
226 call_stack[call].try_inline = 0;
227 call_stack[call].func_arg_num = (uint32_t)-1;
228 break;
229 case ZEND_FETCH_FUNC_ARG:
230 case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
231 case ZEND_FETCH_OBJ_FUNC_ARG:
232 case ZEND_FETCH_DIM_FUNC_ARG:
233 if (call_stack[call - 1].func
234 && call_stack[call - 1].func_arg_num != (uint32_t)-1) {
235 if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, call_stack[call - 1].func_arg_num)) {
236 if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) {
237 opline->opcode -= 9;
238 } else {
239 opline->opcode = ZEND_FETCH_STATIC_PROP_W;
240 }
241 } else {
242 if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG
243 && opline->op2_type == IS_UNUSED) {
244 /* FETCH_DIM_FUNC_ARG supports UNUSED op2, while FETCH_DIM_R does not.
245 * Performing the replacement would create an invalid opcode. */
246 call_stack[call - 1].try_inline = 0;
247 break;
248 }
249
250 if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) {
251 opline->opcode -= 12;
252 } else {
253 opline->opcode = ZEND_FETCH_STATIC_PROP_R;
254 }
255 }
256 }
257 break;
258 case ZEND_SEND_VAL_EX:
259 if (call_stack[call - 1].func) {
260 if (opline->op2_type == IS_CONST) {
261 call_stack[call - 1].try_inline = 0;
262 break;
263 }
264
265 if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
266 /* We won't convert it into_DO_FCALL to emit error at run-time */
267 call_stack[call - 1].opline = NULL;
268 } else {
269 opline->opcode = ZEND_SEND_VAL;
270 }
271 }
272 break;
273 case ZEND_CHECK_FUNC_ARG:
274 if (call_stack[call - 1].func) {
275 if (opline->op2_type == IS_CONST) {
276 call_stack[call - 1].try_inline = 0;
277 call_stack[call - 1].func_arg_num = (uint32_t)-1;
278 break;
279 }
280
281 call_stack[call - 1].func_arg_num = opline->op2.num;
282 MAKE_NOP(opline);
283 }
284 break;
285 case ZEND_SEND_VAR_EX:
286 case ZEND_SEND_FUNC_ARG:
287 if (call_stack[call - 1].func) {
288 if (opline->op2_type == IS_CONST) {
289 call_stack[call - 1].try_inline = 0;
290 break;
291 }
292
293 call_stack[call - 1].func_arg_num = (uint32_t)-1;
294 if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
295 opline->opcode = ZEND_SEND_REF;
296 } else {
297 opline->opcode = ZEND_SEND_VAR;
298 }
299 }
300 break;
301 case ZEND_SEND_VAR_NO_REF_EX:
302 if (call_stack[call - 1].func) {
303 if (opline->op2_type == IS_CONST) {
304 call_stack[call - 1].try_inline = 0;
305 break;
306 }
307
308 if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
309 opline->opcode = ZEND_SEND_VAR_NO_REF;
310 } else if (ARG_MAY_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
311 opline->opcode = ZEND_SEND_VAL;
312 } else {
313 opline->opcode = ZEND_SEND_VAR;
314 }
315 }
316 break;
317 case ZEND_SEND_VAL:
318 case ZEND_SEND_VAR:
319 case ZEND_SEND_REF:
320 if (opline->op2_type == IS_CONST) {
321 call_stack[call - 1].try_inline = 0;
322 break;
323 }
324 break;
325 case ZEND_SEND_UNPACK:
326 case ZEND_SEND_USER:
327 case ZEND_SEND_ARRAY:
328 call_stack[call - 1].try_inline = 0;
329 break;
330 default:
331 break;
332 }
333 opline++;
334 }
335
336 zend_arena_release(&ctx->arena, checkpoint);
337 }
338