1 /*
2 +----------------------------------------------------------------------+
3 | Zend OPcache |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1998-2018 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 try_inline;
43 uint32_t func_arg_num;
44 } optimizer_call_info;
45
zend_delete_call_instructions(zend_op * opline)46 static void zend_delete_call_instructions(zend_op *opline)
47 {
48 int call = 0;
49
50 while (1) {
51 switch (opline->opcode) {
52 case ZEND_INIT_FCALL_BY_NAME:
53 case ZEND_INIT_NS_FCALL_BY_NAME:
54 case ZEND_INIT_STATIC_METHOD_CALL:
55 case ZEND_INIT_METHOD_CALL:
56 case ZEND_INIT_FCALL:
57 if (call == 0) {
58 MAKE_NOP(opline);
59 return;
60 }
61 /* break missing intentionally */
62 case ZEND_NEW:
63 case ZEND_INIT_DYNAMIC_CALL:
64 case ZEND_INIT_USER_CALL:
65 call--;
66 break;
67 case ZEND_DO_FCALL:
68 case ZEND_DO_ICALL:
69 case ZEND_DO_UCALL:
70 case ZEND_DO_FCALL_BY_NAME:
71 call++;
72 break;
73 case ZEND_SEND_VAL:
74 case ZEND_SEND_VAR:
75 if (call == 0) {
76 if (opline->op1_type == IS_CONST) {
77 MAKE_NOP(opline);
78 } else if (opline->op1_type == IS_CV) {
79 opline->opcode = ZEND_CHECK_VAR;
80 opline->extended_value = 0;
81 opline->result.var = 0;
82 } else {
83 opline->opcode = ZEND_FREE;
84 opline->extended_value = 0;
85 opline->result.var = 0;
86 }
87 }
88 break;
89 }
90 opline--;
91 }
92 }
93
zend_try_inline_call(zend_op_array * op_array,zend_op * fcall,zend_op * opline,zend_function * func)94 static void zend_try_inline_call(zend_op_array *op_array, zend_op *fcall, zend_op *opline, zend_function *func)
95 {
96 if (func->type == ZEND_USER_FUNCTION
97 && !(func->op_array.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_HAS_TYPE_HINTS))
98 && fcall->extended_value >= func->op_array.required_num_args
99 && func->op_array.opcodes[func->op_array.num_args].opcode == ZEND_RETURN) {
100
101 zend_op *ret_opline = func->op_array.opcodes + func->op_array.num_args;
102
103 if (ret_opline->op1_type == IS_CONST) {
104 uint32_t i, num_args = func->op_array.num_args;
105 num_args += (func->op_array.fn_flags & ZEND_ACC_VARIADIC) != 0;
106
107 if (fcall->opcode == ZEND_INIT_METHOD_CALL && fcall->op1_type == IS_UNUSED) {
108 /* TODO: we can't inlne methods, because $this may be used
109 * not in object context ???
110 */
111 return;
112 }
113
114 for (i = 0; i < num_args; i++) {
115 /* Don't inline functions with by-reference arguments. This would require
116 * correct handling of INDIRECT arguments. */
117 if (func->op_array.arg_info[i].pass_by_reference) {
118 return;
119 }
120 }
121
122 if (fcall->extended_value < func->op_array.num_args) {
123 /* don't inline functions with named constants in default arguments */
124 i = fcall->extended_value;
125
126 do {
127 if (Z_TYPE_P(RT_CONSTANT(&func->op_array.opcodes[i], func->op_array.opcodes[i].op2)) == IS_CONSTANT_AST) {
128 return;
129 }
130 i++;
131 } while (i < func->op_array.num_args);
132 }
133
134 if (RETURN_VALUE_USED(opline)) {
135 zval zv;
136
137 ZVAL_COPY(&zv, RT_CONSTANT(ret_opline, ret_opline->op1));
138 opline->opcode = ZEND_QM_ASSIGN;
139 opline->op1_type = IS_CONST;
140 opline->op1.constant = zend_optimizer_add_literal(op_array, &zv);
141 SET_UNUSED(opline->op2);
142 } else {
143 MAKE_NOP(opline);
144 }
145
146 zend_delete_call_instructions(opline-1);
147 }
148 }
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 call_stack[call].func = zend_optimizer_get_called_func(
174 ctx->script, op_array, opline, 0);
175 call_stack[call].try_inline = opline->opcode != ZEND_NEW;
176 /* break missing intentionally */
177 case ZEND_INIT_DYNAMIC_CALL:
178 case ZEND_INIT_USER_CALL:
179 call_stack[call].opline = opline;
180 call_stack[call].func_arg_num = (uint32_t)-1;
181 call++;
182 break;
183 case ZEND_DO_FCALL:
184 case ZEND_DO_ICALL:
185 case ZEND_DO_UCALL:
186 case ZEND_DO_FCALL_BY_NAME:
187 call--;
188 if (call_stack[call].func && call_stack[call].opline) {
189 zend_op *fcall = call_stack[call].opline;
190
191 if (fcall->opcode == ZEND_INIT_FCALL) {
192 /* nothing to do */
193 } else if (fcall->opcode == ZEND_INIT_FCALL_BY_NAME) {
194 fcall->opcode = ZEND_INIT_FCALL;
195 fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
196 literal_dtor(&ZEND_OP2_LITERAL(fcall));
197 fcall->op2.constant = fcall->op2.constant + 1;
198 opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
199 } else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
200 fcall->opcode = ZEND_INIT_FCALL;
201 fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
202 literal_dtor(&op_array->literals[fcall->op2.constant]);
203 literal_dtor(&op_array->literals[fcall->op2.constant + 2]);
204 fcall->op2.constant = fcall->op2.constant + 1;
205 opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
206 } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
207 || fcall->opcode == ZEND_INIT_METHOD_CALL
208 || fcall->opcode == ZEND_NEW) {
209 /* We don't have specialized opcodes for this, do nothing */
210 } else {
211 ZEND_ASSERT(0);
212 }
213
214 if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
215 && call_stack[call].try_inline) {
216 zend_try_inline_call(op_array, fcall, opline, call_stack[call].func);
217 }
218 }
219 call_stack[call].func = NULL;
220 call_stack[call].opline = NULL;
221 call_stack[call].try_inline = 0;
222 call_stack[call].func_arg_num = (uint32_t)-1;
223 break;
224 case ZEND_FETCH_FUNC_ARG:
225 case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
226 case ZEND_FETCH_OBJ_FUNC_ARG:
227 case ZEND_FETCH_DIM_FUNC_ARG:
228 if (call_stack[call - 1].func) {
229 ZEND_ASSERT(call_stack[call - 1].func_arg_num != (uint32_t)-1);
230 if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, call_stack[call - 1].func_arg_num)) {
231 if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) {
232 opline->opcode -= 9;
233 } else {
234 opline->opcode = ZEND_FETCH_STATIC_PROP_W;
235 }
236 } else {
237 if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG
238 && opline->op2_type == IS_UNUSED) {
239 /* FETCH_DIM_FUNC_ARG supports UNUSED op2, while FETCH_DIM_R does not.
240 * Performing the replacement would create an invalid opcode. */
241 call_stack[call - 1].try_inline = 0;
242 break;
243 }
244
245 if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) {
246 opline->opcode -= 12;
247 } else {
248 opline->opcode = ZEND_FETCH_STATIC_PROP_R;
249 }
250 }
251 }
252 break;
253 case ZEND_SEND_VAL_EX:
254 if (call_stack[call - 1].func) {
255 if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
256 /* We won't convert it into_DO_FCALL to emit error at run-time */
257 call_stack[call - 1].opline = NULL;
258 } else {
259 opline->opcode = ZEND_SEND_VAL;
260 }
261 }
262 break;
263 case ZEND_CHECK_FUNC_ARG:
264 if (call_stack[call - 1].func) {
265 call_stack[call - 1].func_arg_num = opline->op2.num;
266 MAKE_NOP(opline);
267 }
268 break;
269 case ZEND_SEND_VAR_EX:
270 case ZEND_SEND_FUNC_ARG:
271 if (call_stack[call - 1].func) {
272 call_stack[call - 1].func_arg_num = (uint32_t)-1;
273 if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
274 opline->opcode = ZEND_SEND_REF;
275 } else {
276 opline->opcode = ZEND_SEND_VAR;
277 }
278 }
279 break;
280 case ZEND_SEND_VAR_NO_REF_EX:
281 if (call_stack[call - 1].func) {
282 if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
283 opline->opcode = ZEND_SEND_VAR_NO_REF;
284 } else if (ARG_MAY_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
285 opline->opcode = ZEND_SEND_VAL;
286 } else {
287 opline->opcode = ZEND_SEND_VAR;
288 }
289 }
290 break;
291 case ZEND_SEND_UNPACK:
292 case ZEND_SEND_USER:
293 case ZEND_SEND_ARRAY:
294 call_stack[call - 1].try_inline = 0;
295 break;
296 default:
297 break;
298 }
299 opline++;
300 }
301
302 zend_arena_release(&ctx->arena, checkpoint);
303 }
304