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