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